@tool-bridge/server 0.17.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.
Files changed (108) hide show
  1. package/dist/{airtable-NOSOMOVB.js → airtable-ZNWHIJMO.js} +4 -4
  2. package/dist/{alt_text_generator_ai-BHIJ4LW5.js → alt_text_generator_ai-X7RMDDM5.js} +4 -4
  3. package/dist/{amap-QRLP4TWZ.js → amap-3VER75VH.js} +4 -4
  4. package/dist/{anthropic-IEOI36EV.js → anthropic-N7NTB3UC.js} +4 -4
  5. package/dist/{apify-3CCYTQVK.js → apify-JB5P5AJI.js} +4 -4
  6. package/dist/{brave_search-PZNYDQG7.js → brave_search-USL5OOBS.js} +4 -4
  7. package/dist/{chunk-U5UHT7CX.js → chunk-3WL6EXYX.js} +1 -1
  8. package/dist/{chunk-YASUMV2K.js → chunk-6ANLYNFB.js} +1 -1
  9. package/dist/{chunk-WW2IMPQK.js → chunk-6X55ALDX.js} +3 -3
  10. package/dist/{chunk-TLNUIPFZ.js → chunk-RVUERWUZ.js} +2698 -247
  11. package/dist/{chunk-RVWZN3CI.js → chunk-WIGQCP6O.js} +1306 -207
  12. package/dist/{circleci-VSJ4O5FC.js → circleci-LBGB25LP.js} +4 -4
  13. package/dist/{clerk-CC2JZBZI.js → clerk-6RX37CNZ.js} +4 -4
  14. package/dist/{cloudflare_dns-PJEBSEYQ.js → cloudflare_dns-NQGW7FHV.js} +4 -4
  15. package/dist/{cohere-KBACYSPF.js → cohere-XPC2NAEW.js} +4 -4
  16. package/dist/{confluence-2PX3O7G5.js → confluence-ZUG2LT5A.js} +4 -4
  17. package/dist/{convertapi-BVOTD2PJ.js → convertapi-ZLXKWV3L.js} +3 -3
  18. package/dist/{currencyapi-6ZQ2VNQ7.js → currencyapi-T3A3HSOD.js} +4 -4
  19. package/dist/{deepseek-MB3WVVTB.js → deepseek-NJMOD23E.js} +4 -4
  20. package/dist/{docker_hub-7CLQL2IJ.js → docker_hub-D2MQCSJ7.js} +4 -4
  21. package/dist/{dropbox-VFYDVUNK.js → dropbox-MI6U3SOT.js} +4 -4
  22. package/dist/{dub-QN3EETJY.js → dub-P2BYBYV4.js} +4 -4
  23. package/dist/{e2b-NKC3Y776.js → e2b-BGKIAE54.js} +4 -4
  24. package/dist/{exa-KKZ6F25K.js → exa-EUY7VV4C.js} +4 -4
  25. package/dist/{fal_ai-25LYJD2O.js → fal_ai-AAOG5FYJ.js} +4 -4
  26. package/dist/{feishu-NRT55B3Q.js → feishu-AN5IJYOW.js} +2 -2
  27. package/dist/{feishu_custom_bot-3W3XXUG4.js → feishu_custom_bot-BWGMNVXF.js} +3 -3
  28. package/dist/{figma-4FLP35TC.js → figma-SJSWYCGD.js} +4 -4
  29. package/dist/{firehydrant-BLXPLZ3X.js → firehydrant-MOXK2PST.js} +4 -4
  30. package/dist/{fixer-4OR62TGE.js → fixer-ISIDUDZU.js} +4 -4
  31. package/dist/{fly-MYCKOZZK.js → fly-U4KPSGBF.js} +4 -4
  32. package/dist/{front-3DFYVMF3.js → front-ZHM2YX2E.js} +4 -4
  33. package/dist/{geocodio-CXMD6VKA.js → geocodio-SVCCAXAV.js} +4 -4
  34. package/dist/{ghost-4UUE737W.js → ghost-INTR7FIY.js} +4 -4
  35. package/dist/{github-WFS2ON2N.js → github-T275VVWR.js} +4 -4
  36. package/dist/{gmail-KW7FXA7A.js → gmail-KSSPOE67.js} +4 -4
  37. package/dist/{googlecalendar-AMH5JQMC.js → googlecalendar-JB5RW27L.js} +4 -4
  38. package/dist/{googledocs-R5VA6CKD.js → googledocs-G522PUGW.js} +3 -3
  39. package/dist/{grafana-IT6DWIVB.js → grafana-44ZPIV5P.js} +4 -4
  40. package/dist/{graphhopper-TYFDJZWC.js → graphhopper-2MORXLOW.js} +4 -4
  41. package/dist/index.d.ts +74 -14
  42. package/dist/index.js +3 -3
  43. package/dist/{ip2proxy-GJ4HXVXO.js → ip2proxy-R4K4KEMI.js} +4 -4
  44. package/dist/{ipgeolocation_io-OAGR6PRF.js → ipgeolocation_io-H4J55OGI.js} +4 -4
  45. package/dist/{ipqualityscore-BCKAMDGL.js → ipqualityscore-XGID4OHA.js} +4 -4
  46. package/dist/{jina_ai-N3NXRVIZ.js → jina_ai-7ZVDOR3Z.js} +4 -4
  47. package/dist/{jira-CHLBKWLO.js → jira-CTIYZTT3.js} +4 -4
  48. package/dist/{langsmith-OIKOTPY3.js → langsmith-ISP567FS.js} +4 -4
  49. package/dist/{lemlist-VAKYPKS3.js → lemlist-OGKK272F.js} +4 -4
  50. package/dist/{linear-Q6QTPJ5R.js → linear-6JX4E3JI.js} +3 -3
  51. package/dist/{logsnag-SQOQBTES.js → logsnag-BVB36ZCL.js} +4 -4
  52. package/dist/main.js +102 -102
  53. package/dist/{mattermost-G2KBEICM.js → mattermost-UECVPZGY.js} +4 -4
  54. package/dist/{mem0-D2FPLYDX.js → mem0-EUA7S2OO.js} +4 -4
  55. package/dist/{memos-76PTWV5V.js → memos-BYSBJRWZ.js} +4 -4
  56. package/dist/{metabase-TWSIDVBM.js → metabase-4JXZH3YG.js} +4 -4
  57. package/dist/{mistral_ai-TP7TQUEJ.js → mistral_ai-2C5BTBKW.js} +4 -4
  58. package/dist/{mother_duck-UXY5THYU.js → mother_duck-ZGNNNR3X.js} +4 -4
  59. package/dist/{ngrok-RZW2ISJK.js → ngrok-NTPGMRXI.js} +4 -4
  60. package/dist/{notes-B2I6FWK2.js → notes-KGYXX6N6.js} +1 -1
  61. package/dist/{notion-QOKRUE6M.js → notion-EX3X2G7G.js} +4 -4
  62. package/dist/{open_exchange_rates-ODLFWEJ7.js → open_exchange_rates-2V2LIGPS.js} +4 -4
  63. package/dist/{openai-ZPBPPPGT.js → openai-KQFQPK7Z.js} +4 -4
  64. package/dist/{opengraph_io-EOI5UJKT.js → opengraph_io-B63SEYJY.js} +4 -4
  65. package/dist/{openrouter-BUWLF3BG.js → openrouter-62A4H62Q.js} +4 -4
  66. package/dist/{outline-NXS3QRXO.js → outline-QC33XLCK.js} +4 -4
  67. package/dist/{paddle-T4MSKQ76.js → paddle-HDZG35Y7.js} +4 -4
  68. package/dist/{perplexity-WW5N2LW7.js → perplexity-M7LOMJWA.js} +4 -4
  69. package/dist/{pinecone-RY5AZPQM.js → pinecone-Y7YCJRZM.js} +4 -4
  70. package/dist/{pipedrive-OHYQNZTU.js → pipedrive-FLJA62UX.js} +4 -4
  71. package/dist/{polar-XZIAHXS5.js → polar-AE55Q425.js} +4 -4
  72. package/dist/{posthog-DTPTWORY.js → posthog-IAY5D3YW.js} +4 -4
  73. package/dist/{postmark-F4UCQKC3.js → postmark-NMKAXDXZ.js} +4 -4
  74. package/dist/{prerender-P3Y2MLIC.js → prerender-CDJM7I7N.js} +4 -4
  75. package/dist/{pubmed-RHU3GXFB.js → pubmed-N437ZJOR.js} +3 -3
  76. package/dist/{railway-VB6SRLI6.js → railway-NBWYXCUM.js} +3 -3
  77. package/dist/{readwise-DIMSKJZR.js → readwise-QRO3UC3R.js} +4 -4
  78. package/dist/{render-XCCDCEEK.js → render-NEW7SROG.js} +4 -4
  79. package/dist/{replicate-IUEOQ4JC.js → replicate-73YQLRTY.js} +4 -4
  80. package/dist/{resend-N5MJZH5E.js → resend-R5BAZFHZ.js} +4 -4
  81. package/dist/{rootly-25T4NBTU.js → rootly-5BLALPCS.js} +4 -4
  82. package/dist/{scrapfly-PK4XFXUA.js → scrapfly-EHFLNNUZ.js} +4 -4
  83. package/dist/{scrapingbee-ITPBMWQU.js → scrapingbee-UXLYS72S.js} +4 -4
  84. package/dist/{screenshot_fyi-5U6ZYPKR.js → screenshot_fyi-KC3NLTJ6.js} +4 -4
  85. package/dist/{semantic_scholar-BJGCI2MU.js → semantic_scholar-SA5OIUWA.js} +4 -4
  86. package/dist/{sentry-63IC263P.js → sentry-BAE3XLJN.js} +4 -4
  87. package/dist/{shodan-2WBIFHFN.js → shodan-FJR6MKKW.js} +4 -4
  88. package/dist/{shopify-XLLJRAGM.js → shopify-7OA6SLN5.js} +4 -4
  89. package/dist/{stripe-WLVCY3UN.js → stripe-MUWDZGGF.js} +3 -3
  90. package/dist/{supabase-BKNZ35ER.js → supabase-SWWGNWYR.js} +4 -4
  91. package/dist/{tavily-YFSDEUTL.js → tavily-XQNJ5EDI.js} +4 -4
  92. package/dist/{telegram-FC35W74G.js → telegram-RR6JPZOG.js} +4 -4
  93. package/dist/{telnyx-MI32XZUG.js → telnyx-7PKXOWSO.js} +4 -4
  94. package/dist/{todoist-4ZFJKZC6.js → todoist-63X5IUJE.js} +4 -4
  95. package/dist/{trello-UOGFNMK4.js → trello-MOG63DAI.js} +4 -4
  96. package/dist/{turso-YTSTW3ZS.js → turso-ENWTSHDL.js} +4 -4
  97. package/dist/{twilio-XAJDFUQH.js → twilio-FDXUN7VX.js} +3 -3
  98. package/dist/{umami-ZVIUBPAI.js → umami-TM7PROWF.js} +4 -4
  99. package/dist/{unsplash-IU3PLDNS.js → unsplash-NWXLKSOW.js} +4 -4
  100. package/dist/{upstash_redis-3EYJW7SY.js → upstash_redis-CUTQ5L2N.js} +4 -4
  101. package/dist/{uptimerobot-PJG2ASRU.js → uptimerobot-NNO32M3I.js} +3 -3
  102. package/dist/{v2ex-KFQWWGFC.js → v2ex-24KMSWS7.js} +4 -4
  103. package/dist/{vercel-I2ZDOQRL.js → vercel-B45LPF6I.js} +4 -4
  104. package/dist/{wolfram_alpha_api-BNXOJMB6.js → wolfram_alpha_api-DKJWVR6G.js} +4 -4
  105. package/dist/{wordpress-36D7BSMP.js → wordpress-XYFSDZXY.js} +4 -4
  106. package/dist/{workos-6X4JJRRC.js → workos-BEM7EPHM.js} +4 -4
  107. package/dist/{zhihu-CIXKCHIU.js → zhihu-CFHZ4NC7.js} +4 -4
  108. package/package.json +3 -3
@@ -1328,16 +1328,16 @@ function cleanEnum(obj) {
1328
1328
  }
1329
1329
  function base64ToUint8Array(base644) {
1330
1330
  const binaryString = atob(base644);
1331
- const bytes = new Uint8Array(binaryString.length);
1331
+ const bytes2 = new Uint8Array(binaryString.length);
1332
1332
  for (let i = 0; i < binaryString.length; i++) {
1333
- bytes[i] = binaryString.charCodeAt(i);
1333
+ bytes2[i] = binaryString.charCodeAt(i);
1334
1334
  }
1335
- return bytes;
1335
+ return bytes2;
1336
1336
  }
1337
- function uint8ArrayToBase64(bytes) {
1337
+ function uint8ArrayToBase64(bytes2) {
1338
1338
  let binaryString = "";
1339
- for (let i = 0; i < bytes.length; i++) {
1340
- binaryString += String.fromCharCode(bytes[i]);
1339
+ for (let i = 0; i < bytes2.length; i++) {
1340
+ binaryString += String.fromCharCode(bytes2[i]);
1341
1341
  }
1342
1342
  return btoa(binaryString);
1343
1343
  }
@@ -1346,22 +1346,22 @@ function base64urlToUint8Array(base64url3) {
1346
1346
  const padding = "=".repeat((4 - base644.length % 4) % 4);
1347
1347
  return base64ToUint8Array(base644 + padding);
1348
1348
  }
1349
- function uint8ArrayToBase64url(bytes) {
1350
- return uint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1349
+ function uint8ArrayToBase64url(bytes2) {
1350
+ return uint8ArrayToBase64(bytes2).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1351
1351
  }
1352
1352
  function hexToUint8Array(hex3) {
1353
1353
  const cleanHex = hex3.replace(/^0x/, "");
1354
1354
  if (cleanHex.length % 2 !== 0) {
1355
1355
  throw new Error("Invalid hex string length");
1356
1356
  }
1357
- const bytes = new Uint8Array(cleanHex.length / 2);
1357
+ const bytes2 = new Uint8Array(cleanHex.length / 2);
1358
1358
  for (let i = 0; i < cleanHex.length; i += 2) {
1359
- bytes[i / 2] = Number.parseInt(cleanHex.slice(i, i + 2), 16);
1359
+ bytes2[i / 2] = Number.parseInt(cleanHex.slice(i, i + 2), 16);
1360
1360
  }
1361
- return bytes;
1361
+ return bytes2;
1362
1362
  }
1363
- function uint8ArrayToHex(bytes) {
1364
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1363
+ function uint8ArrayToHex(bytes2) {
1364
+ return Array.from(bytes2).map((b) => b.toString(16).padStart(2, "0")).join("");
1365
1365
  }
1366
1366
  var Class = class {
1367
1367
  constructor(..._args) {
@@ -14873,6 +14873,10 @@ function secretRefsInConfig(config2) {
14873
14873
  const refs = [];
14874
14874
  const oauthMcp = c.kind === "mcp" && c.auth === "oauth";
14875
14875
  if (!oauthMcp && typeof c.authRef === "string" && c.authRef.length > 0) refs.push(c.authRef);
14876
+ if (oauthMcp && c.oauthClient !== null && typeof c.oauthClient === "object") {
14877
+ const secretRef = c.oauthClient.clientSecretRef;
14878
+ if (typeof secretRef === "string" && secretRef.length > 0) refs.push(secretRef);
14879
+ }
14876
14880
  if (typeof c.skRef === "string" && c.skRef.length > 0) refs.push(c.skRef);
14877
14881
  return refs;
14878
14882
  }
@@ -14889,17 +14893,17 @@ function assertSecretRefUse(scopes, config2) {
14889
14893
 
14890
14894
  // ../core/src/encoding/base64url.ts
14891
14895
  var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
14892
- function base64urlEncode(bytes) {
14896
+ function base64urlEncode(bytes2) {
14893
14897
  let out = "";
14894
- for (let i = 0; i < bytes.length; i += 3) {
14895
- const a = bytes[i] ?? 0;
14896
- const b = bytes[i + 1] ?? 0;
14897
- const c = bytes[i + 2] ?? 0;
14898
+ for (let i = 0; i < bytes2.length; i += 3) {
14899
+ const a = bytes2[i] ?? 0;
14900
+ const b = bytes2[i + 1] ?? 0;
14901
+ const c = bytes2[i + 2] ?? 0;
14898
14902
  const n = a << 16 | b << 8 | c;
14899
14903
  out += ALPHABET[n >>> 18 & 63];
14900
14904
  out += ALPHABET[n >>> 12 & 63];
14901
- if (i + 1 < bytes.length) out += ALPHABET[n >>> 6 & 63];
14902
- if (i + 2 < bytes.length) out += ALPHABET[n & 63];
14905
+ if (i + 1 < bytes2.length) out += ALPHABET[n >>> 6 & 63];
14906
+ if (i + 2 < bytes2.length) out += ALPHABET[n & 63];
14903
14907
  }
14904
14908
  return out;
14905
14909
  }
@@ -14907,7 +14911,7 @@ function base64urlDecode(input) {
14907
14911
  if (input.length % 4 === 1 || !/^[A-Za-z0-9_-]*$/.test(input)) {
14908
14912
  throw new Error("base64urlDecode: malformed base64url input");
14909
14913
  }
14910
- const bytes = [];
14914
+ const bytes2 = [];
14911
14915
  for (let i = 0; i < input.length; i += 4) {
14912
14916
  const chunk = input.slice(i, i + 4);
14913
14917
  const a = ALPHABET.indexOf(chunk[0] ?? "");
@@ -14915,11 +14919,11 @@ function base64urlDecode(input) {
14915
14919
  const c = chunk.length >= 3 ? ALPHABET.indexOf(chunk[2] ?? "") : 0;
14916
14920
  const d = chunk.length >= 4 ? ALPHABET.indexOf(chunk[3] ?? "") : 0;
14917
14921
  const n = a << 18 | b << 12 | c << 6 | d;
14918
- bytes.push(n >>> 16 & 255);
14919
- if (chunk.length >= 3) bytes.push(n >>> 8 & 255);
14920
- if (chunk.length >= 4) bytes.push(n & 255);
14922
+ bytes2.push(n >>> 16 & 255);
14923
+ if (chunk.length >= 3) bytes2.push(n >>> 8 & 255);
14924
+ if (chunk.length >= 4) bytes2.push(n & 255);
14921
14925
  }
14922
- return new Uint8Array(bytes);
14926
+ return new Uint8Array(bytes2);
14923
14927
  }
14924
14928
 
14925
14929
  // ../core/src/omit.ts
@@ -14938,8 +14942,8 @@ async function sha256Hex(text) {
14938
14942
  return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
14939
14943
  }
14940
14944
  function generateSecret() {
14941
- const bytes = crypto.getRandomValues(new Uint8Array(16));
14942
- return `${SECRET_PREFIX}${base64urlEncode(bytes)}`;
14945
+ const bytes2 = crypto.getRandomValues(new Uint8Array(16));
14946
+ return `${SECRET_PREFIX}${base64urlEncode(bytes2)}`;
14943
14947
  }
14944
14948
  function normalizeExpiresAt(value) {
14945
14949
  const parsed = ISO_TIMESTAMP.safeParse(value);
@@ -14996,10 +15000,10 @@ function clampLimit(limit) {
14996
15000
  return Math.min(Math.max(1, limit), LIST_LIMIT_MAX);
14997
15001
  }
14998
15002
  var SKRegistryStore = class {
15003
+ store;
14999
15004
  constructor(store) {
15000
15005
  this.store = store;
15001
15006
  }
15002
- store;
15003
15007
  async recordById(id) {
15004
15008
  const hash2 = await this.store.get(KEY_SK_ID + id);
15005
15009
  if (typeof hash2 !== "string") return null;
@@ -15111,6 +15115,7 @@ var OperationRegistry = class {
15111
15115
  const schema = spec.inputSchema !== void 0 ? normalizeSchema(spec.inputSchema) : void 0;
15112
15116
  const toolSpec = { name };
15113
15117
  if (spec.description !== void 0) toolSpec.description = spec.description;
15118
+ if (spec.delivery !== void 0) toolSpec.delivery = spec.delivery;
15114
15119
  if (spec.effect !== void 0) toolSpec.effect = spec.effect;
15115
15120
  if (spec.confirm !== void 0) toolSpec.confirm = spec.confirm;
15116
15121
  if (schema !== void 0) toolSpec.inputSchema = toJsonSchema(schema);
@@ -15589,7 +15594,8 @@ var contextCommands = new HtbpCommandRegistry().register(
15589
15594
  opts: HTBP_LIST_OPTIONS_SCHEMA.optional()
15590
15595
  }),
15591
15596
  returns: "Page<ContextEntryMeta>",
15592
- scope: "read"
15597
+ scope: "read",
15598
+ effect: "read"
15593
15599
  },
15594
15600
  ({ opts, path }, { provider }) => provider.list(path ?? "", opts)
15595
15601
  ).register(
@@ -15600,7 +15606,8 @@ var contextCommands = new HtbpCommandRegistry().register(
15600
15606
  path: external_exports.string().describe("entry path inside the namespace")
15601
15607
  }),
15602
15608
  returns: "ContextEntry",
15603
- scope: "read"
15609
+ scope: "read",
15610
+ effect: "read"
15604
15611
  },
15605
15612
  ({ path }, { provider }) => provider.get(path)
15606
15613
  ).register(
@@ -15612,7 +15619,8 @@ var contextCommands = new HtbpCommandRegistry().register(
15612
15619
  entry: entrySchema
15613
15620
  }),
15614
15621
  returns: "ContextEntryMeta",
15615
- scope: "write"
15622
+ scope: "write",
15623
+ effect: "write"
15616
15624
  },
15617
15625
  ({ entry, path }, { provider }) => provider.write(path, entry)
15618
15626
  ).register(
@@ -15624,7 +15632,8 @@ var contextCommands = new HtbpCommandRegistry().register(
15624
15632
  patch: patchSchema
15625
15633
  }),
15626
15634
  returns: "ContextEntryMeta",
15627
- scope: "write"
15635
+ scope: "write",
15636
+ effect: "write"
15628
15637
  },
15629
15638
  ({ patch, path }, { provider }) => provider.update(path, patch)
15630
15639
  ).register(
@@ -15647,7 +15656,8 @@ var contextCommands = new HtbpCommandRegistry().register(
15647
15656
  opts: searchOptionsSchema.optional()
15648
15657
  }),
15649
15658
  returns: "Page<ContextEntryMeta>",
15650
- scope: "read"
15659
+ scope: "read",
15660
+ effect: "read"
15651
15661
  },
15652
15662
  ({ opts, query }, { provider }) => provider.search(query, opts)
15653
15663
  ).register(
@@ -16608,6 +16618,661 @@ function createFederationModule(deps) {
16608
16618
  return COMMANDS5.module(deps);
16609
16619
  }
16610
16620
 
16621
+ // ../core/src/device/mailbox.ts
16622
+ var KEY_DEVICE_OPERATION = "deviceop:";
16623
+ var DEVICE_OPERATION_STATES = [
16624
+ "queued",
16625
+ "claimed",
16626
+ "succeeded",
16627
+ "rejected",
16628
+ "failed",
16629
+ "result_unknown",
16630
+ "cancelled",
16631
+ "expired"
16632
+ ];
16633
+ var DEFAULT_TTL_SECONDS = 24 * 60 * 60;
16634
+ var MAX_TTL_SECONDS = 7 * 24 * 60 * 60;
16635
+ var DEFAULT_LEASE_SECONDS = 60;
16636
+ var DEFAULT_MAX_PAYLOAD_BYTES = 256 * 1024;
16637
+ var DEFAULT_MAX_PENDING_PER_DEVICE = 1e3;
16638
+ var DEFAULT_TERMINAL_RETENTION_SECONDS = 7 * 24 * 60 * 60;
16639
+ var MAX_CAS_ATTEMPTS = 8;
16640
+ var LIST_LIMIT_DEFAULT2 = 50;
16641
+ var LIST_LIMIT_MAX2 = 200;
16642
+ var HKDF_SALT = new TextEncoder().encode("tool-bridge:device-mailbox:v1");
16643
+ var HKDF_INFO = new TextEncoder().encode("aes-256-gcm:operation-payload-and-result:v1");
16644
+ function mailboxUnavailable(message) {
16645
+ return new TBError("unavailable", message, { retryable: false });
16646
+ }
16647
+ function finiteTimestamp(value) {
16648
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
16649
+ }
16650
+ function nonEmpty(value) {
16651
+ return typeof value === "string" && value !== "";
16652
+ }
16653
+ function positiveInteger(value, fallback, field) {
16654
+ const actual = value ?? fallback;
16655
+ if (!Number.isSafeInteger(actual) || actual < 1) {
16656
+ throw new TBError("invalid_argument", `${field} must be a positive integer`);
16657
+ }
16658
+ return actual;
16659
+ }
16660
+ function clampLimit3(value) {
16661
+ if (value === void 0) return LIST_LIMIT_DEFAULT2;
16662
+ if (!Number.isSafeInteger(value) || value < 1) {
16663
+ throw new TBError("invalid_argument", "limit must be a positive integer");
16664
+ }
16665
+ return Math.min(value, LIST_LIMIT_MAX2);
16666
+ }
16667
+ function isState(value) {
16668
+ return typeof value === "string" && DEVICE_OPERATION_STATES.includes(value);
16669
+ }
16670
+ function isTerminal(state) {
16671
+ return state !== "queued" && state !== "claimed";
16672
+ }
16673
+ function parseEncrypted(value) {
16674
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("invalid encrypted value");
16675
+ const raw = value;
16676
+ if (raw.v !== 1 || !nonEmpty(raw.iv) || !nonEmpty(raw.ciphertext)) {
16677
+ throw new Error("invalid encrypted value");
16678
+ }
16679
+ return { v: 1, iv: raw.iv, ciphertext: raw.ciphertext };
16680
+ }
16681
+ function parseRecord(value) {
16682
+ try {
16683
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error();
16684
+ const raw = value;
16685
+ if (raw.v !== 1 || !Number.isSafeInteger(raw.revision) || raw.revision < 1 || !nonEmpty(raw.operationId) || raw.commandId !== raw.operationId || !nonEmpty(raw.deviceId) || !nonEmpty(raw.deviceKeyId) || !nonEmpty(raw.mountPath) || !nonEmpty(raw.targetPath) || !nonEmpty(raw.path) || !nonEmpty(raw.callerKeyId) || !nonEmpty(raw.callerOwner) || !nonEmpty(raw.traceId) || !finiteTimestamp(raw.createdAt) || !finiteTimestamp(raw.updatedAt) || !finiteTimestamp(raw.expiresAt) || !isState(raw.state) || !Number.isSafeInteger(raw.attempt) || raw.attempt < 0 || typeof raw.executionMayHaveOccurred !== "boolean") throw new Error();
16686
+ if (raw.leaseId === void 0 !== (raw.leaseUntil === void 0)) throw new Error();
16687
+ if (raw.leaseId !== void 0 && (!nonEmpty(raw.leaseId) || !finiteTimestamp(raw.leaseUntil))) {
16688
+ throw new Error();
16689
+ }
16690
+ if (raw.cancelRequestedAt !== void 0 && !finiteTimestamp(raw.cancelRequestedAt)) throw new Error();
16691
+ if (raw.terminalAt !== void 0 && !finiteTimestamp(raw.terminalAt)) throw new Error();
16692
+ if (isTerminal(raw.state) && raw.terminalAt === void 0) throw new Error();
16693
+ return {
16694
+ v: 1,
16695
+ revision: raw.revision,
16696
+ operationId: raw.operationId,
16697
+ commandId: raw.commandId,
16698
+ deviceId: raw.deviceId,
16699
+ deviceKeyId: raw.deviceKeyId,
16700
+ mountPath: raw.mountPath,
16701
+ targetPath: raw.targetPath,
16702
+ path: raw.path,
16703
+ callerKeyId: raw.callerKeyId,
16704
+ callerOwner: raw.callerOwner,
16705
+ traceId: raw.traceId,
16706
+ createdAt: raw.createdAt,
16707
+ updatedAt: raw.updatedAt,
16708
+ expiresAt: raw.expiresAt,
16709
+ state: raw.state,
16710
+ attempt: raw.attempt,
16711
+ executionMayHaveOccurred: raw.executionMayHaveOccurred,
16712
+ payload: parseEncrypted(raw.payload),
16713
+ ...raw.leaseId === void 0 ? {} : { leaseId: raw.leaseId, leaseUntil: raw.leaseUntil },
16714
+ ...raw.cancelRequestedAt === void 0 ? {} : { cancelRequestedAt: raw.cancelRequestedAt },
16715
+ ...raw.completedLeaseId === void 0 ? {} : { completedLeaseId: String(raw.completedLeaseId) },
16716
+ ...raw.terminalAt === void 0 ? {} : { terminalAt: raw.terminalAt },
16717
+ ...raw.terminalData === void 0 ? {} : { terminalData: parseEncrypted(raw.terminalData) },
16718
+ ...raw.idempotencyFingerprint === void 0 ? {} : { idempotencyFingerprint: String(raw.idempotencyFingerprint) }
16719
+ };
16720
+ } catch {
16721
+ throw new TBError("internal", "device mailbox record is invalid", { retryable: false });
16722
+ }
16723
+ }
16724
+ function canonicalJson(value) {
16725
+ if (value === null) return "null";
16726
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
16727
+ if (typeof value === "number") {
16728
+ if (!Number.isFinite(value)) throw new TBError("invalid_argument", "mailbox data must be valid JSON");
16729
+ return JSON.stringify(value);
16730
+ }
16731
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
16732
+ if (typeof value !== "object") {
16733
+ throw new TBError("invalid_argument", "mailbox data must be valid JSON");
16734
+ }
16735
+ const object2 = value;
16736
+ const keys = Object.keys(object2).sort();
16737
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(object2[key])}`).join(",")}}`;
16738
+ }
16739
+ function safeError(value) {
16740
+ if (typeof value !== "object" || value === null || typeof value.code !== "string" || typeof value.message !== "string" || typeof value.retryable !== "boolean") throw new TBError("invalid_argument", "completion error is invalid");
16741
+ return { code: value.code, message: value.message, retryable: value.retryable };
16742
+ }
16743
+ function normalizeCompletion(value) {
16744
+ if (value.outcome === "succeeded") {
16745
+ canonicalJson(value.result);
16746
+ return { outcome: "succeeded", result: value.result };
16747
+ }
16748
+ if (value.outcome === "rejected" || value.outcome === "failed") {
16749
+ return { outcome: value.outcome, error: safeError(value.error) };
16750
+ }
16751
+ return {
16752
+ outcome: "result_unknown",
16753
+ ...value.error === void 0 ? {} : { error: safeError(value.error) }
16754
+ };
16755
+ }
16756
+ function bytes(value) {
16757
+ return new TextEncoder().encode(value).byteLength;
16758
+ }
16759
+ function randomId(randomBytes) {
16760
+ const value = randomBytes(18);
16761
+ if (!(value instanceof Uint8Array) || value.length !== 18) {
16762
+ throw mailboxUnavailable("device mailbox randomness is unavailable");
16763
+ }
16764
+ return `dop_${base64urlEncode(value)}`;
16765
+ }
16766
+ async function digestBytes(value) {
16767
+ return new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)));
16768
+ }
16769
+ async function deterministicOperationId(binding) {
16770
+ return `dop_${base64urlEncode((await digestBytes(binding)).slice(0, 18))}`;
16771
+ }
16772
+ async function devicePrefix(deviceId) {
16773
+ return `${KEY_DEVICE_OPERATION}${await sha256Hex(deviceId)}:`;
16774
+ }
16775
+ async function stateKey(deviceId, operationId) {
16776
+ if (!/^dop_[A-Za-z0-9_-]{24}$/.test(operationId)) {
16777
+ throw new TBError("invalid_argument", "operationId is invalid");
16778
+ }
16779
+ return await devicePrefix(deviceId) + operationId;
16780
+ }
16781
+ var DeviceMailboxCipher = class {
16782
+ root;
16783
+ keyPromise;
16784
+ constructor(masterKey) {
16785
+ let decoded;
16786
+ try {
16787
+ decoded = base64urlDecode(masterKey);
16788
+ } catch {
16789
+ throw mailboxUnavailable("device mailbox encryption key is invalid");
16790
+ }
16791
+ if (decoded.length !== 32) {
16792
+ throw mailboxUnavailable("device mailbox encryption key is invalid");
16793
+ }
16794
+ this.root = decoded;
16795
+ }
16796
+ key() {
16797
+ this.keyPromise ??= (async () => {
16798
+ const rootKey = await crypto.subtle.importKey("raw", this.root, "HKDF", false, ["deriveKey"]);
16799
+ return await crypto.subtle.deriveKey(
16800
+ { name: "HKDF", hash: "SHA-256", salt: HKDF_SALT, info: HKDF_INFO },
16801
+ rootKey,
16802
+ { name: "AES-GCM", length: 256 },
16803
+ false,
16804
+ ["encrypt", "decrypt"]
16805
+ );
16806
+ })();
16807
+ return this.keyPromise;
16808
+ }
16809
+ async encrypt(operationId, field, value) {
16810
+ const plaintext = canonicalJson(value);
16811
+ const iv = crypto.getRandomValues(new Uint8Array(12));
16812
+ const additionalData = new TextEncoder().encode(`tool-bridge:device-mailbox:v1:${operationId}:${field}`);
16813
+ const ciphertext = await crypto.subtle.encrypt(
16814
+ { name: "AES-GCM", iv, additionalData },
16815
+ await this.key(),
16816
+ new TextEncoder().encode(plaintext)
16817
+ );
16818
+ return {
16819
+ v: 1,
16820
+ iv: base64urlEncode(iv),
16821
+ ciphertext: base64urlEncode(new Uint8Array(ciphertext))
16822
+ };
16823
+ }
16824
+ async decrypt(operationId, field, value) {
16825
+ try {
16826
+ const iv = base64urlDecode(value.iv);
16827
+ const ciphertext = base64urlDecode(value.ciphertext);
16828
+ if (iv.length !== 12) throw new Error("invalid iv");
16829
+ const additionalData = new TextEncoder().encode(`tool-bridge:device-mailbox:v1:${operationId}:${field}`);
16830
+ const plaintext = await crypto.subtle.decrypt(
16831
+ { name: "AES-GCM", iv, additionalData },
16832
+ await this.key(),
16833
+ ciphertext
16834
+ );
16835
+ return JSON.parse(new TextDecoder().decode(plaintext));
16836
+ } catch {
16837
+ throw new TBError("internal", "device mailbox data cannot be decrypted", { retryable: false });
16838
+ }
16839
+ }
16840
+ };
16841
+ var DeviceMailboxService = class {
16842
+ cas;
16843
+ cipher;
16844
+ defaultTtlSeconds;
16845
+ leaseSeconds;
16846
+ maxPayloadBytes;
16847
+ maxPendingPerDevice;
16848
+ maxTtlSeconds;
16849
+ now;
16850
+ randomBytes;
16851
+ state;
16852
+ terminalRetentionSeconds;
16853
+ constructor(state, encryptionRoot, opts = {}) {
16854
+ if (state.compareAndSwap === void 0) {
16855
+ throw mailboxUnavailable("device mailbox requires StateStore.compareAndSwap");
16856
+ }
16857
+ if (encryptionRoot === void 0) {
16858
+ throw mailboxUnavailable("device mailbox requires TB_SECRET_ENCRYPTION_KEY");
16859
+ }
16860
+ this.cas = state.compareAndSwap.bind(state);
16861
+ this.state = state;
16862
+ this.cipher = new DeviceMailboxCipher(encryptionRoot);
16863
+ this.defaultTtlSeconds = positiveInteger(
16864
+ opts.defaultTtlSeconds,
16865
+ DEFAULT_TTL_SECONDS,
16866
+ "defaultTtlSeconds"
16867
+ );
16868
+ this.maxTtlSeconds = positiveInteger(opts.maxTtlSeconds, MAX_TTL_SECONDS, "maxTtlSeconds");
16869
+ if (this.defaultTtlSeconds > this.maxTtlSeconds) {
16870
+ throw new TBError("invalid_argument", "defaultTtlSeconds cannot exceed maxTtlSeconds");
16871
+ }
16872
+ this.leaseSeconds = positiveInteger(opts.leaseSeconds, DEFAULT_LEASE_SECONDS, "leaseSeconds");
16873
+ this.maxPayloadBytes = positiveInteger(
16874
+ opts.maxPayloadBytes,
16875
+ DEFAULT_MAX_PAYLOAD_BYTES,
16876
+ "maxPayloadBytes"
16877
+ );
16878
+ this.maxPendingPerDevice = positiveInteger(
16879
+ opts.maxPendingPerDevice,
16880
+ DEFAULT_MAX_PENDING_PER_DEVICE,
16881
+ "maxPendingPerDevice"
16882
+ );
16883
+ this.terminalRetentionSeconds = positiveInteger(
16884
+ opts.terminalRetentionSeconds,
16885
+ DEFAULT_TERMINAL_RETENTION_SECONDS,
16886
+ "terminalRetentionSeconds"
16887
+ );
16888
+ this.now = opts.now ?? (() => Date.now());
16889
+ this.randomBytes = opts.randomBytes ?? ((length) => crypto.getRandomValues(new Uint8Array(length)));
16890
+ }
16891
+ nowIso() {
16892
+ return new Date(this.now()).toISOString();
16893
+ }
16894
+ async read(deviceId, operationId) {
16895
+ const key = await stateKey(deviceId, operationId);
16896
+ const value = await this.state.get(key);
16897
+ if (value === null) throw TBError.notFound("device operation not found");
16898
+ const record2 = parseRecord(value);
16899
+ if (record2.deviceId !== deviceId || record2.operationId !== operationId) {
16900
+ throw new TBError("internal", "device mailbox record identity mismatch", { retryable: false });
16901
+ }
16902
+ return { key, record: record2 };
16903
+ }
16904
+ summary(record2) {
16905
+ return {
16906
+ operationId: record2.operationId,
16907
+ commandId: record2.commandId,
16908
+ deviceId: record2.deviceId,
16909
+ mountPath: record2.mountPath,
16910
+ targetPath: record2.targetPath,
16911
+ caller: { keyId: record2.callerKeyId, owner: record2.callerOwner },
16912
+ traceId: record2.traceId,
16913
+ createdAt: record2.createdAt,
16914
+ updatedAt: record2.updatedAt,
16915
+ expiresAt: record2.expiresAt,
16916
+ state: record2.state,
16917
+ attempt: record2.attempt,
16918
+ executionMayHaveOccurred: record2.executionMayHaveOccurred,
16919
+ ...record2.leaseUntil === void 0 ? {} : { leaseUntil: record2.leaseUntil },
16920
+ ...record2.cancelRequestedAt === void 0 ? {} : { cancelRequestedAt: record2.cancelRequestedAt },
16921
+ ...record2.terminalAt === void 0 ? {} : { terminalAt: record2.terminalAt }
16922
+ };
16923
+ }
16924
+ async detail(record2) {
16925
+ const summary = this.summary(record2);
16926
+ if (record2.terminalData === void 0) return summary;
16927
+ const terminal = await this.cipher.decrypt(
16928
+ record2.operationId,
16929
+ "terminal",
16930
+ record2.terminalData
16931
+ );
16932
+ if (record2.state === "succeeded") return { ...summary, result: terminal.result };
16933
+ const error51 = terminal.error;
16934
+ if (error51 === void 0) return summary;
16935
+ return { ...summary, error: safeError(error51) };
16936
+ }
16937
+ async expireRecord(key, record2) {
16938
+ let current = record2;
16939
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
16940
+ if (isTerminal(current.state) || Date.parse(current.expiresAt) > this.now()) return current;
16941
+ const now = this.nowIso();
16942
+ const next = {
16943
+ ...current,
16944
+ revision: current.revision + 1,
16945
+ state: "expired",
16946
+ updatedAt: now,
16947
+ terminalAt: now,
16948
+ executionMayHaveOccurred: current.state === "claimed"
16949
+ };
16950
+ if (await this.cas(key, current.revision, next)) return next;
16951
+ const winner = await this.state.get(key);
16952
+ if (winner === null) throw TBError.notFound("device operation not found");
16953
+ current = parseRecord(winner);
16954
+ }
16955
+ throw new TBError("unavailable", "device operation expiry is busy", { retryable: true });
16956
+ }
16957
+ async enforcePendingCap(deviceId) {
16958
+ const prefix = await devicePrefix(deviceId);
16959
+ let cursor;
16960
+ let pending = 0;
16961
+ do {
16962
+ const page = await this.state.list(prefix, {
16963
+ limit: LIST_LIMIT_MAX2,
16964
+ ...cursor === void 0 ? {} : { cursor }
16965
+ });
16966
+ for (const item of page.items) {
16967
+ let record2 = parseRecord(item.value);
16968
+ if (record2.deviceId !== deviceId) continue;
16969
+ record2 = await this.expireRecord(item.key, record2);
16970
+ if (!isTerminal(record2.state)) pending++;
16971
+ if (pending >= this.maxPendingPerDevice) {
16972
+ throw new TBError("rate_limited", "device mailbox pending limit reached", {
16973
+ retryable: true
16974
+ });
16975
+ }
16976
+ }
16977
+ cursor = page.cursor;
16978
+ } while (cursor !== void 0);
16979
+ }
16980
+ async enqueue(input) {
16981
+ const ttlSeconds = input.ttlSeconds ?? this.defaultTtlSeconds;
16982
+ if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > this.maxTtlSeconds) {
16983
+ throw new TBError("invalid_argument", `ttlSeconds must be between 1 and ${this.maxTtlSeconds}`);
16984
+ }
16985
+ const payloadText = canonicalJson({ arguments: input.arguments });
16986
+ if (bytes(payloadText) > this.maxPayloadBytes) {
16987
+ throw new TBError("rate_limited", "device mailbox payload is too large", { retryable: false });
16988
+ }
16989
+ if (input.idempotencyKey !== void 0 && (input.idempotencyKey.length < 1 || input.idempotencyKey.length > 255 || /[\r\n\0]/.test(input.idempotencyKey))) throw new TBError("invalid_argument", "idempotency key is invalid");
16990
+ const binding = input.idempotencyKey === void 0 ? void 0 : canonicalJson({
16991
+ callerOwner: input.caller.owner,
16992
+ deviceId: input.deviceId,
16993
+ targetPath: input.targetPath,
16994
+ idempotencyKey: input.idempotencyKey
16995
+ });
16996
+ const operationId = binding === void 0 ? randomId(this.randomBytes) : await deterministicOperationId(binding);
16997
+ const fingerprint = binding === void 0 ? void 0 : await sha256Hex(canonicalJson({
16998
+ arguments: input.arguments,
16999
+ deviceKeyId: input.deviceKeyId,
17000
+ path: input.path,
17001
+ ttlSeconds
17002
+ }));
17003
+ const key = await stateKey(input.deviceId, operationId);
17004
+ if (binding !== void 0) {
17005
+ const existing2 = await this.state.get(key);
17006
+ if (existing2 !== null) {
17007
+ const record3 = parseRecord(existing2);
17008
+ if (record3.callerOwner !== input.caller.owner || record3.deviceId !== input.deviceId || record3.targetPath !== input.targetPath || record3.idempotencyFingerprint !== fingerprint) throw new TBError("conflict", "idempotency key is bound to another operation");
17009
+ return await this.detail(record3);
17010
+ }
17011
+ }
17012
+ await this.enforcePendingCap(input.deviceId);
17013
+ const now = this.nowIso();
17014
+ const record2 = {
17015
+ v: 1,
17016
+ revision: 1,
17017
+ operationId,
17018
+ commandId: operationId,
17019
+ deviceId: input.deviceId,
17020
+ deviceKeyId: input.deviceKeyId,
17021
+ mountPath: input.mountPath,
17022
+ targetPath: input.targetPath,
17023
+ path: input.path,
17024
+ callerKeyId: input.caller.keyId,
17025
+ callerOwner: input.caller.owner,
17026
+ traceId: input.traceId,
17027
+ createdAt: now,
17028
+ updatedAt: now,
17029
+ expiresAt: new Date(this.now() + ttlSeconds * 1e3).toISOString(),
17030
+ state: "queued",
17031
+ attempt: 0,
17032
+ executionMayHaveOccurred: false,
17033
+ payload: await this.cipher.encrypt(operationId, "payload", { arguments: input.arguments }),
17034
+ ...fingerprint === void 0 ? {} : { idempotencyFingerprint: fingerprint }
17035
+ };
17036
+ if (await this.cas(key, null, record2)) return await this.detail(record2);
17037
+ if (binding === void 0) {
17038
+ throw new TBError("conflict", "device operation id collision", { retryable: true });
17039
+ }
17040
+ const winner = await this.state.get(key);
17041
+ if (winner === null) throw new TBError("conflict", "device operation enqueue conflicted");
17042
+ const existing = parseRecord(winner);
17043
+ if (existing.idempotencyFingerprint !== fingerprint) {
17044
+ throw new TBError("conflict", "idempotency key is bound to another operation");
17045
+ }
17046
+ return await this.detail(existing);
17047
+ }
17048
+ async get(deviceId, operationId) {
17049
+ const { key, record: record2 } = await this.read(deviceId, operationId);
17050
+ return await this.detail(await this.expireRecord(key, record2));
17051
+ }
17052
+ async list(input) {
17053
+ const limit = clampLimit3(input.limit);
17054
+ const states = input.states === void 0 ? void 0 : new Set(input.states);
17055
+ if (states !== void 0 && [...states].some((state) => !isState(state))) {
17056
+ throw new TBError("invalid_argument", "states contains an invalid operation state");
17057
+ }
17058
+ const page = await this.state.list(await devicePrefix(input.deviceId), {
17059
+ limit,
17060
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor }
17061
+ });
17062
+ const items = [];
17063
+ for (const item of page.items) {
17064
+ let record2 = parseRecord(item.value);
17065
+ if (record2.deviceId !== input.deviceId) continue;
17066
+ record2 = await this.expireRecord(item.key, record2);
17067
+ if (states !== void 0 && !states.has(record2.state)) continue;
17068
+ items.push(this.summary(record2));
17069
+ }
17070
+ return { items, ...page.cursor === void 0 ? {} : { cursor: page.cursor } };
17071
+ }
17072
+ async claim(input) {
17073
+ const serverNow = this.nowIso();
17074
+ const page = await this.state.list(await devicePrefix(input.deviceId), {
17075
+ limit: clampLimit3(input.limit),
17076
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor }
17077
+ });
17078
+ for (const item of page.items) {
17079
+ let record2 = parseRecord(item.value);
17080
+ if (record2.deviceId !== input.deviceId || record2.deviceKeyId !== input.deviceKeyId) continue;
17081
+ record2 = await this.expireRecord(item.key, record2);
17082
+ const reclaimable = record2.state === "claimed" && record2.leaseUntil !== void 0 && Date.parse(record2.leaseUntil) <= this.now();
17083
+ if (record2.state !== "queued" && !reclaimable) continue;
17084
+ await input.authorize?.({
17085
+ deviceId: record2.deviceId,
17086
+ deviceKeyId: record2.deviceKeyId,
17087
+ mountPath: record2.mountPath,
17088
+ targetPath: record2.targetPath
17089
+ });
17090
+ const leaseId = randomId(this.randomBytes);
17091
+ const leaseUntil = new Date(Math.min(
17092
+ this.now() + this.leaseSeconds * 1e3,
17093
+ Date.parse(record2.expiresAt)
17094
+ )).toISOString();
17095
+ const next = {
17096
+ ...record2,
17097
+ revision: record2.revision + 1,
17098
+ state: "claimed",
17099
+ attempt: record2.attempt + 1,
17100
+ leaseId,
17101
+ leaseUntil,
17102
+ updatedAt: serverNow
17103
+ };
17104
+ if (!await this.cas(item.key, record2.revision, next)) continue;
17105
+ const payload = await this.cipher.decrypt(
17106
+ record2.operationId,
17107
+ "payload",
17108
+ record2.payload
17109
+ );
17110
+ if (typeof payload !== "object" || payload === null || typeof payload.arguments !== "object" || payload.arguments === null || Array.isArray(payload.arguments)) throw new TBError("internal", "device mailbox payload is invalid", { retryable: false });
17111
+ return {
17112
+ serverNow,
17113
+ ...page.cursor === void 0 ? {} : { cursor: page.cursor },
17114
+ operation: {
17115
+ operationId: next.operationId,
17116
+ commandId: next.commandId,
17117
+ targetPath: next.targetPath,
17118
+ path: next.path,
17119
+ arguments: payload.arguments,
17120
+ caller: { keyId: next.callerKeyId, owner: next.callerOwner },
17121
+ traceId: next.traceId,
17122
+ createdAt: next.createdAt,
17123
+ expiresAt: next.expiresAt,
17124
+ attempt: next.attempt,
17125
+ leaseId,
17126
+ leaseUntil,
17127
+ ...next.cancelRequestedAt === void 0 ? {} : { cancelRequestedAt: next.cancelRequestedAt }
17128
+ }
17129
+ };
17130
+ }
17131
+ return {
17132
+ serverNow,
17133
+ ...page.cursor === void 0 ? {} : { cursor: page.cursor }
17134
+ };
17135
+ }
17136
+ async activeLease(input) {
17137
+ const current = await this.read(input.deviceId, input.operationId);
17138
+ const record2 = await this.expireRecord(current.key, current.record);
17139
+ if (record2.deviceKeyId !== input.deviceKeyId || record2.state !== "claimed" || record2.leaseId !== input.leaseId || record2.leaseUntil === void 0 || Date.parse(record2.leaseUntil) <= this.now()) throw new TBError("conflict", "device operation lease is no longer active");
17140
+ await input.authorize?.({
17141
+ deviceId: record2.deviceId,
17142
+ deviceKeyId: record2.deviceKeyId,
17143
+ mountPath: record2.mountPath,
17144
+ targetPath: record2.targetPath
17145
+ });
17146
+ return { key: current.key, record: record2 };
17147
+ }
17148
+ async renew(input) {
17149
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
17150
+ const { key, record: record2 } = await this.activeLease(input);
17151
+ const serverNow = this.nowIso();
17152
+ const leaseUntil = new Date(Math.min(
17153
+ this.now() + this.leaseSeconds * 1e3,
17154
+ Date.parse(record2.expiresAt)
17155
+ )).toISOString();
17156
+ const next = {
17157
+ ...record2,
17158
+ revision: record2.revision + 1,
17159
+ leaseUntil,
17160
+ updatedAt: serverNow
17161
+ };
17162
+ if (await this.cas(key, record2.revision, next)) {
17163
+ return {
17164
+ serverNow,
17165
+ leaseUntil,
17166
+ ...next.cancelRequestedAt === void 0 ? {} : { cancelRequestedAt: next.cancelRequestedAt }
17167
+ };
17168
+ }
17169
+ }
17170
+ throw new TBError("unavailable", "device operation lease is busy", { retryable: true });
17171
+ }
17172
+ async complete(input, completionInput) {
17173
+ const completion = normalizeCompletion(completionInput);
17174
+ const terminalData = completion.outcome === "succeeded" ? { result: completion.result } : completion.error === void 0 ? {} : { error: completion.error };
17175
+ if (bytes(canonicalJson(terminalData)) > this.maxPayloadBytes) {
17176
+ throw new TBError("rate_limited", "device mailbox terminal payload is too large", {
17177
+ retryable: false
17178
+ });
17179
+ }
17180
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
17181
+ const current = await this.read(input.deviceId, input.operationId);
17182
+ let record2 = await this.expireRecord(current.key, current.record);
17183
+ if (record2.deviceKeyId !== input.deviceKeyId) {
17184
+ throw new TBError("conflict", "device operation credential does not match");
17185
+ }
17186
+ await input.authorize?.({
17187
+ deviceId: record2.deviceId,
17188
+ deviceKeyId: record2.deviceKeyId,
17189
+ mountPath: record2.mountPath,
17190
+ targetPath: record2.targetPath
17191
+ });
17192
+ if (isTerminal(record2.state)) {
17193
+ if (record2.completedLeaseId !== input.leaseId || record2.state !== completion.outcome || record2.terminalData === void 0) throw new TBError("conflict", "device operation is already terminal");
17194
+ const existing = await this.cipher.decrypt(
17195
+ record2.operationId,
17196
+ "terminal",
17197
+ record2.terminalData
17198
+ );
17199
+ const requested = completion.outcome === "succeeded" ? { result: completion.result } : completion.error === void 0 ? {} : { error: completion.error };
17200
+ if (canonicalJson(existing) !== canonicalJson(requested)) {
17201
+ throw new TBError("conflict", "device operation completion differs from terminal result");
17202
+ }
17203
+ return await this.detail(record2);
17204
+ }
17205
+ const active = await this.activeLease(input);
17206
+ record2 = active.record;
17207
+ const now = this.nowIso();
17208
+ const next = {
17209
+ ...record2,
17210
+ revision: record2.revision + 1,
17211
+ state: completion.outcome,
17212
+ terminalAt: now,
17213
+ terminalData: await this.cipher.encrypt(record2.operationId, "terminal", terminalData),
17214
+ completedLeaseId: input.leaseId,
17215
+ updatedAt: now
17216
+ };
17217
+ if (await this.cas(active.key, record2.revision, next)) return await this.detail(next);
17218
+ }
17219
+ throw new TBError("unavailable", "device operation completion is busy", { retryable: true });
17220
+ }
17221
+ async cancel(deviceId, operationId) {
17222
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
17223
+ const current = await this.read(deviceId, operationId);
17224
+ const record2 = await this.expireRecord(current.key, current.record);
17225
+ if (isTerminal(record2.state) || record2.cancelRequestedAt !== void 0) {
17226
+ return await this.detail(record2);
17227
+ }
17228
+ const now = this.nowIso();
17229
+ const next = record2.state === "queued" ? {
17230
+ ...record2,
17231
+ revision: record2.revision + 1,
17232
+ state: "cancelled",
17233
+ cancelRequestedAt: now,
17234
+ terminalAt: now,
17235
+ updatedAt: now
17236
+ } : {
17237
+ ...record2,
17238
+ revision: record2.revision + 1,
17239
+ cancelRequestedAt: now,
17240
+ updatedAt: now
17241
+ };
17242
+ if (await this.cas(current.key, record2.revision, next)) return await this.detail(next);
17243
+ }
17244
+ throw new TBError("unavailable", "device operation cancellation is busy", { retryable: true });
17245
+ }
17246
+ async cleanup(opts = {}) {
17247
+ const page = await this.state.list(KEY_DEVICE_OPERATION, {
17248
+ limit: clampLimit3(opts.limit),
17249
+ ...opts.cursor === void 0 ? {} : { cursor: opts.cursor }
17250
+ });
17251
+ let deleted = 0;
17252
+ let expired = 0;
17253
+ for (const item of page.items) {
17254
+ let record2;
17255
+ try {
17256
+ record2 = parseRecord(item.value);
17257
+ } catch {
17258
+ continue;
17259
+ }
17260
+ if (!isTerminal(record2.state) && Date.parse(record2.expiresAt) <= this.now()) {
17261
+ const next = await this.expireRecord(item.key, record2);
17262
+ if (next.state === "expired") expired++;
17263
+ record2 = next;
17264
+ }
17265
+ if (isTerminal(record2.state) && record2.terminalAt !== void 0 && Date.parse(record2.terminalAt) + this.terminalRetentionSeconds * 1e3 <= this.now() && await this.cas(item.key, record2.revision, null)) deleted++;
17266
+ }
17267
+ return {
17268
+ scanned: page.items.length,
17269
+ expired,
17270
+ deleted,
17271
+ ...page.cursor === void 0 ? {} : { cursor: page.cursor }
17272
+ };
17273
+ }
17274
+ };
17275
+
16611
17276
  // ../core/src/protocol/errorWire.ts
16612
17277
  var tbErrorCodeSchema = external_exports.enum(TB_ERROR_CODES);
16613
17278
  var tbErrorBodySchema = external_exports.object({
@@ -16620,7 +17285,7 @@ var tbErrorBodySchema = external_exports.object({
16620
17285
  var actionSchema = external_exports.enum(ACTIONS);
16621
17286
  var nodeKindSchema = external_exports.enum(NODE_KINDS);
16622
17287
  var nodeInputConfigSchema = external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe(
16623
- "kind-specific config, e.g. mcp: { url, auth?, authRef? } / http: { endpoint, tools } / context: { provider, bucket, \u2026 } / remote: { baseUrl, skRef }; credentials go by authRef/skRef name, never inline"
17288
+ "kind-specific config, e.g. mcp: { url, auth?, authRef?, oauthClient?: { clientId, clientSecretRef? } } / http: { endpoint, tools } / context: { provider, bucket, \u2026 } / remote: { baseUrl, skRef }; credentials go by SecretStore reference name, never inline"
16624
17289
  );
16625
17290
  var nodeInputVirtualizeSchema = external_exports.strictObject({
16626
17291
  prefix: external_exports.string().optional(),
@@ -16654,6 +17319,7 @@ var helpChildSchema = external_exports.object({
16654
17319
  });
16655
17320
  var helpCommandSchema = external_exports.object({
16656
17321
  confirm: external_exports.boolean().optional(),
17322
+ delivery: external_exports.enum(["realtime", "mailbox", "both"]).optional(),
16657
17323
  effect: external_exports.string().optional(),
16658
17324
  h: external_exports.string().optional(),
16659
17325
  inputSchema: external_exports.unknown().optional(),
@@ -16687,14 +17353,42 @@ var treeJsonSchema = external_exports.lazy(() => external_exports.object({
16687
17353
  }));
16688
17354
  var toolSpecSchema = external_exports.object({
16689
17355
  confirm: external_exports.boolean().optional(),
17356
+ delivery: external_exports.enum(["realtime", "mailbox", "both"]).optional(),
16690
17357
  description: external_exports.string().optional(),
16691
17358
  effect: external_exports.string().optional(),
16692
17359
  inputSchema: external_exports.unknown().optional(),
16693
17360
  name: external_exports.string(),
16694
17361
  outputSchema: external_exports.unknown().optional()
16695
17362
  });
16696
- var toolSearchItemSchema = external_exports.object({
17363
+ var toolSearchRelevanceSchema = external_exports.strictObject({
17364
+ coverage: external_exports.number().min(0).max(1),
17365
+ matchedTermCount: external_exports.number().int().nonnegative(),
17366
+ rankingVersion: external_exports.literal("keyword-v2"),
17367
+ totalTermCount: external_exports.number().int().positive()
17368
+ }).refine(
17369
+ (value) => value.matchedTermCount <= value.totalTermCount && value.coverage === value.matchedTermCount / value.totalTermCount,
17370
+ { message: "coverage must equal matchedTermCount / totalTermCount" }
17371
+ );
17372
+ var toolSearchSourceSchema = external_exports.strictObject({
17373
+ path: external_exports.string()
17374
+ });
17375
+ var toolSearchSourceResultSchema = external_exports.strictObject({
16697
17376
  path: external_exports.string(),
17377
+ status: external_exports.enum([
17378
+ "ok",
17379
+ "unsupported",
17380
+ "timed_out",
17381
+ "unavailable",
17382
+ "cycle",
17383
+ "hop_limit",
17384
+ "budget_exhausted",
17385
+ "invalid_response"
17386
+ ])
17387
+ });
17388
+ var toolSearchItemSchema = external_exports.strictObject({
17389
+ path: external_exports.string(),
17390
+ relevance: toolSearchRelevanceSchema,
17391
+ source: toolSearchSourceSchema.optional(),
16698
17392
  tool: toolSpecSchema
16699
17393
  });
16700
17394
  function pageSchema(itemSchema) {
@@ -16703,19 +17397,136 @@ function pageSchema(itemSchema) {
16703
17397
  items: external_exports.array(itemSchema)
16704
17398
  });
16705
17399
  }
16706
- var toolSearchPageSchema = pageSchema(toolSearchItemSchema);
17400
+ var toolSearchPageSchema = external_exports.strictObject({
17401
+ cursor: external_exports.string().optional(),
17402
+ items: external_exports.array(toolSearchItemSchema),
17403
+ partial: external_exports.boolean().optional(),
17404
+ snapshot: external_exports.string().optional(),
17405
+ sources: external_exports.array(toolSearchSourceResultSchema).optional()
17406
+ });
16707
17407
  var toolSearchRequestSchema = external_exports.strictObject({
16708
17408
  opts: external_exports.strictObject({
16709
17409
  cursor: external_exports.string().optional(),
17410
+ detail: external_exports.enum(["compact", "full"]).optional(),
17411
+ effects: external_exports.array(external_exports.enum(["read", "write", "destructive", "unknown"])).min(1).optional(),
17412
+ federation: external_exports.enum(["local", "recursive"]).optional(),
16710
17413
  limit: external_exports.number().int().optional(),
16711
- mode: external_exports.enum(["keyword", "semantic"]).optional()
17414
+ matching: external_exports.enum(["best", "all"]).optional(),
17415
+ minCoverage: external_exports.number().gt(0).max(1).optional(),
17416
+ mode: external_exports.enum(["keyword", "semantic"]).optional(),
17417
+ pathPrefix: external_exports.string().optional()
16712
17418
  }).optional(),
16713
17419
  query: external_exports.string().trim().min(1)
16714
17420
  });
17421
+ var deviceOperationStateSchema = external_exports.enum(
17422
+ DEVICE_OPERATION_STATES
17423
+ );
17424
+ var deviceOperationCallerSchema = external_exports.object({
17425
+ keyId: external_exports.string().min(1),
17426
+ owner: external_exports.string().min(1)
17427
+ });
17428
+ var deviceOperationSummaryShape = {
17429
+ attempt: external_exports.number().int().nonnegative(),
17430
+ caller: deviceOperationCallerSchema,
17431
+ cancelRequestedAt: external_exports.iso.datetime({ offset: true }).optional(),
17432
+ commandId: external_exports.string().min(1),
17433
+ createdAt: external_exports.iso.datetime({ offset: true }),
17434
+ deviceId: external_exports.string().min(1),
17435
+ executionMayHaveOccurred: external_exports.boolean(),
17436
+ expiresAt: external_exports.iso.datetime({ offset: true }),
17437
+ leaseUntil: external_exports.iso.datetime({ offset: true }).optional(),
17438
+ mountPath: external_exports.string().min(1),
17439
+ operationId: external_exports.string().min(1),
17440
+ state: deviceOperationStateSchema,
17441
+ targetPath: external_exports.string().min(1),
17442
+ terminalAt: external_exports.iso.datetime({ offset: true }).optional(),
17443
+ traceId: external_exports.string().min(1),
17444
+ updatedAt: external_exports.iso.datetime({ offset: true })
17445
+ };
17446
+ var deviceOperationSummarySchema = external_exports.object(
17447
+ deviceOperationSummaryShape
17448
+ );
17449
+ var deviceOperationDetailSchema = external_exports.object({
17450
+ ...deviceOperationSummaryShape,
17451
+ error: tbErrorBodySchema.optional(),
17452
+ result: external_exports.json().optional()
17453
+ });
17454
+ var deviceOperationListRequestSchema = external_exports.strictObject({
17455
+ deviceId: external_exports.string().min(1),
17456
+ opts: external_exports.strictObject({
17457
+ cursor: external_exports.string().min(1).optional(),
17458
+ limit: external_exports.number().int().positive().optional(),
17459
+ states: external_exports.array(deviceOperationStateSchema).min(1).optional()
17460
+ }).optional()
17461
+ });
17462
+ var deviceOperationListResponseSchema = pageSchema(deviceOperationSummarySchema);
17463
+ var deviceOperationIdentityRequestSchema = external_exports.strictObject({
17464
+ deviceId: external_exports.string().min(1),
17465
+ operationId: external_exports.string().min(1)
17466
+ });
17467
+ var deviceOperationClaimRequestSchema = external_exports.strictObject({
17468
+ cursor: external_exports.string().min(1).optional(),
17469
+ deviceId: external_exports.string().min(1),
17470
+ limit: external_exports.number().int().positive().optional()
17471
+ });
17472
+ var deviceOperationClaimSchema = external_exports.object({
17473
+ arguments: external_exports.record(external_exports.string(), external_exports.json()),
17474
+ attempt: external_exports.number().int().positive(),
17475
+ caller: deviceOperationCallerSchema,
17476
+ cancelRequestedAt: external_exports.iso.datetime({ offset: true }).optional(),
17477
+ commandId: external_exports.string().min(1),
17478
+ createdAt: external_exports.iso.datetime({ offset: true }),
17479
+ expiresAt: external_exports.iso.datetime({ offset: true }),
17480
+ leaseId: external_exports.string().min(1),
17481
+ leaseUntil: external_exports.iso.datetime({ offset: true }),
17482
+ operationId: external_exports.string().min(1),
17483
+ path: external_exports.string().min(1),
17484
+ targetPath: external_exports.string().min(1),
17485
+ traceId: external_exports.string().min(1)
17486
+ });
17487
+ var deviceOperationClaimResponseSchema = external_exports.object({
17488
+ cursor: external_exports.string().optional(),
17489
+ operation: deviceOperationClaimSchema.optional(),
17490
+ serverNow: external_exports.iso.datetime({ offset: true })
17491
+ });
17492
+ var deviceOperationLeaseShape = {
17493
+ deviceId: external_exports.string().min(1),
17494
+ leaseId: external_exports.string().min(1),
17495
+ operationId: external_exports.string().min(1)
17496
+ };
17497
+ var deviceOperationRenewRequestSchema = external_exports.strictObject(deviceOperationLeaseShape);
17498
+ var deviceOperationRenewResponseSchema = external_exports.object({
17499
+ cancelRequestedAt: external_exports.iso.datetime({ offset: true }).optional(),
17500
+ leaseUntil: external_exports.iso.datetime({ offset: true }),
17501
+ serverNow: external_exports.iso.datetime({ offset: true })
17502
+ });
17503
+ var deviceOperationCompleteRequestSchema = external_exports.discriminatedUnion("outcome", [
17504
+ external_exports.strictObject({
17505
+ ...deviceOperationLeaseShape,
17506
+ outcome: external_exports.literal("succeeded"),
17507
+ result: external_exports.json()
17508
+ }),
17509
+ external_exports.strictObject({
17510
+ ...deviceOperationLeaseShape,
17511
+ error: tbErrorBodySchema,
17512
+ outcome: external_exports.literal("rejected")
17513
+ }),
17514
+ external_exports.strictObject({
17515
+ ...deviceOperationLeaseShape,
17516
+ error: tbErrorBodySchema,
17517
+ outcome: external_exports.literal("failed")
17518
+ }),
17519
+ external_exports.strictObject({
17520
+ ...deviceOperationLeaseShape,
17521
+ error: tbErrorBodySchema.optional(),
17522
+ outcome: external_exports.literal("result_unknown")
17523
+ })
17524
+ ]);
16715
17525
  var registryNodeSchema = external_exports.object({
16716
17526
  config: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
16717
17527
  createdAt: external_exports.string().optional(),
16718
17528
  description: external_exports.string(),
17529
+ deviceId: external_exports.string().optional(),
16719
17530
  kind: nodeKindSchema,
16720
17531
  lastSeenAt: external_exports.string().optional(),
16721
17532
  online: external_exports.boolean().optional(),
@@ -17164,7 +17975,7 @@ async function objectBodyToBytes(body) {
17164
17975
  if (body instanceof ArrayBuffer) return new Uint8Array(body);
17165
17976
  return readStreamBytes(body);
17166
17977
  }
17167
- function bytesToObjectStream(bytes) {
17978
+ function bytesToObjectStream(bytes2) {
17168
17979
  return {
17169
17980
  getReader() {
17170
17981
  let done = false;
@@ -17172,7 +17983,7 @@ function bytesToObjectStream(bytes) {
17172
17983
  async read() {
17173
17984
  if (done) return { done: true };
17174
17985
  done = true;
17175
- return { done: false, value: bytes };
17986
+ return { done: false, value: bytes2 };
17176
17987
  },
17177
17988
  releaseLock() {
17178
17989
  },
@@ -17297,7 +18108,7 @@ async function createObjectContextUploadGrant(store, opts, input) {
17297
18108
  expiresAt
17298
18109
  };
17299
18110
  }
17300
- function clampLimit3(limit) {
18111
+ function clampLimit4(limit) {
17301
18112
  if (limit === void 0) return LIST_LIMIT_DEFAULT;
17302
18113
  if (!Number.isInteger(limit) || limit < 1) {
17303
18114
  throw new TBError("invalid_argument", `limit \u975E\u6CD5:${limit}`);
@@ -17371,7 +18182,7 @@ function createObjectContextProvider(store, opts) {
17371
18182
  return {
17372
18183
  async list(path, listOpts) {
17373
18184
  rejectFilter(listOpts);
17374
- const limit = clampLimit3(listOpts?.limit);
18185
+ const limit = clampLimit4(listOpts?.limit);
17375
18186
  const rel = path === "" ? "" : `${normalizeEntryPath(path)}/`;
17376
18187
  const full = keyPrefix + rel;
17377
18188
  assertContextKeyAllowed(full.slice(0, -1), path, false);
@@ -17481,7 +18292,7 @@ function createObjectContextProvider(store, opts) {
17481
18292
  throw new TBError("invalid_argument", "query \u4E0D\u80FD\u4E3A\u7A7A");
17482
18293
  }
17483
18294
  rejectFilter(searchOpts);
17484
- const limit = clampLimit3(searchOpts?.limit);
18295
+ const limit = clampLimit4(searchOpts?.limit);
17485
18296
  const q = query.toLowerCase();
17486
18297
  const after = searchOpts?.cursor;
17487
18298
  const items = [];
@@ -17538,6 +18349,7 @@ function isContextExpired(createdAt, ttlSec, now) {
17538
18349
  // ../core/src/device/frames.ts
17539
18350
  var deviceNodeCmdSchema = external_exports.object({
17540
18351
  name: external_exports.string().min(1),
18352
+ delivery: external_exports.enum(["realtime", "mailbox", "both"]).optional(),
17541
18353
  description: external_exports.string().optional(),
17542
18354
  inputSchema: external_exports.unknown().optional(),
17543
18355
  outputSchema: external_exports.unknown().optional(),
@@ -17669,7 +18481,7 @@ function deviceShellHelpModel(nodePath, shell = {}) {
17669
18481
  timeoutMs: { type: "number", description: "kill the command after this many ms" }
17670
18482
  }
17671
18483
  },
17672
- returns: "{ stdout, stderr, exitCode }",
18484
+ returns: "{ stdout, stderr, exitCode, startedAt, completedAt, outcome, signal?, stdoutTruncated, stderrTruncated }",
17673
18485
  scope: "call",
17674
18486
  effect: "destructive",
17675
18487
  confirm: true
@@ -17804,7 +18616,7 @@ var DeviceGatewaySession = class {
17804
18616
  call(req, done) {
17805
18617
  const cached2 = this.results.get(req.id);
17806
18618
  if (cached2 !== void 0) {
17807
- done(cached2);
18619
+ done(cached2, "completed");
17808
18620
  return;
17809
18621
  }
17810
18622
  const inflight = this.pending.get(req.id);
@@ -17813,19 +18625,28 @@ var DeviceGatewaySession = class {
17813
18625
  return;
17814
18626
  }
17815
18627
  if (this.phase_ !== "ready") {
17816
- done({ ok: false, error: TBError.deviceOffline().toJSON() });
18628
+ done({ ok: false, error: TBError.deviceOffline().toJSON() }, "not_dispatched");
17817
18629
  return;
17818
18630
  }
17819
18631
  const cancelTimer = this.opts.setTimer(() => this.onCallTimeout(req.id), this.timeoutMs);
17820
18632
  this.pending.set(req.id, { waiters: [done], cancelTimer });
17821
- this.io.send({
17822
- type: "call",
17823
- id: req.id,
17824
- path: req.path,
17825
- arguments: req.arguments,
17826
- // undefined 经 JSON.stringify 丢弃:老网关不带 context 时帧形状不变。
17827
- ...req.context !== void 0 ? { context: req.context } : {}
17828
- });
18633
+ try {
18634
+ this.io.send({
18635
+ type: "call",
18636
+ id: req.id,
18637
+ path: req.path,
18638
+ arguments: req.arguments,
18639
+ // undefined JSON.stringify 丢弃:老网关不带 context 时帧形状不变。
18640
+ ...req.context !== void 0 ? { context: req.context } : {}
18641
+ });
18642
+ } catch {
18643
+ this.pending.delete(req.id);
18644
+ cancelTimer();
18645
+ done({
18646
+ ok: false,
18647
+ error: new TBError("unavailable", "\u8BBE\u5907\u8C03\u7528\u53D1\u9001\u5931\u8D25", { retryable: true }).toJSON()
18648
+ }, "unknown");
18649
+ }
17829
18650
  }
17830
18651
  /** 幂等表回放种子(DO 从 ctx.storage 恢复时用);已有同 id 结果则忽略。 */
17831
18652
  seedResult(id, result) {
@@ -17845,7 +18666,7 @@ var DeviceGatewaySession = class {
17845
18666
  };
17846
18667
  for (const entry of entries) {
17847
18668
  entry.cancelTimer();
17848
- for (const waiter of entry.waiters) waiter(result);
18669
+ for (const waiter of entry.waiters) waiter(result, "unknown");
17849
18670
  }
17850
18671
  }
17851
18672
  protocolReject(message) {
@@ -17859,7 +18680,7 @@ var DeviceGatewaySession = class {
17859
18680
  if (entry === void 0) return;
17860
18681
  this.pending.delete(id);
17861
18682
  entry.cancelTimer();
17862
- for (const waiter of entry.waiters) waiter(result);
18683
+ for (const waiter of entry.waiters) waiter(result, "completed");
17863
18684
  }
17864
18685
  onCallTimeout(id) {
17865
18686
  const entry = this.pending.get(id);
@@ -17872,7 +18693,7 @@ var DeviceGatewaySession = class {
17872
18693
  retryable: true
17873
18694
  }).toJSON()
17874
18695
  };
17875
- for (const waiter of entry.waiters) waiter(result);
18696
+ for (const waiter of entry.waiters) waiter(result, "unknown");
17876
18697
  }
17877
18698
  };
17878
18699
 
@@ -17946,10 +18767,10 @@ function toView(entry) {
17946
18767
  }
17947
18768
  var ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
17948
18769
  function randomFeedbackId() {
17949
- const bytes = new Uint8Array(6);
17950
- crypto.getRandomValues(bytes);
18770
+ const bytes2 = new Uint8Array(6);
18771
+ crypto.getRandomValues(bytes2);
17951
18772
  let out = "fb_";
17952
- for (const b of bytes) out += ID_ALPHABET[b % ID_ALPHABET.length];
18773
+ for (const b of bytes2) out += ID_ALPHABET[b % ID_ALPHABET.length];
17953
18774
  return out;
17954
18775
  }
17955
18776
  function requireShort(value, field, max) {
@@ -18094,6 +18915,7 @@ function renderHelpDsl(model) {
18094
18915
  if (cmd.outputSchema !== void 0) lines.push(` result ${JSON.stringify(cmd.outputSchema)}`);
18095
18916
  if (cmd.returns !== void 0) lines.push(...attrLines("returns", cmd.returns));
18096
18917
  lines.push(` scope ${cmd.scope}`);
18918
+ if (cmd.delivery !== void 0) lines.push(` delivery ${cmd.delivery}`);
18097
18919
  if (cmd.effect !== void 0) lines.push(` effect ${cmd.effect}`);
18098
18920
  if (cmd.confirm) lines.push(" confirm");
18099
18921
  }
@@ -18126,6 +18948,7 @@ function renderHelpJson(model) {
18126
18948
  if (cmd.inputSchema !== void 0) out.inputSchema = cmd.inputSchema;
18127
18949
  if (cmd.outputSchema !== void 0) out.outputSchema = cmd.outputSchema;
18128
18950
  if (cmd.returns !== void 0) out.returns = cmd.returns;
18951
+ if (cmd.delivery !== void 0) out.delivery = cmd.delivery;
18129
18952
  if (cmd.effect !== void 0) out.effect = cmd.effect;
18130
18953
  if (cmd.confirm) out.confirm = cmd.confirm;
18131
18954
  return out;
@@ -18165,6 +18988,7 @@ function cmdSection(cmd, index) {
18165
18988
  }
18166
18989
  lines.push(`- Invoke: \`POST ${cmd.path}\` with body \`{...arguments}\``);
18167
18990
  lines.push(`- Required scope: \`${cmd.scope}\``);
18991
+ if (cmd.delivery !== void 0) lines.push(`- Delivery: \`${cmd.delivery}\``);
18168
18992
  if (cmd.effect !== void 0) {
18169
18993
  lines.push(
18170
18994
  `- Effect: \`${cmd.effect}\`${cmd.confirm ? " \u2014 **ask the user to confirm before calling**" : ""}`
@@ -18382,7 +19206,7 @@ var KEY_STORE_IDEMPOTENCY = "store:idempotency:";
18382
19206
  var UPLOAD_TTL_SEC_DEFAULT = 15 * 60;
18383
19207
  var SHARE_TTL_SEC_DEFAULT = 15 * 60;
18384
19208
  var MAX_OBJECT_BYTES_DEFAULT = 256 * 1024 * 1024;
18385
- var MAX_CAS_ATTEMPTS = 32;
19209
+ var MAX_CAS_ATTEMPTS2 = 32;
18386
19210
  var CAPABILITY_TTL_SEC_MAX = 7 * 24 * 60 * 60;
18387
19211
  var TOKEN_PREFIX = {
18388
19212
  call: "tbc",
@@ -18732,7 +19556,7 @@ var StoreService = class {
18732
19556
  }
18733
19557
  async revokeCallUploadCapability(token) {
18734
19558
  const id = this.tokenId(token, "call");
18735
- for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
19559
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS2; attempt++) {
18736
19560
  const raw = await this.state.get(`${KEY_STORE_CALL_CAPABILITY}${id}`);
18737
19561
  if (raw === null) throw new TBError("permission_denied", "call upload capability \u65E0\u6548");
18738
19562
  const capability = parseCallCapability(raw);
@@ -18946,7 +19770,7 @@ var StoreService = class {
18946
19770
  }
18947
19771
  async delete(uri, actor) {
18948
19772
  const { objectId } = parseStoreUri(uri);
18949
- for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
19773
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS2; attempt++) {
18950
19774
  const object2 = await this.requireObject(objectId);
18951
19775
  if (!actor.admin && object2.owner !== actor.owner) {
18952
19776
  throw new TBError("not_found", "Store \u5BF9\u8C61\u4E0D\u5B58\u5728");
@@ -19010,7 +19834,7 @@ var StoreService = class {
19010
19834
  }
19011
19835
  async revokeShare(shareId, actor) {
19012
19836
  const owner = requireOwner(actor);
19013
- for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
19837
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS2; attempt++) {
19014
19838
  const raw = await this.state.get(`${KEY_STORE_SHARE}${shareId}`);
19015
19839
  if (raw === null) throw new TBError("not_found", "share grant \u4E0D\u5B58\u5728");
19016
19840
  const grant = parseShare(raw);
@@ -19334,7 +20158,7 @@ var StoreService = class {
19334
20158
  await this.compareRecord(resolved.key, resolved.binding, null);
19335
20159
  }
19336
20160
  async reserveCallCapability(token, objectId, input, transportMaxBytes) {
19337
- for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
20161
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS2; attempt++) {
19338
20162
  const capability = await this.verifyCallUploadCapability(token);
19339
20163
  const previous = capability.reservations.find((r) => r.objectId === objectId);
19340
20164
  if (previous !== void 0) {
@@ -19456,7 +20280,7 @@ var StoreService = class {
19456
20280
  }
19457
20281
  /** driver DELETE 成功后持久化证据;后续 cleanup 只收 tombstone,不再重复外部 DELETE。 */
19458
20282
  async markBytesDeleted(objectId, expectedDriverKey) {
19459
- for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
20283
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS2; attempt++) {
19460
20284
  const raw = await this.state.get(`${KEY_STORE_OBJECT}${objectId}`);
19461
20285
  if (raw === null) return;
19462
20286
  const object2 = parseObject(raw);
@@ -19556,7 +20380,7 @@ var StoreService = class {
19556
20380
  }
19557
20381
  async markReady(sessionInput, meta3) {
19558
20382
  const actualChecksum = sessionInput.expectedChecksum !== void 0 && meta3.metadata?.["checksum-sha256"]?.toLowerCase() === sessionInput.expectedChecksum.value ? sessionInput.expectedChecksum : void 0;
19559
- for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
20383
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS2; attempt++) {
19560
20384
  const object2 = await this.requireObject(sessionInput.objectId);
19561
20385
  if (object2.status === "ready") {
19562
20386
  await this.completeSessionBestEffort(await this.requireSession(sessionInput.id));
@@ -19586,7 +20410,7 @@ var StoreService = class {
19586
20410
  throw new TBError("conflict", "\u5BF9\u8C61\u5E76\u53D1\u72B6\u6001\u51B2\u7A81:pending");
19587
20411
  }
19588
20412
  async completeSessionBestEffort(sessionInput) {
19589
- for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
20413
+ for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS2; attempt++) {
19590
20414
  const session = await this.requireSession(sessionInput.id);
19591
20415
  if (session.status === "completed" || session.status !== "created") return;
19592
20416
  const completedAt = this.nowIso();
@@ -19762,8 +20586,8 @@ function canonicalCatalogJson(value) {
19762
20586
  return JSON.stringify(normalize(value));
19763
20587
  }
19764
20588
  async function catalogDigest(value) {
19765
- const bytes = new TextEncoder().encode(canonicalCatalogJson(value));
19766
- const hash2 = await crypto.subtle.digest("SHA-256", bytes);
20589
+ const bytes2 = new TextEncoder().encode(canonicalCatalogJson(value));
20590
+ const hash2 = await crypto.subtle.digest("SHA-256", bytes2);
19767
20591
  return [...new Uint8Array(hash2)].map((b) => b.toString(16).padStart(2, "0")).join("");
19768
20592
  }
19769
20593
  async function catalogSetDigest(catalog) {
@@ -19957,11 +20781,11 @@ var pluginCallSchema = external_exports.object({
19957
20781
  arguments: external_exports.record(external_exports.string(), external_exports.unknown())
19958
20782
  });
19959
20783
  function assertPluginPayloadSize(payload) {
19960
- const bytes = new TextEncoder().encode(payload).length;
19961
- if (bytes > PLUGIN_PAYLOAD_MAX_BYTES) {
20784
+ const bytes2 = new TextEncoder().encode(payload).length;
20785
+ if (bytes2 > PLUGIN_PAYLOAD_MAX_BYTES) {
19962
20786
  throw new TBError(
19963
20787
  "invalid_argument",
19964
- `payload ${bytes} \u5B57\u8282\u8D85\u8FC7\u4E0A\u9650 ${PLUGIN_PAYLOAD_MAX_BYTES}(\u2264 1 MiB;\u66F4\u5927\u5185\u5BB9\u7ECF $ref)`
20788
+ `payload ${bytes2} \u5B57\u8282\u8D85\u8FC7\u4E0A\u9650 ${PLUGIN_PAYLOAD_MAX_BYTES}(\u2264 1 MiB;\u66F4\u5927\u5185\u5BB9\u7ECF $ref)`
19965
20789
  );
19966
20790
  }
19967
20791
  }
@@ -20000,6 +20824,13 @@ var positiveEnvKeys = [
20000
20824
  "TB_MAX_HOPS",
20001
20825
  "TB_REF_THRESHOLD_BYTES",
20002
20826
  "TB_REF_TTL_SEC",
20827
+ "TB_SEARCH_FEDERATION_CONCURRENCY",
20828
+ "TB_SEARCH_FEDERATION_DEADLINE_MS",
20829
+ "TB_SEARCH_FEDERATION_MAX_RESPONSE_BYTES",
20830
+ "TB_SEARCH_FEDERATION_MAX_SOURCES",
20831
+ "TB_SEARCH_FEDERATION_MIN_CHILD_WORK_MS",
20832
+ "TB_SEARCH_FEDERATION_RETURN_RESERVE_MS",
20833
+ "TB_SEARCH_FEDERATION_SESSION_TTL_SEC",
20003
20834
  "TB_STORE_CALL_MAX_BYTES",
20004
20835
  "TB_STORE_CALL_MAX_OBJECT_BYTES",
20005
20836
  "TB_STORE_CALL_MAX_OBJECTS",
@@ -20047,6 +20878,15 @@ function parseRuntimeEnv(env) {
20047
20878
  remote: {
20048
20879
  allowInsecure: allowInsecureHttp,
20049
20880
  allowlist,
20881
+ federatedSearch: {
20882
+ ...parsed.TB_SEARCH_FEDERATION_CONCURRENCY === void 0 ? {} : { maxConcurrency: parsed.TB_SEARCH_FEDERATION_CONCURRENCY },
20883
+ ...parsed.TB_SEARCH_FEDERATION_DEADLINE_MS === void 0 ? {} : { totalDeadlineMs: parsed.TB_SEARCH_FEDERATION_DEADLINE_MS },
20884
+ ...parsed.TB_SEARCH_FEDERATION_MAX_RESPONSE_BYTES === void 0 ? {} : { maxResponseBodyBytes: parsed.TB_SEARCH_FEDERATION_MAX_RESPONSE_BYTES },
20885
+ ...parsed.TB_SEARCH_FEDERATION_MAX_SOURCES === void 0 ? {} : { maxSources: parsed.TB_SEARCH_FEDERATION_MAX_SOURCES },
20886
+ ...parsed.TB_SEARCH_FEDERATION_MIN_CHILD_WORK_MS === void 0 ? {} : { minChildWorkMs: parsed.TB_SEARCH_FEDERATION_MIN_CHILD_WORK_MS },
20887
+ ...parsed.TB_SEARCH_FEDERATION_RETURN_RESERVE_MS === void 0 ? {} : { perHopReturnReserveMs: parsed.TB_SEARCH_FEDERATION_RETURN_RESERVE_MS },
20888
+ ...parsed.TB_SEARCH_FEDERATION_SESSION_TTL_SEC === void 0 ? {} : { sessionTtlMs: parsed.TB_SEARCH_FEDERATION_SESSION_TTL_SEC * 1e3 }
20889
+ },
20050
20890
  maxHops: parsed.TB_MAX_HOPS ?? DEFAULT_MAX_HOPS,
20051
20891
  ...parsed.TB_INSTANCE_ID ? { instanceId: parsed.TB_INSTANCE_ID } : {}
20052
20892
  }
@@ -20095,26 +20935,37 @@ var TOOL_SEARCH_SNAPSHOT_CHUNKS_MAX = Math.ceil(
20095
20935
  var TOOL_SEARCH_REBUILD_CHUNKS_MAX = TOOL_SEARCH_SOURCE_CHUNKS_MAX + TOOL_SEARCH_SNAPSHOT_CHUNKS_MAX;
20096
20936
  var TOOL_SEARCH_TERM_LIMIT = 32;
20097
20937
  var TOOL_SEARCH_UNIT_LIMIT = 98;
20938
+ var TOOL_SEARCH_LIMIT_DEFAULT = 10;
20098
20939
  var TOOL_SEARCH_LIKE_PATTERN_BYTES_MAX = 50;
20099
20940
  var TOOL_SEARCH_QUERY_MAX = 1024;
20941
+ var TOOL_SEARCH_RANKING_VERSION = "keyword-v2";
20100
20942
  var TOOL_SEARCH_CURSOR_OFFSET_MAX = 1e6;
20943
+ var TOOL_SEARCH_EFFECTS = [
20944
+ "read",
20945
+ "write",
20946
+ "destructive",
20947
+ "unknown"
20948
+ ];
20101
20949
  function assertKeywordToolSearchMode(opts) {
20102
20950
  if (opts?.mode !== void 0 && opts.mode !== "keyword") {
20103
20951
  throw new TBError("invalid_argument", `SearchIndex \u4E0D\u652F\u6301 mode '${String(opts.mode)}'`);
20104
20952
  }
20105
20953
  }
20106
20954
  function normalizeToolSearchLimit(limit) {
20107
- if (limit === void 0) return LIST_LIMIT_DEFAULT;
20955
+ if (limit === void 0) return TOOL_SEARCH_LIMIT_DEFAULT;
20108
20956
  if (typeof limit !== "number" || !Number.isFinite(limit) || !Number.isInteger(limit)) {
20109
20957
  throw new TBError("invalid_argument", "opts.limit \u5FC5\u987B\u662F\u6574\u6570");
20110
20958
  }
20111
- if (limit < 1) return LIST_LIMIT_DEFAULT;
20959
+ if (limit < 1) return TOOL_SEARCH_LIMIT_DEFAULT;
20112
20960
  return Math.min(limit, LIST_LIMIT_MAX);
20113
20961
  }
20962
+ function searchUnitAllowsPath(unit) {
20963
+ return !/^%[A-Za-z0-9]{1,2}%$/u.test(unit.pattern);
20964
+ }
20114
20965
  function stableDigest(value) {
20115
- const bytes = new TextEncoder().encode(JSON.stringify(value));
20966
+ const bytes2 = new TextEncoder().encode(JSON.stringify(value));
20116
20967
  let hash2 = 0xcbf29ce484222325n;
20117
- for (const byte of bytes) {
20968
+ for (const byte of bytes2) {
20118
20969
  hash2 ^= BigInt(byte);
20119
20970
  hash2 = BigInt.asUintN(64, hash2 * 0x100000001b3n);
20120
20971
  }
@@ -20124,12 +20975,12 @@ function truncateUtf8(value, maxBytes) {
20124
20975
  const encoder = new TextEncoder();
20125
20976
  if (encoder.encode(value).length <= maxBytes) return value;
20126
20977
  let result = "";
20127
- let bytes = 0;
20978
+ let bytes2 = 0;
20128
20979
  for (const char of value) {
20129
20980
  const size = encoder.encode(char).length;
20130
- if (bytes + size > maxBytes) break;
20981
+ if (bytes2 + size > maxBytes) break;
20131
20982
  result += char;
20132
- bytes += size;
20983
+ bytes2 += size;
20133
20984
  }
20134
20985
  return result;
20135
20986
  }
@@ -20154,6 +21005,7 @@ function serializedRecord(path, tool, feedback) {
20154
21005
  }
20155
21006
  const record2 = {
20156
21007
  description: truncateUtf8(tool.description ?? "", TOOL_SEARCH_DESCRIPTION_BYTES_MAX),
21008
+ effect: TOOL_SEARCH_EFFECTS.includes(tool.effect) ? tool.effect : "unknown",
20157
21009
  feedback,
20158
21010
  name: tool.name,
20159
21011
  path,
@@ -20164,17 +21016,70 @@ function serializedRecord(path, tool, feedback) {
20164
21016
  function jsonPayload(record2) {
20165
21017
  return {
20166
21018
  description: record2.description,
21019
+ effect: record2.effect,
20167
21020
  feedback: record2.feedback,
20168
21021
  name: record2.name,
20169
21022
  path: record2.path
20170
21023
  };
20171
21024
  }
20172
21025
  function normalizeToolSearchPath(path) {
20173
- const canonical = normalizePath(path);
21026
+ const canonical = canonicalizePath(path);
20174
21027
  const pathError = validatePath(canonical);
20175
21028
  if (pathError !== null) throw pathError;
20176
21029
  return canonical;
20177
21030
  }
21031
+ function normalizeToolSearchOptions(opts) {
21032
+ let effects;
21033
+ if (opts?.effects !== void 0) {
21034
+ if (!Array.isArray(opts.effects) || opts.effects.length === 0) {
21035
+ throw new TBError("invalid_argument", "opts.effects \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
21036
+ }
21037
+ const selected = /* @__PURE__ */ new Set();
21038
+ for (const effect of opts.effects) {
21039
+ if (!TOOL_SEARCH_EFFECTS.includes(effect)) {
21040
+ throw new TBError("invalid_argument", `opts.effects \u542B\u975E\u6CD5 effect '${String(effect)}'`);
21041
+ }
21042
+ selected.add(effect);
21043
+ }
21044
+ effects = TOOL_SEARCH_EFFECTS.filter((effect) => selected.has(effect));
21045
+ }
21046
+ const matching = opts?.matching ?? "best";
21047
+ if (matching !== "best" && matching !== "all") {
21048
+ throw new TBError("invalid_argument", `opts.matching '${String(matching)}' \u975E\u6CD5`);
21049
+ }
21050
+ let minCoverage = opts?.minCoverage;
21051
+ if (minCoverage !== void 0 && (typeof minCoverage !== "number" || !Number.isFinite(minCoverage) || minCoverage <= 0 || minCoverage > 1)) {
21052
+ throw new TBError("invalid_argument", "opts.minCoverage \u5FC5\u987B\u5927\u4E8E 0 \u4E14\u4E0D\u8D85\u8FC7 1");
21053
+ }
21054
+ if (matching === "all") {
21055
+ if (minCoverage !== void 0 && minCoverage !== 1) {
21056
+ throw new TBError("invalid_argument", "opts.matching='all' \u53EA\u5141\u8BB8 minCoverage=1");
21057
+ }
21058
+ minCoverage = 1;
21059
+ }
21060
+ let pathPrefix;
21061
+ if (opts?.pathPrefix !== void 0) {
21062
+ if (typeof opts.pathPrefix !== "string") {
21063
+ throw new TBError("invalid_argument", "opts.pathPrefix \u5FC5\u987B\u662F\u8DEF\u5F84\u5B57\u7B26\u4E32");
21064
+ }
21065
+ pathPrefix = normalizeToolSearchPath(opts.pathPrefix);
21066
+ }
21067
+ return {
21068
+ ...effects === void 0 ? {} : { effects },
21069
+ matching,
21070
+ ...minCoverage === void 0 ? {} : { minCoverage },
21071
+ ...pathPrefix === void 0 ? {} : { pathPrefix }
21072
+ };
21073
+ }
21074
+ function toolSearchOptionsFingerprint(opts) {
21075
+ const normalized = normalizeToolSearchOptions(opts);
21076
+ return stableDigest([
21077
+ normalized.effects ?? null,
21078
+ normalized.matching,
21079
+ normalized.minCoverage ?? null,
21080
+ normalized.pathPrefix ?? null
21081
+ ]);
21082
+ }
20178
21083
  function serializeToolSearchSnapshot(path, tools, feedback = "") {
20179
21084
  const canonical = normalizeToolSearchPath(path);
20180
21085
  const names = /* @__PURE__ */ new Set();
@@ -20230,6 +21135,7 @@ function toolSearchSnapshotDigest(records) {
20230
21135
  record2.path,
20231
21136
  record2.name,
20232
21137
  record2.description,
21138
+ record2.effect,
20233
21139
  record2.feedback,
20234
21140
  record2.toolDigest
20235
21141
  ]));
@@ -20279,54 +21185,93 @@ function scriptRuns(term) {
20279
21185
  }
20280
21186
  return runs;
20281
21187
  }
20282
- function addSearchUnit(units, value, tier) {
21188
+ function addSearchUnit(units, value, tier, logicalTermId) {
20283
21189
  const pattern = likePattern(value);
20284
21190
  if (new TextEncoder().encode(pattern).length > TOOL_SEARCH_LIKE_PATTERN_BYTES_MAX) return false;
20285
- const existing = units.get(pattern);
21191
+ const key = `${logicalTermId}\0${pattern}`;
21192
+ const existing = units.get(key);
20286
21193
  if (existing === void 0 || tier > existing.tier) {
20287
- units.set(pattern, { pattern, tier });
21194
+ units.set(key, { logicalTermId, pattern, tier });
20288
21195
  }
20289
21196
  return true;
20290
21197
  }
20291
- function addChunkedSearchUnits(units, value, tier) {
21198
+ function addChunkedSearchUnits(units, value, tier, logicalTermId) {
20292
21199
  let chunk = "";
20293
21200
  for (const codePoint of value) {
20294
21201
  if (likePatternBytes(chunk + codePoint) <= TOOL_SEARCH_LIKE_PATTERN_BYTES_MAX) {
20295
21202
  chunk += codePoint;
20296
21203
  continue;
20297
21204
  }
20298
- if (chunk.length > 0) addSearchUnit(units, chunk, tier);
21205
+ if (chunk.length > 0) addSearchUnit(units, chunk, tier, logicalTermId);
20299
21206
  chunk = codePoint;
20300
21207
  }
20301
- if (chunk.length > 0) addSearchUnit(units, chunk, tier);
21208
+ if (chunk.length > 0) addSearchUnit(units, chunk, tier, logicalTermId);
20302
21209
  }
20303
- function prepareToolSearchUnits(query) {
20304
- const terms = normalizeToolSearchQuery(query).split(/\s+/u);
20305
- if (terms.length > TOOL_SEARCH_TERM_LIMIT) {
21210
+ function prepareToolSearchQuery(query, unitLimit = TOOL_SEARCH_UNIT_LIMIT) {
21211
+ if (!Number.isInteger(unitLimit) || unitLimit < 1 || unitLimit > TOOL_SEARCH_UNIT_LIMIT) {
21212
+ throw new TBError("invalid_argument", "\u5DE5\u5177\u641C\u7D22 unit limit \u975E\u6CD5");
21213
+ }
21214
+ const rawTerms = normalizeToolSearchQuery(query).split(/\s+/u);
21215
+ if (rawTerms.length > TOOL_SEARCH_TERM_LIMIT) {
20306
21216
  throw new TBError("invalid_argument", `\u641C\u7D22 query \u6700\u591A ${TOOL_SEARCH_TERM_LIMIT} \u4E2A terms`);
20307
21217
  }
21218
+ const seenTerms = /* @__PURE__ */ new Set();
21219
+ const terms = rawTerms.filter((term) => {
21220
+ const key = term.replace(/[A-Z]/g, (char) => char.toLowerCase());
21221
+ if (seenTerms.has(key)) return false;
21222
+ seenTerms.add(key);
21223
+ return true;
21224
+ });
21225
+ if (terms.length > unitLimit) {
21226
+ throw new TBError("invalid_argument", "\u5DE5\u5177\u641C\u7D22 unit limit \u65E0\u6CD5\u8986\u76D6\u5168\u90E8 logical terms");
21227
+ }
20308
21228
  const units = /* @__PURE__ */ new Map();
20309
- for (const term of terms) {
20310
- const wholeAdded = addSearchUnit(units, term, 4);
21229
+ for (const [logicalTermId, term] of terms.entries()) {
21230
+ const wholeAdded = addSearchUnit(units, term, 4, logicalTermId);
20311
21231
  const runs = scriptRuns(term);
20312
21232
  if (runs.length > 1) {
20313
21233
  for (const run of runs) {
20314
21234
  const value = run.codePoints.join("");
20315
- if (run.cjk) addSearchUnit(units, value, 2);
20316
- else addChunkedSearchUnits(units, value, 2);
21235
+ if (run.cjk) addSearchUnit(units, value, 2, logicalTermId);
21236
+ else addChunkedSearchUnits(units, value, 2, logicalTermId);
20317
21237
  }
20318
21238
  } else if (!wholeAdded && runs[0]?.cjk === false) {
20319
- addChunkedSearchUnits(units, term, 2);
21239
+ addChunkedSearchUnits(units, term, 2, logicalTermId);
20320
21240
  }
20321
21241
  for (const run of runs) {
20322
21242
  if (!run.cjk) continue;
20323
21243
  for (let index = 0; index < run.codePoints.length - 1; index += 1) {
20324
- addSearchUnit(units, run.codePoints.slice(index, index + 2).join(""), 2);
21244
+ addSearchUnit(
21245
+ units,
21246
+ run.codePoints.slice(index, index + 2).join(""),
21247
+ 2,
21248
+ logicalTermId
21249
+ );
21250
+ }
21251
+ for (const codePoint of run.codePoints) {
21252
+ addSearchUnit(units, codePoint, 1, logicalTermId);
20325
21253
  }
20326
- for (const codePoint of run.codePoints) addSearchUnit(units, codePoint, 1);
20327
21254
  }
20328
21255
  }
20329
- return [...units.values()].sort((left, right) => right.tier - left.tier).slice(0, TOOL_SEARCH_UNIT_LIMIT);
21256
+ const ordered = [...units.values()].sort((left, right) => right.tier - left.tier);
21257
+ if (ordered.length <= unitLimit) {
21258
+ return { totalTermCount: terms.length, units: ordered };
21259
+ }
21260
+ const selected = /* @__PURE__ */ new Set();
21261
+ const represented = /* @__PURE__ */ new Set();
21262
+ for (const unit of ordered) {
21263
+ if (represented.has(unit.logicalTermId)) continue;
21264
+ represented.add(unit.logicalTermId);
21265
+ selected.add(unit);
21266
+ }
21267
+ for (const unit of ordered) {
21268
+ if (selected.size >= unitLimit) break;
21269
+ selected.add(unit);
21270
+ }
21271
+ return {
21272
+ totalTermCount: terms.length,
21273
+ units: ordered.filter((unit) => selected.has(unit))
21274
+ };
20330
21275
  }
20331
21276
  function cursorBase64UrlDecode(value) {
20332
21277
  if (value.length === 0 || value.length > 4096) {
@@ -20353,13 +21298,15 @@ async function cursorCryptoKey(secret) {
20353
21298
  ["encrypt", "decrypt"]
20354
21299
  );
20355
21300
  }
20356
- async function encodeToolSearchCursor(query, mode, revision, offset, secret) {
21301
+ async function sealToolSearchCursor(query, mode, revision, offset, secret, optionsFingerprint) {
20357
21302
  const payload = {
21303
+ f: optionsFingerprint,
20358
21304
  h: stableDigest(normalizeToolSearchQuery(query)),
21305
+ k: TOOL_SEARCH_RANKING_VERSION,
20359
21306
  m: mode,
20360
21307
  o: offset,
20361
21308
  r: revision,
20362
- v: 1
21309
+ v: 2
20363
21310
  };
20364
21311
  const iv = crypto.getRandomValues(new Uint8Array(12));
20365
21312
  const ciphertext = new Uint8Array(await crypto.subtle.encrypt(
@@ -20372,7 +21319,17 @@ async function encodeToolSearchCursor(query, mode, revision, offset, secret) {
20372
21319
  sealed.set(ciphertext, iv.length);
20373
21320
  return base64urlEncode(sealed);
20374
21321
  }
20375
- async function decodeToolSearchCursor(cursor, query, mode, revision, secret) {
21322
+ async function encodeToolSearchCursor(query, mode, revision, offset, secret, opts) {
21323
+ return await sealToolSearchCursor(
21324
+ query,
21325
+ mode,
21326
+ revision,
21327
+ offset,
21328
+ secret,
21329
+ toolSearchOptionsFingerprint(opts)
21330
+ );
21331
+ }
21332
+ async function decodeToolSearchCursor(cursor, query, mode, revision, secret, opts) {
20376
21333
  if (cursor === void 0) return 0;
20377
21334
  let value;
20378
21335
  try {
@@ -20393,115 +21350,199 @@ async function decodeToolSearchCursor(cursor, query, mode, revision, secret) {
20393
21350
  }
20394
21351
  const payload = value;
20395
21352
  const keys = Object.keys(payload).sort().join(",");
20396
- if (keys !== "h,m,o,r,v" || payload.v !== 1 || payload.h !== stableDigest(normalizeToolSearchQuery(query)) || payload.m !== mode || payload.r !== revision || !Number.isSafeInteger(payload.o) || (payload.o ?? -1) < 0 || (payload.o ?? 0) > TOOL_SEARCH_CURSOR_OFFSET_MAX) {
21353
+ if (keys !== "f,h,k,m,o,r,v" || payload.v !== 2 || payload.k !== TOOL_SEARCH_RANKING_VERSION || payload.f !== toolSearchOptionsFingerprint(opts) || payload.h !== stableDigest(normalizeToolSearchQuery(query)) || payload.m !== mode || payload.r !== revision || !Number.isSafeInteger(payload.o) || (payload.o ?? -1) < 0 || (payload.o ?? 0) > TOOL_SEARCH_CURSOR_OFFSET_MAX) {
20397
21354
  throw new TBError("invalid_argument", "\u641C\u7D22 cursor \u5DF2\u5931\u6548\u6216\u4E0E\u5F53\u524D\u67E5\u8BE2\u4E0D\u5339\u914D");
20398
21355
  }
20399
21356
  return payload.o ?? 0;
20400
21357
  }
21358
+ async function encodeToolSearchCursorForCandidate(query, mode, candidate, secret) {
21359
+ const fingerprint = candidate.searchOptionsFingerprint ?? toolSearchOptionsFingerprint();
21360
+ if (!/^[a-f0-9]{16}$/.test(fingerprint)) {
21361
+ throw new TBError("invalid_argument", "\u5DE5\u5177\u641C\u7D22\u5019\u9009\u7684 options fingerprint \u975E\u6CD5");
21362
+ }
21363
+ return await sealToolSearchCursor(
21364
+ query,
21365
+ mode,
21366
+ candidate.revision,
21367
+ candidate.resumeOffset,
21368
+ secret,
21369
+ fingerprint
21370
+ );
21371
+ }
20401
21372
 
20402
21373
  // ../core/src/search/sqlSearchIndex.ts
20403
21374
  var TOOL_SEARCH_SCHEMA_STATEMENTS = [
20404
- `CREATE TABLE IF NOT EXISTS tb_search_tools_v4 (
21375
+ `CREATE TABLE IF NOT EXISTS tb_search_tools_v5 (
20405
21376
  id INTEGER PRIMARY KEY,
20406
21377
  path TEXT NOT NULL,
20407
21378
  name TEXT NOT NULL,
20408
21379
  description TEXT NOT NULL DEFAULT '',
21380
+ effect TEXT NOT NULL CHECK (effect IN ('read', 'write', 'destructive', 'unknown')),
20409
21381
  feedback TEXT NOT NULL DEFAULT '',
20410
21382
  UNIQUE(path, name)
20411
21383
  )`,
20412
- `CREATE TABLE IF NOT EXISTS tb_search_meta_v4 (
21384
+ `CREATE TABLE IF NOT EXISTS tb_search_meta_v5 (
20413
21385
  singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
20414
21386
  revision INTEGER NOT NULL DEFAULT 0,
20415
21387
  seeded INTEGER NOT NULL DEFAULT 0,
20416
21388
  cursor_secret TEXT NOT NULL
20417
21389
  )`,
20418
- `INSERT OR IGNORE INTO tb_search_meta_v4(
21390
+ `INSERT OR IGNORE INTO tb_search_meta_v5(
20419
21391
  singleton, revision, seeded, cursor_secret
20420
21392
  ) VALUES (1, 0, 0, lower(hex(randomblob(32))))`,
20421
- `CREATE TABLE IF NOT EXISTS tb_search_snapshots_v4 (
21393
+ `CREATE TABLE IF NOT EXISTS tb_search_snapshots_v5 (
20422
21394
  path TEXT PRIMARY KEY,
20423
21395
  digest TEXT NOT NULL
20424
21396
  )`,
20425
- `CREATE TRIGGER IF NOT EXISTS tb_search_snapshots_v4_capacity
20426
- BEFORE INSERT ON tb_search_snapshots_v4
21397
+ `CREATE TRIGGER IF NOT EXISTS tb_search_snapshots_v5_capacity
21398
+ BEFORE INSERT ON tb_search_snapshots_v5
20427
21399
  WHEN NOT EXISTS (
20428
- SELECT 1 FROM tb_search_snapshots_v4 WHERE path = new.path
20429
- ) AND (SELECT COUNT(*) FROM tb_search_snapshots_v4) >= ${TOOL_SEARCH_AUDIT_NODE_LIMIT}
21400
+ SELECT 1 FROM tb_search_snapshots_v5 WHERE path = new.path
21401
+ ) AND (SELECT COUNT(*) FROM tb_search_snapshots_v5) >= ${TOOL_SEARCH_AUDIT_NODE_LIMIT}
20430
21402
  BEGIN
20431
21403
  SELECT RAISE(ABORT, 'tb_search_path_capacity');
20432
21404
  END`
20433
21405
  ];
20434
21406
  var TOOL_SEARCH_CAPACITY_MARKER = "tb_search_path_capacity";
20435
21407
  var TOOL_SEARCH_INSERT_SQL = `
20436
- INSERT INTO tb_search_tools_v4 (path, name, description, feedback)
20437
- VALUES (?, ?, ?, ?)
21408
+ INSERT INTO tb_search_tools_v5 (path, name, description, effect, feedback)
21409
+ VALUES (?, ?, ?, ?, ?)
20438
21410
  `;
20439
- var TOOL_SEARCH_INSERT_SNAPSHOT_SQL = "INSERT INTO tb_search_snapshots_v4(path, digest) VALUES (?, ?)";
20440
- var META_SQL = "SELECT revision, seeded, cursor_secret FROM tb_search_meta_v4 WHERE singleton = 1";
20441
- var SNAPSHOT_DIGESTS_SQL = "SELECT path, digest FROM tb_search_snapshots_v4 ORDER BY path";
21411
+ var TOOL_SEARCH_INSERT_SNAPSHOT_SQL = "INSERT INTO tb_search_snapshots_v5(path, digest) VALUES (?, ?)";
21412
+ var META_SQL = "SELECT revision, seeded, cursor_secret FROM tb_search_meta_v5 WHERE singleton = 1";
21413
+ var SNAPSHOT_DIGESTS_SQL = "SELECT path, digest FROM tb_search_snapshots_v5 ORDER BY path";
20442
21414
  var PATH_STATE_SQL = `
20443
21415
  SELECT snapshots.digest,
20444
- EXISTS(SELECT 1 FROM tb_search_tools_v4 WHERE path = ?) AS has_tools,
20445
- (SELECT COUNT(*) FROM tb_search_snapshots_v4) AS path_count
21416
+ EXISTS(SELECT 1 FROM tb_search_tools_v5 WHERE path = ?) AS has_tools,
21417
+ (SELECT COUNT(*) FROM tb_search_snapshots_v5) AS path_count
20446
21418
  FROM (SELECT 1) AS singleton
20447
- LEFT JOIN tb_search_snapshots_v4 AS snapshots ON snapshots.path = ?
21419
+ LEFT JOIN tb_search_snapshots_v5 AS snapshots ON snapshots.path = ?
20448
21420
  `;
20449
- var PRESENT_SQL = "SELECT 1 AS present FROM tb_search_tools_v4 WHERE path = ? LIMIT 1";
21421
+ var PRESENT_SQL = "SELECT 1 AS present FROM tb_search_tools_v5 WHERE path = ? LIMIT 1";
20450
21422
  var PRESENT_PREFIX_SQL = `
20451
- SELECT 1 AS present FROM tb_search_tools_v4
21423
+ SELECT 1 AS present FROM tb_search_tools_v5
20452
21424
  WHERE path = ? OR substr(path, 1, length(?) + 1) = ? || '/'
20453
21425
  LIMIT 1
20454
21426
  `;
20455
- var DELETE_TOOLS_SQL = "DELETE FROM tb_search_tools_v4 WHERE path = ?";
20456
- var DELETE_SNAPSHOT_SQL = "DELETE FROM tb_search_snapshots_v4 WHERE path = ?";
21427
+ var DELETE_TOOLS_SQL = "DELETE FROM tb_search_tools_v5 WHERE path = ?";
21428
+ var DELETE_SNAPSHOT_SQL = "DELETE FROM tb_search_snapshots_v5 WHERE path = ?";
20457
21429
  var DELETE_TOOLS_PREFIX_SQL = `
20458
- DELETE FROM tb_search_tools_v4
21430
+ DELETE FROM tb_search_tools_v5
20459
21431
  WHERE path = ? OR substr(path, 1, length(?) + 1) = ? || '/'
20460
21432
  `;
20461
21433
  var DELETE_SNAPSHOT_PREFIX_SQL = `
20462
- DELETE FROM tb_search_snapshots_v4
21434
+ DELETE FROM tb_search_snapshots_v5
20463
21435
  WHERE path = ? OR substr(path, 1, length(?) + 1) = ? || '/'
20464
21436
  `;
20465
- var DELETE_ALL_TOOLS_SQL = "DELETE FROM tb_search_tools_v4";
20466
- var DELETE_ALL_SNAPSHOTS_SQL = "DELETE FROM tb_search_snapshots_v4";
20467
- var BUMP_REVISION_SQL = "UPDATE tb_search_meta_v4 SET revision = revision + 1 WHERE singleton = 1";
20468
- var COMPLETE_REBUILD_SQL = "UPDATE tb_search_meta_v4 SET seeded = 1, revision = revision + 1 WHERE singleton = 1";
20469
- function toolSearchCandidateStatement(units, limit, offset, syntax) {
20470
- const values = units.map((unit, index) => `(${syntax.placeholder(index + 1)}, ${unit.tier})`).join(", ");
20471
- const limitParam = syntax.placeholder(units.length + 1);
20472
- const offsetParam = syntax.placeholder(units.length + 2);
21437
+ var DELETE_ALL_TOOLS_SQL = "DELETE FROM tb_search_tools_v5";
21438
+ var DELETE_ALL_SNAPSHOTS_SQL = "DELETE FROM tb_search_snapshots_v5";
21439
+ var BUMP_REVISION_SQL = "UPDATE tb_search_meta_v5 SET revision = revision + 1 WHERE singleton = 1";
21440
+ var COMPLETE_REBUILD_SQL = "UPDATE tb_search_meta_v5 SET seeded = 1, revision = revision + 1 WHERE singleton = 1";
21441
+ function toolSearchEffectSql(effect) {
21442
+ switch (effect) {
21443
+ case "read":
21444
+ return "'read'";
21445
+ case "write":
21446
+ return "'write'";
21447
+ case "destructive":
21448
+ return "'destructive'";
21449
+ case "unknown":
21450
+ return "'unknown'";
21451
+ default:
21452
+ throw new TBError("invalid_argument", `\u5DE5\u5177\u641C\u7D22 effect '${String(effect)}' \u975E\u6CD5`);
21453
+ }
21454
+ }
21455
+ function toolSearchCandidateStatement(units, totalTermCount, limit, offset, constraints, syntax) {
21456
+ if (!Number.isInteger(totalTermCount) || totalTermCount < 1 || units.some((unit) => !Number.isInteger(unit.logicalTermId) || unit.logicalTermId < 0 || unit.logicalTermId >= totalTermCount)) {
21457
+ throw new TBError("invalid_argument", "\u5DE5\u5177\u641C\u7D22 logical term \u8BA1\u5212\u975E\u6CD5");
21458
+ }
21459
+ const values = units.map((unit, index) => `(${unit.logicalTermId}, ${syntax.placeholder(index + 1)}, ${unit.tier}, ${searchUnitAllowsPath(unit) ? 1 : 0})`).join(", ");
21460
+ const hasPrefix = constraints.pathPrefix !== void 0;
21461
+ const prefixParam = hasPrefix ? syntax.placeholder(units.length + 1) : void 0;
21462
+ const trailingStart = units.length + (hasPrefix ? 2 : 1);
21463
+ const limitParam = syntax.placeholder(trailingStart);
21464
+ const offsetParam = syntax.placeholder(trailingStart + 1);
21465
+ const requiredMatchedTerms = constraints.minCoverage === void 0 ? 1 : Math.ceil(constraints.minCoverage * totalTermCount);
20473
21466
  const like = syntax.likeOperator;
21467
+ const effectLiterals = constraints.effects?.map(toolSearchEffectSql);
21468
+ const prefixCte = hasPrefix ? `, prefix_filter(value) AS (VALUES (${prefixParam}))` : "";
21469
+ const filterPredicates = [
21470
+ ...hasPrefix ? [
21471
+ `(
21472
+ tools.path = prefix_filter.value
21473
+ OR substr(tools.path, 1, length(prefix_filter.value) + 1)
21474
+ = prefix_filter.value || '/'
21475
+ )`
21476
+ ] : [],
21477
+ ...effectLiterals === void 0 ? [] : [`tools.effect IN (${effectLiterals.join(", ")})`]
21478
+ ];
21479
+ const filteredToolsCte = filterPredicates.length > 0 ? `,
21480
+ filtered_tools AS (
21481
+ SELECT tools.*
21482
+ FROM tb_search_tools_v5 AS tools
21483
+ ${hasPrefix ? "CROSS JOIN prefix_filter" : ""}
21484
+ WHERE ${filterPredicates.join("\n AND ")}
21485
+ )` : "";
21486
+ const candidateTable = filterPredicates.length > 0 ? "filtered_tools" : "tb_search_tools_v5";
20474
21487
  return {
20475
- params: [...units.map((unit) => unit.pattern), limit + 1, offset],
21488
+ params: [
21489
+ ...units.map((unit) => unit.pattern),
21490
+ ...hasPrefix ? [constraints.pathPrefix] : [],
21491
+ limit + 1,
21492
+ offset
21493
+ ],
20476
21494
  sql: `
20477
- WITH units(pattern, tier) AS (VALUES ${values})
20478
- SELECT * FROM (
20479
- SELECT tools.id, tools.path, tools.name,
20480
- (
20481
- SELECT COALESCE(SUM(units.tier * CASE
20482
- WHEN tools.name ${like} units.pattern ESCAPE '!' THEN 10
20483
- WHEN tools.path ${like} units.pattern ESCAPE '!' THEN 5
20484
- WHEN tools.description ${like} units.pattern ESCAPE '!' THEN 3
20485
- WHEN tools.feedback ${like} units.pattern ESCAPE '!' THEN 1
20486
- ELSE 0
20487
- END), 0)
20488
- FROM units
20489
- ) AS score
20490
- FROM tb_search_tools_v4 AS tools
20491
- ) AS scored_tools
20492
- WHERE score > 0
20493
- ORDER BY score DESC, path, name
21495
+ WITH units(logical_term_id, pattern, tier, path_allowed) AS (VALUES ${values})${prefixCte}${filteredToolsCte},
21496
+ unit_matches AS (
21497
+ SELECT tools.id, tools.path, tools.name, units.logical_term_id,
21498
+ units.tier * CASE
21499
+ WHEN tools.name ${like} units.pattern ESCAPE '!' THEN 10
21500
+ WHEN units.path_allowed = 1
21501
+ AND tools.path ${like} units.pattern ESCAPE '!' THEN 5
21502
+ WHEN tools.description ${like} units.pattern ESCAPE '!' THEN 3
21503
+ WHEN tools.feedback ${like} units.pattern ESCAPE '!' THEN 1
21504
+ ELSE 0
21505
+ END AS unit_score
21506
+ FROM ${candidateTable} AS tools
21507
+ CROSS JOIN units
21508
+ ),
21509
+ term_matches AS (
21510
+ SELECT id, path, name, logical_term_id, MAX(unit_score) AS term_score
21511
+ FROM unit_matches
21512
+ WHERE unit_score > 0
21513
+ GROUP BY id, path, name, logical_term_id
21514
+ ),
21515
+ scored_tools AS (
21516
+ SELECT id, path, name,
21517
+ CAST(COUNT(*) AS INTEGER) AS matched_term_count,
21518
+ CAST(SUM(term_score) AS INTEGER) AS score
21519
+ FROM term_matches
21520
+ GROUP BY id, path, name
21521
+ )
21522
+ SELECT id, path, name, matched_term_count, ${totalTermCount} AS total_term_count
21523
+ FROM scored_tools
21524
+ WHERE matched_term_count >= ${requiredMatchedTerms}
21525
+ ORDER BY matched_term_count DESC, score DESC, path, name
20494
21526
  LIMIT ${limitParam} OFFSET ${offsetParam}
20495
21527
  `
20496
21528
  };
20497
21529
  }
20498
21530
  var sqliteSearchDialect = {
20499
- candidateStatement: (query, limit, offset) => toolSearchCandidateStatement(
20500
- prepareToolSearchUnits(query),
20501
- limit,
20502
- offset,
20503
- { likeOperator: "LIKE", placeholder: () => "?" }
20504
- ),
21531
+ candidateStatement: (query, limit, offset, rawConstraints) => {
21532
+ const constraints = normalizeToolSearchOptions(rawConstraints);
21533
+ const prepared = prepareToolSearchQuery(
21534
+ query,
21535
+ constraints.pathPrefix === void 0 ? TOOL_SEARCH_UNIT_LIMIT : TOOL_SEARCH_UNIT_LIMIT - 1
21536
+ );
21537
+ return toolSearchCandidateStatement(
21538
+ prepared.units,
21539
+ prepared.totalTermCount,
21540
+ limit,
21541
+ offset,
21542
+ constraints,
21543
+ { likeOperator: "LIKE", placeholder: () => "?" }
21544
+ );
21545
+ },
20505
21546
  schemaStatements: TOOL_SEARCH_SCHEMA_STATEMENTS,
20506
21547
  statements: {
20507
21548
  bumpRevision: BUMP_REVISION_SQL,
@@ -20555,6 +21596,10 @@ var SqlSearchIndex = class {
20555
21596
  await this.driver.ensureSchema();
20556
21597
  return (await this.meta()).seeded === 1;
20557
21598
  }
21599
+ async revision() {
21600
+ await this.driver.ensureSchema();
21601
+ return (await this.meta()).revision;
21602
+ }
20558
21603
  async replace(path, tools, opts = {}) {
20559
21604
  const canonical = normalizeToolSearchPath(path);
20560
21605
  const records = serializeToolSearchSnapshot(canonical, tools, opts.feedback ?? "");
@@ -20633,6 +21678,8 @@ var SqlSearchIndex = class {
20633
21678
  async search(query, opts) {
20634
21679
  assertKeywordToolSearchMode(opts);
20635
21680
  const normalized = normalizeToolSearchQuery(query);
21681
+ const constraints = normalizeToolSearchOptions(opts);
21682
+ const optionsFingerprint = toolSearchOptionsFingerprint(constraints);
20636
21683
  const mode = opts?.mode ?? "keyword";
20637
21684
  await this.driver.ensureSchema();
20638
21685
  const meta3 = await this.meta();
@@ -20642,20 +21689,28 @@ var SqlSearchIndex = class {
20642
21689
  normalized,
20643
21690
  mode,
20644
21691
  revision,
20645
- meta3.cursor_secret
21692
+ meta3.cursor_secret,
21693
+ constraints
20646
21694
  );
20647
21695
  const limit = Math.min(normalizeToolSearchLimit(opts?.limit), TOOL_SEARCH_BATCH_LIMIT);
20648
21696
  const rows = await this.driver.all(
20649
- this.dialect.candidateStatement(normalized, limit, offset)
21697
+ this.dialect.candidateStatement(normalized, limit, offset, constraints)
20650
21698
  );
20651
- const hasMore = rows.length > limit;
20652
- const page = hasMore ? rows.slice(0, limit) : rows;
21699
+ const firstBand = rows[0]?.matched_term_count;
21700
+ const bandEnd = constraints.matching === "best" && firstBand !== void 0 ? rows.findIndex((row) => row.matched_term_count !== firstBand) : -1;
21701
+ const pageLength = bandEnd >= 0 ? Math.min(limit, bandEnd) : Math.min(limit, rows.length);
21702
+ const page = rows.slice(0, pageLength);
21703
+ const hasMore = rows.length > page.length;
20653
21704
  const items = page.map((row, index) => ({
21705
+ coverage: row.matched_term_count / row.total_term_count,
21706
+ matchedTermCount: row.matched_term_count,
20654
21707
  name: row.name,
20655
21708
  path: row.path,
20656
21709
  ref: String(row.id),
20657
21710
  resumeOffset: offset + index + 1,
20658
- revision
21711
+ revision,
21712
+ searchOptionsFingerprint: optionsFingerprint,
21713
+ totalTermCount: row.total_term_count
20659
21714
  }));
20660
21715
  const last = items[items.length - 1];
20661
21716
  return hasMore && last !== void 0 ? {
@@ -20665,7 +21720,8 @@ var SqlSearchIndex = class {
20665
21720
  mode,
20666
21721
  revision,
20667
21722
  last.resumeOffset,
20668
- meta3.cursor_secret
21723
+ meta3.cursor_secret,
21724
+ constraints
20669
21725
  )
20670
21726
  } : { items };
20671
21727
  }
@@ -20675,11 +21731,10 @@ var SqlSearchIndex = class {
20675
21731
  if (candidate.revision !== meta3.revision) {
20676
21732
  throw new TBError("invalid_argument", "\u641C\u7D22 cursor \u5DF2\u5931\u6548");
20677
21733
  }
20678
- return await encodeToolSearchCursor(
21734
+ return await encodeToolSearchCursorForCandidate(
20679
21735
  query,
20680
21736
  mode,
20681
- meta3.revision,
20682
- candidate.resumeOffset,
21737
+ candidate,
20683
21738
  meta3.cursor_secret
20684
21739
  );
20685
21740
  }
@@ -20687,11 +21742,12 @@ var SqlSearchIndex = class {
20687
21742
 
20688
21743
  // ../core/src/search/pgSearchDialect.ts
20689
21744
  var PG_SEARCH_SCHEMA_STATEMENTS = [
20690
- `CREATE TABLE IF NOT EXISTS tb_search_tools_v4 (
21745
+ `CREATE TABLE IF NOT EXISTS tb_search_tools_v5 (
20691
21746
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
20692
21747
  path text NOT NULL,
20693
21748
  name text NOT NULL,
20694
21749
  description text NOT NULL DEFAULT '',
21750
+ effect text NOT NULL CHECK (effect IN ('read', 'write', 'destructive', 'unknown')),
20695
21751
  feedback text NOT NULL DEFAULT '',
20696
21752
  UNIQUE(path, name)
20697
21753
  )`,
@@ -20699,7 +21755,7 @@ var PG_SEARCH_SCHEMA_STATEMENTS = [
20699
21755
  // 样本上对单 unit 多列 ILIKE 一律选 Seq Scan;GIN 让写入和体积显著增加却未改善
20700
21756
  // 检索。4000 行只是代表样本,不是严格工具行上限(节点内还受 20KB JSON 约束);
20701
21757
  // 查询单元数或容量边界变化时,应重跑 searchBench 并重新评估查询形状与索引。
20702
- `CREATE TABLE IF NOT EXISTS tb_search_meta_v4 (
21758
+ `CREATE TABLE IF NOT EXISTS tb_search_meta_v5 (
20703
21759
  singleton integer PRIMARY KEY CHECK (singleton = 1),
20704
21760
  revision bigint NOT NULL DEFAULT 0,
20705
21761
  seeded integer NOT NULL DEFAULT 0,
@@ -20707,32 +21763,32 @@ var PG_SEARCH_SCHEMA_STATEMENTS = [
20707
21763
  )`,
20708
21764
  // cursor_secret 需 64 hex(32 字节)。两个内置 gen_random_uuid() 去连字符正好 64
20709
21765
  // hex,避免依赖 pgcrypto 的 gen_random_bytes(自托管 PG 未必预装 pgcrypto)。
20710
- `INSERT INTO tb_search_meta_v4(singleton, revision, seeded, cursor_secret)
21766
+ `INSERT INTO tb_search_meta_v5(singleton, revision, seeded, cursor_secret)
20711
21767
  VALUES (
20712
21768
  1, 0, 0,
20713
21769
  replace(gen_random_uuid()::text, '-', '') || replace(gen_random_uuid()::text, '-', '')
20714
21770
  )
20715
21771
  ON CONFLICT (singleton) DO NOTHING`,
20716
- `CREATE TABLE IF NOT EXISTS tb_search_snapshots_v4 (
21772
+ `CREATE TABLE IF NOT EXISTS tb_search_snapshots_v5 (
20717
21773
  path text PRIMARY KEY,
20718
21774
  digest text NOT NULL
20719
21775
  )`,
20720
- `CREATE OR REPLACE FUNCTION tb_search_snapshots_v4_capacity()
21776
+ `CREATE OR REPLACE FUNCTION tb_search_snapshots_v5_capacity()
20721
21777
  RETURNS trigger AS $$
20722
21778
  BEGIN
20723
- IF (SELECT COUNT(*) FROM tb_search_snapshots_v4) >= ${TOOL_SEARCH_AUDIT_NODE_LIMIT} THEN
21779
+ IF (SELECT COUNT(*) FROM tb_search_snapshots_v5) >= ${TOOL_SEARCH_AUDIT_NODE_LIMIT} THEN
20724
21780
  RAISE EXCEPTION '${TOOL_SEARCH_CAPACITY_MARKER}';
20725
21781
  END IF;
20726
21782
  RETURN NEW;
20727
21783
  END;
20728
21784
  $$ LANGUAGE plpgsql`,
20729
- `DROP TRIGGER IF EXISTS tb_search_snapshots_v4_capacity_trg ON tb_search_snapshots_v4`,
20730
- `CREATE TRIGGER tb_search_snapshots_v4_capacity_trg
20731
- BEFORE INSERT ON tb_search_snapshots_v4
20732
- FOR EACH ROW EXECUTE FUNCTION tb_search_snapshots_v4_capacity()`
21785
+ `DROP TRIGGER IF EXISTS tb_search_snapshots_v5_capacity_trg ON tb_search_snapshots_v5`,
21786
+ `CREATE TRIGGER tb_search_snapshots_v5_capacity_trg
21787
+ BEFORE INSERT ON tb_search_snapshots_v5
21788
+ FOR EACH ROW EXECUTE FUNCTION tb_search_snapshots_v5_capacity()`
20733
21789
  ];
20734
21790
  var PG_SEARCH_WRITE_LOCK_KEY = 2069799624;
20735
- var PG_SEARCH_INSERT_SNAPSHOT_SQL = "INSERT INTO tb_search_snapshots_v4(path, digest) VALUES ($1, $2)";
21791
+ var PG_SEARCH_INSERT_SNAPSHOT_SQL = "INSERT INTO tb_search_snapshots_v5(path, digest) VALUES ($1, $2)";
20736
21792
  function pgBulkInsertSql(table, columns, rows) {
20737
21793
  const tuples = [];
20738
21794
  for (let r = 0; r < rows; r++) {
@@ -20741,50 +21797,65 @@ function pgBulkInsertSql(table, columns, rows) {
20741
21797
  }
20742
21798
  return `INSERT INTO ${table} (${columns.join(", ")}) VALUES ${tuples.join(", ")}`;
20743
21799
  }
20744
- var PG_SEARCH_TOOLS_TABLE = "tb_search_tools_v4";
20745
- var PG_SEARCH_TOOLS_COLUMNS = ["path", "name", "description", "feedback"];
20746
- var PG_SEARCH_SNAPSHOTS_TABLE = "tb_search_snapshots_v4";
21800
+ var PG_SEARCH_TOOLS_TABLE = "tb_search_tools_v5";
21801
+ var PG_SEARCH_TOOLS_COLUMNS = [
21802
+ "path",
21803
+ "name",
21804
+ "description",
21805
+ "effect",
21806
+ "feedback"
21807
+ ];
21808
+ var PG_SEARCH_SNAPSHOTS_TABLE = "tb_search_snapshots_v5";
20747
21809
  var PG_SEARCH_SNAPSHOTS_COLUMNS = ["path", "digest"];
20748
21810
  var PG_SEARCH_INSERT_ROWS_MAX = 1e3;
20749
21811
  var META_SQL2 = `
20750
21812
  SELECT revision::int AS revision, seeded::int AS seeded, cursor_secret
20751
- FROM tb_search_meta_v4 WHERE singleton = 1
21813
+ FROM tb_search_meta_v5 WHERE singleton = 1
20752
21814
  `;
20753
- var SNAPSHOT_DIGESTS_SQL2 = "SELECT path, digest FROM tb_search_snapshots_v4 ORDER BY path";
21815
+ var SNAPSHOT_DIGESTS_SQL2 = "SELECT path, digest FROM tb_search_snapshots_v5 ORDER BY path";
20754
21816
  var PATH_STATE_SQL2 = `
20755
21817
  SELECT snapshots.digest,
20756
- EXISTS(SELECT 1 FROM tb_search_tools_v4 WHERE path = $1)::int AS has_tools,
20757
- (SELECT COUNT(*) FROM tb_search_snapshots_v4)::int AS path_count
21818
+ EXISTS(SELECT 1 FROM tb_search_tools_v5 WHERE path = $1)::int AS has_tools,
21819
+ (SELECT COUNT(*) FROM tb_search_snapshots_v5)::int AS path_count
20758
21820
  FROM (SELECT 1) AS singleton
20759
- LEFT JOIN tb_search_snapshots_v4 AS snapshots ON snapshots.path = $2
21821
+ LEFT JOIN tb_search_snapshots_v5 AS snapshots ON snapshots.path = $2
20760
21822
  `;
20761
- var PRESENT_SQL2 = "SELECT 1 AS present FROM tb_search_tools_v4 WHERE path = $1 LIMIT 1";
21823
+ var PRESENT_SQL2 = "SELECT 1 AS present FROM tb_search_tools_v5 WHERE path = $1 LIMIT 1";
20762
21824
  var PRESENT_PREFIX_SQL2 = `
20763
- SELECT 1 AS present FROM tb_search_tools_v4
21825
+ SELECT 1 AS present FROM tb_search_tools_v5
20764
21826
  WHERE path = $1 OR substr(path, 1, length($2) + 1) = $3 || '/'
20765
21827
  LIMIT 1
20766
21828
  `;
20767
- var DELETE_TOOLS_SQL2 = "DELETE FROM tb_search_tools_v4 WHERE path = $1";
20768
- var DELETE_SNAPSHOT_SQL2 = "DELETE FROM tb_search_snapshots_v4 WHERE path = $1";
21829
+ var DELETE_TOOLS_SQL2 = "DELETE FROM tb_search_tools_v5 WHERE path = $1";
21830
+ var DELETE_SNAPSHOT_SQL2 = "DELETE FROM tb_search_snapshots_v5 WHERE path = $1";
20769
21831
  var DELETE_TOOLS_PREFIX_SQL2 = `
20770
- DELETE FROM tb_search_tools_v4
21832
+ DELETE FROM tb_search_tools_v5
20771
21833
  WHERE path = $1 OR substr(path, 1, length($2) + 1) = $3 || '/'
20772
21834
  `;
20773
21835
  var DELETE_SNAPSHOT_PREFIX_SQL2 = `
20774
- DELETE FROM tb_search_snapshots_v4
21836
+ DELETE FROM tb_search_snapshots_v5
20775
21837
  WHERE path = $1 OR substr(path, 1, length($2) + 1) = $3 || '/'
20776
21838
  `;
20777
- var DELETE_ALL_TOOLS_SQL2 = "DELETE FROM tb_search_tools_v4";
20778
- var DELETE_ALL_SNAPSHOTS_SQL2 = "DELETE FROM tb_search_snapshots_v4";
20779
- var BUMP_REVISION_SQL2 = "UPDATE tb_search_meta_v4 SET revision = revision + 1 WHERE singleton = 1";
20780
- var COMPLETE_REBUILD_SQL2 = "UPDATE tb_search_meta_v4 SET seeded = 1, revision = revision + 1 WHERE singleton = 1";
21839
+ var DELETE_ALL_TOOLS_SQL2 = "DELETE FROM tb_search_tools_v5";
21840
+ var DELETE_ALL_SNAPSHOTS_SQL2 = "DELETE FROM tb_search_snapshots_v5";
21841
+ var BUMP_REVISION_SQL2 = "UPDATE tb_search_meta_v5 SET revision = revision + 1 WHERE singleton = 1";
21842
+ var COMPLETE_REBUILD_SQL2 = "UPDATE tb_search_meta_v5 SET seeded = 1, revision = revision + 1 WHERE singleton = 1";
20781
21843
  var pgSearchDialect = {
20782
- candidateStatement: (query, limit, offset) => toolSearchCandidateStatement(
20783
- prepareToolSearchUnits(query),
20784
- limit,
20785
- offset,
20786
- { likeOperator: "ILIKE", placeholder: (index) => `$${index}` }
20787
- ),
21844
+ candidateStatement: (query, limit, offset, rawConstraints) => {
21845
+ const constraints = normalizeToolSearchOptions(rawConstraints);
21846
+ const prepared = prepareToolSearchQuery(
21847
+ query,
21848
+ constraints.pathPrefix === void 0 ? TOOL_SEARCH_UNIT_LIMIT : TOOL_SEARCH_UNIT_LIMIT - 1
21849
+ );
21850
+ return toolSearchCandidateStatement(
21851
+ prepared.units,
21852
+ prepared.totalTermCount,
21853
+ limit,
21854
+ offset,
21855
+ constraints,
21856
+ { likeOperator: "ILIKE", placeholder: (index) => `$${index}` }
21857
+ );
21858
+ },
20788
21859
  schemaStatements: PG_SEARCH_SCHEMA_STATEMENTS,
20789
21860
  statements: {
20790
21861
  bumpRevision: BUMP_REVISION_SQL2,
@@ -21098,7 +22169,7 @@ function guessContentType(path) {
21098
22169
  if (dot < 0) return "text/plain";
21099
22170
  return CONTENT_TYPE_BY_EXT[path.slice(dot + 1).toLowerCase()] ?? "text/plain";
21100
22171
  }
21101
- function clampLimit4(limit) {
22172
+ function clampLimit5(limit) {
21102
22173
  if (limit === void 0) return LIST_LIMIT_DEFAULT;
21103
22174
  if (!Number.isInteger(limit) || limit < 1) {
21104
22175
  throw new TBError("invalid_argument", `limit \u975E\u6CD5:${limit}`);
@@ -21153,7 +22224,7 @@ function createSkillhubProvider(store, opts) {
21153
22224
  };
21154
22225
  return {
21155
22226
  async list(listOpts) {
21156
- const limit = clampLimit4(listOpts?.limit);
22227
+ const limit = clampLimit5(listOpts?.limit);
21157
22228
  const res = await store.list(keyPrefix, {
21158
22229
  delimiter: "/",
21159
22230
  cursor: listOpts?.cursor,
@@ -21213,7 +22284,7 @@ function createSkillhubProvider(store, opts) {
21213
22284
  if (typeof query !== "string" || query === "") {
21214
22285
  throw new TBError("invalid_argument", "query \u4E0D\u80FD\u4E3A\u7A7A");
21215
22286
  }
21216
- const limit = clampLimit4(searchOpts?.limit);
22287
+ const limit = clampLimit5(searchOpts?.limit);
21217
22288
  const q = query.toLowerCase();
21218
22289
  const after = searchOpts?.cursor;
21219
22290
  const items = [];
@@ -21388,6 +22459,7 @@ function toolToCmd(nodePath, tool, index) {
21388
22459
  if (tool.description !== void 0) {
21389
22460
  cmd.h = index ? summarizeOneLine(tool.description) : tool.description;
21390
22461
  }
22462
+ if (tool.delivery !== void 0) cmd.delivery = tool.delivery;
21391
22463
  if (!index && tool.inputSchema !== void 0) cmd.inputSchema = tool.inputSchema;
21392
22464
  if (!index && tool.outputSchema !== void 0) cmd.outputSchema = tool.outputSchema;
21393
22465
  if (tool.effect !== void 0) cmd.effect = tool.effect;
@@ -21494,7 +22566,15 @@ function virtualizeTools(v, upstream) {
21494
22566
  const hidden = new Set(v?.hide ?? []);
21495
22567
  for (const tool of upstream) {
21496
22568
  if (hidden.has(tool.name)) continue;
21497
- const name = v ? virtualName(v, tool.name) : tool.name;
22569
+ const projectedName = v ? virtualName(v, tool.name) : tool.name;
22570
+ const name = canonicalizeSegment(projectedName);
22571
+ const previous = reverse.get(name);
22572
+ if (previous !== void 0) {
22573
+ throw new TBError(
22574
+ "invalid_argument",
22575
+ `\u5DE5\u5177\u540D '${previous}' \u4E0E '${tool.name}' \u89C4\u8303\u5316\u540E\u51B2\u7A81:'${name}',\u8BF7\u663E\u5F0F rename`
22576
+ );
22577
+ }
21498
22578
  const description = v?.describe?.[tool.name] ?? tool.description;
21499
22579
  exposed.push({ ...tool, name, description });
21500
22580
  reverse.set(name, tool.name);
@@ -21511,7 +22591,7 @@ function resolveUpstreamTool(v, source, virtualName2) {
21511
22591
  }
21512
22592
 
21513
22593
  // ../core/src/tree/registry.ts
21514
- function clampLimit5(limit) {
22594
+ function clampLimit6(limit) {
21515
22595
  if (limit === void 0 || limit < 1) return LIST_LIMIT_DEFAULT;
21516
22596
  return limit > LIST_LIMIT_MAX ? LIST_LIMIT_MAX : limit;
21517
22597
  }
@@ -21612,7 +22692,7 @@ var NodeRegistryStore = class {
21612
22692
  */
21613
22693
  async list(prefix, opts) {
21614
22694
  const normPrefix = prefix === void 0 ? "" : canonicalizePath(prefix);
21615
- const limit = clampLimit5(opts?.limit);
22695
+ const limit = clampLimit6(opts?.limit);
21616
22696
  const all = (await this.scanAll()).filter((n) => isPrefixOf(normPrefix, n.path)).sort(byPath);
21617
22697
  const cursor = opts?.cursor;
21618
22698
  const start = cursor ? all.findIndex((n) => n.path === cursor) + 1 : 0;
@@ -21701,6 +22781,7 @@ var NodeRegistryStore = class {
21701
22781
  description: node.description,
21702
22782
  ...node.config !== void 0 ? { config: node.config } : {},
21703
22783
  ...node.virtualize !== void 0 ? { virtualize: node.virtualize } : {},
22784
+ ...opts.deviceId !== void 0 ? { deviceId: opts.deviceId } : {},
21704
22785
  ...opts.online !== void 0 ? { online: opts.online } : {},
21705
22786
  ...opts.lastSeenAt !== void 0 ? { lastSeenAt: opts.lastSeenAt } : {},
21706
22787
  registeredBy,
@@ -21921,8 +23002,8 @@ function createPlugin(opts = {}) {
21921
23002
  const b64 = raw.replaceAll("-", "+").replaceAll("_", "/");
21922
23003
  const padded = b64.padEnd(b64.length + (4 - b64.length % 4) % 4, "=");
21923
23004
  try {
21924
- const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
21925
- return new TextDecoder().decode(bytes);
23005
+ const bytes2 = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
23006
+ return new TextDecoder().decode(bytes2);
21926
23007
  } catch {
21927
23008
  throw new TBError("invalid_argument", `${HEADER_TB_UPSTREAM_AUTH} is not valid base64url`);
21928
23009
  }
@@ -22248,12 +23329,26 @@ export {
22248
23329
  pluginTokenSecretName,
22249
23330
  createStoreModule,
22250
23331
  RemoteAllowlistStore,
23332
+ DeviceMailboxService,
22251
23333
  tbErrorBodySchema,
22252
23334
  nodeInputSchema,
22253
23335
  helpJsonSchema,
22254
23336
  treeJsonSchema,
23337
+ toolSearchRelevanceSchema,
23338
+ toolSearchSourceSchema,
23339
+ toolSearchSourceResultSchema,
22255
23340
  toolSearchPageSchema,
22256
23341
  toolSearchRequestSchema,
23342
+ deviceOperationSummarySchema,
23343
+ deviceOperationDetailSchema,
23344
+ deviceOperationListRequestSchema,
23345
+ deviceOperationListResponseSchema,
23346
+ deviceOperationIdentityRequestSchema,
23347
+ deviceOperationClaimRequestSchema,
23348
+ deviceOperationClaimResponseSchema,
23349
+ deviceOperationRenewRequestSchema,
23350
+ deviceOperationRenewResponseSchema,
23351
+ deviceOperationCompleteRequestSchema,
22257
23352
  registryNodeSchema,
22258
23353
  feedbackViewSchema,
22259
23354
  feedbackDetailSchema,
@@ -22320,11 +23415,15 @@ export {
22320
23415
  TOOL_SEARCH_BATCH_LIMIT,
22321
23416
  TOOL_SEARCH_WORK_LIMIT,
22322
23417
  TOOL_SEARCH_AUDIT_NODE_LIMIT,
23418
+ TOOL_SEARCH_DESCRIPTION_BYTES_MAX,
22323
23419
  TOOL_SEARCH_PAGE_BYTES,
23420
+ TOOL_SEARCH_RANKING_VERSION,
22324
23421
  normalizeToolSearchLimit,
22325
23422
  normalizeToolSearchPath,
23423
+ normalizeToolSearchOptions,
22326
23424
  serializeToolSearchSnapshot,
22327
23425
  toolSearchSnapshotDigest,
23426
+ prepareToolSearchQuery,
22328
23427
  TOOL_SEARCH_SCHEMA_STATEMENTS,
22329
23428
  TOOL_SEARCH_INSERT_SQL,
22330
23429
  TOOL_SEARCH_INSERT_SNAPSHOT_SQL,