@tool-bridge/server 0.15.0 → 0.16.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-QYTOPRFV.js → airtable-S3T4TAN7.js} +4 -4
  2. package/dist/{alt_text_generator_ai-2CFYNSZV.js → alt_text_generator_ai-4DIRIDKL.js} +4 -4
  3. package/dist/{amap-HGOBOHSZ.js → amap-LGVGNPML.js} +4 -4
  4. package/dist/{anthropic-3OXOQNSR.js → anthropic-27RFZO35.js} +4 -4
  5. package/dist/{apify-PUV2QGKL.js → apify-6WLYAIAS.js} +4 -4
  6. package/dist/{brave_search-DG75AVKA.js → brave_search-K2SVJ66F.js} +4 -4
  7. package/dist/{chunk-KH663T3A.js → chunk-2O27H4Y2.js} +1 -1
  8. package/dist/{chunk-NLPTB3IL.js → chunk-BB6ZLMTU.js} +1120 -114
  9. package/dist/{chunk-UI4E4T5Q.js → chunk-GKX72JMJ.js} +1 -1
  10. package/dist/{chunk-NQ67PIIS.js → chunk-VK3V2WTY.js} +2 -2
  11. package/dist/{chunk-DXXYNIXV.js → chunk-YB2FLNWI.js} +1611 -42
  12. package/dist/{circleci-EFNLX5AW.js → circleci-WV3ITU7Q.js} +4 -4
  13. package/dist/{clerk-UWDLMZVM.js → clerk-NFQEFKS4.js} +4 -4
  14. package/dist/{cloudflare_dns-WQXVDSPU.js → cloudflare_dns-2RK5ASBJ.js} +4 -4
  15. package/dist/{cohere-VPDIKUDP.js → cohere-5KEW7LWT.js} +4 -4
  16. package/dist/{confluence-NBOOQNTR.js → confluence-SZE63S46.js} +4 -4
  17. package/dist/{convertapi-6Z6OP25T.js → convertapi-QPG2JC5K.js} +4 -4
  18. package/dist/{currencyapi-CAMUWCTV.js → currencyapi-UO2APQRY.js} +4 -4
  19. package/dist/{deepseek-GRALXYXR.js → deepseek-LJELIMM3.js} +4 -4
  20. package/dist/{docker_hub-5AASF3TJ.js → docker_hub-2MFXSBOF.js} +4 -4
  21. package/dist/{dropbox-HS3VGRD3.js → dropbox-GQYNTTVA.js} +4 -4
  22. package/dist/{dub-KHERUTW6.js → dub-KI2LHXST.js} +4 -4
  23. package/dist/{e2b-4MGHPFXY.js → e2b-WO6ISY3C.js} +4 -4
  24. package/dist/{exa-O77PKTBT.js → exa-IZ7B3BPT.js} +4 -4
  25. package/dist/{fal_ai-TX2TZFMP.js → fal_ai-LTMF36GJ.js} +4 -4
  26. package/dist/{feishu-I7VPWO3C.js → feishu-WM7SLSFD.js} +3 -3
  27. package/dist/{feishu_custom_bot-D4XZDLES.js → feishu_custom_bot-66W3MXH2.js} +4 -4
  28. package/dist/{figma-SW3HZCZ5.js → figma-2Z274VQZ.js} +4 -4
  29. package/dist/{firehydrant-ND5DEW3L.js → firehydrant-IFI4R65V.js} +4 -4
  30. package/dist/{fixer-ZTSV7ZK3.js → fixer-BCCZEB3D.js} +4 -4
  31. package/dist/{fly-RZ7SZKFT.js → fly-755FXYB5.js} +4 -4
  32. package/dist/{front-Q5MZVUGX.js → front-4FZKKXXK.js} +4 -4
  33. package/dist/{geocodio-QPHHODHG.js → geocodio-WYKDJ3DP.js} +4 -4
  34. package/dist/{ghost-3Q6O7EWZ.js → ghost-5BNQTCOH.js} +4 -4
  35. package/dist/{github-LDMGJHXV.js → github-ULELF5VX.js} +4 -4
  36. package/dist/{gmail-6QBCI3TT.js → gmail-G34WT64J.js} +4 -4
  37. package/dist/{googlecalendar-LLSYY4ZJ.js → googlecalendar-BQU255SC.js} +4 -4
  38. package/dist/{googledocs-KRGVBGWY.js → googledocs-HPCNQXYJ.js} +4 -4
  39. package/dist/{grafana-OHPZMRHT.js → grafana-VOV24FIC.js} +4 -4
  40. package/dist/{graphhopper-2PBY7OX4.js → graphhopper-D5RXW6AP.js} +4 -4
  41. package/dist/index.d.ts +147 -61
  42. package/dist/index.js +2 -2
  43. package/dist/{ip2proxy-GPBQZKKG.js → ip2proxy-DAYO7EKZ.js} +4 -4
  44. package/dist/{ipgeolocation_io-YNTOLOFH.js → ipgeolocation_io-XD4PSLII.js} +4 -4
  45. package/dist/{ipqualityscore-CZLQUAQ7.js → ipqualityscore-K27QEAWK.js} +4 -4
  46. package/dist/{jina_ai-JIFEYH5C.js → jina_ai-E6UXFV2P.js} +4 -4
  47. package/dist/{jira-2DKTIWHK.js → jira-ZDSXGQ6W.js} +4 -4
  48. package/dist/{langsmith-UCX7NHKP.js → langsmith-TIFJRREW.js} +4 -4
  49. package/dist/{lemlist-IS6K2VJG.js → lemlist-DASEMJMA.js} +4 -4
  50. package/dist/{linear-AK6547ZS.js → linear-KE6UP4SP.js} +4 -4
  51. package/dist/{logsnag-EB5LICOH.js → logsnag-IMNDN4R5.js} +4 -4
  52. package/dist/main.js +101 -101
  53. package/dist/{mattermost-YOSWUA3R.js → mattermost-HHHMUXQA.js} +4 -4
  54. package/dist/{mem0-IICTSHLB.js → mem0-D2FGGOIY.js} +4 -4
  55. package/dist/{memos-DIV6MZPB.js → memos-4OVIGFPH.js} +4 -4
  56. package/dist/{metabase-ZGHNTCAT.js → metabase-33WCX5YD.js} +4 -4
  57. package/dist/{mistral_ai-IAG3WZYO.js → mistral_ai-QIMZ2JYL.js} +4 -4
  58. package/dist/{mother_duck-PYKG4ZHO.js → mother_duck-M6VWS2VH.js} +4 -4
  59. package/dist/{ngrok-BXN3DGTK.js → ngrok-Z2PKQBHH.js} +4 -4
  60. package/dist/{notes-L2UO36HQ.js → notes-SS4RPC4K.js} +2 -2
  61. package/dist/{notion-PVHVOV4R.js → notion-FJCNQ6I7.js} +4 -4
  62. package/dist/{open_exchange_rates-DVMOPFNQ.js → open_exchange_rates-WU57WTT5.js} +4 -4
  63. package/dist/{openai-VW6F5YTK.js → openai-JODZ4GKT.js} +4 -4
  64. package/dist/{opengraph_io-MQUREYWM.js → opengraph_io-SQRAUSV5.js} +4 -4
  65. package/dist/{openrouter-5MI3ODCU.js → openrouter-Q5XXEJZ7.js} +4 -4
  66. package/dist/{outline-B2U7NIVA.js → outline-RT2EHJ2X.js} +4 -4
  67. package/dist/{paddle-LDZTVYQO.js → paddle-OF2V7GAC.js} +4 -4
  68. package/dist/{perplexity-P7BNWFMH.js → perplexity-LNZEWLHG.js} +4 -4
  69. package/dist/{pinecone-3Y67EJYP.js → pinecone-JSJIXZYN.js} +4 -4
  70. package/dist/{pipedrive-4KFLGCJW.js → pipedrive-D4CKVUXR.js} +4 -4
  71. package/dist/{polar-NIULQPUD.js → polar-M7ZYP6UR.js} +4 -4
  72. package/dist/{posthog-UXGCIHRP.js → posthog-YV7CZOH4.js} +4 -4
  73. package/dist/{postmark-XT77YNQM.js → postmark-KYHLECRG.js} +4 -4
  74. package/dist/{prerender-37E6ZN6Z.js → prerender-HCMHBKV2.js} +4 -4
  75. package/dist/{pubmed-BO2W4IR6.js → pubmed-WYVLCCN3.js} +4 -4
  76. package/dist/{railway-5BACZNYC.js → railway-TWIRD5KR.js} +4 -4
  77. package/dist/{readwise-DXABSDKY.js → readwise-UZ467IQ7.js} +4 -4
  78. package/dist/{render-4MCO6RLA.js → render-MECUC5CC.js} +4 -4
  79. package/dist/{replicate-IVEPY4EP.js → replicate-A5MEK3SW.js} +4 -4
  80. package/dist/{resend-XJBJO46B.js → resend-BC6EAWJQ.js} +4 -4
  81. package/dist/{rootly-R6QDVF3D.js → rootly-ZM3QDUIS.js} +4 -4
  82. package/dist/{scrapfly-E24XNN32.js → scrapfly-WAML42QJ.js} +4 -4
  83. package/dist/{scrapingbee-QT3O334C.js → scrapingbee-DG7PET3V.js} +4 -4
  84. package/dist/{screenshot_fyi-MJNFPUFV.js → screenshot_fyi-HY6WTHIO.js} +4 -4
  85. package/dist/{semantic_scholar-BVKWS72R.js → semantic_scholar-C5IZA7IP.js} +4 -4
  86. package/dist/{sentry-52T5XARK.js → sentry-OKBPSTDM.js} +4 -4
  87. package/dist/{shodan-RTOTTCX2.js → shodan-PPC6DYU4.js} +4 -4
  88. package/dist/{shopify-3RVWQDZ6.js → shopify-EWGUHBXO.js} +4 -4
  89. package/dist/{stripe-M3T2K3EW.js → stripe-6RPDPC5W.js} +4 -4
  90. package/dist/{supabase-UUUXAEYR.js → supabase-SK7DQ42N.js} +4 -4
  91. package/dist/{tavily-LVOZQQKJ.js → tavily-YCG5VQMH.js} +4 -4
  92. package/dist/{telegram-WJQPCKQT.js → telegram-2OES5FYB.js} +4 -4
  93. package/dist/{telnyx-THKSKAI2.js → telnyx-POOSBBR4.js} +4 -4
  94. package/dist/{todoist-YTRGFFRC.js → todoist-M7YD6YSS.js} +4 -4
  95. package/dist/{trello-QBK6WKN2.js → trello-IS6V5W3E.js} +4 -4
  96. package/dist/{turso-EA44ZKLR.js → turso-RCFHUFVH.js} +4 -4
  97. package/dist/{twilio-GSW3V55S.js → twilio-OS2RXFGO.js} +4 -4
  98. package/dist/{umami-DVZ7XUYC.js → umami-PQ2SJ2LN.js} +4 -4
  99. package/dist/{unsplash-ZWUWQ7OS.js → unsplash-C6BPZO4E.js} +4 -4
  100. package/dist/{upstash_redis-BQIQB5NM.js → upstash_redis-WH6UER7H.js} +4 -4
  101. package/dist/{uptimerobot-2OJHTUPV.js → uptimerobot-M3YZGS6Z.js} +4 -4
  102. package/dist/{v2ex-IZF4BB4O.js → v2ex-R4JWTEHN.js} +4 -4
  103. package/dist/{vercel-PI3BJENX.js → vercel-FZVYUOQD.js} +4 -4
  104. package/dist/{wolfram_alpha_api-EYQ2QCHH.js → wolfram_alpha_api-2KOLO5DC.js} +4 -4
  105. package/dist/{wordpress-ETDA6XLX.js → wordpress-XRWGO6LG.js} +4 -4
  106. package/dist/{workos-D7INO4Y4.js → workos-WR4NKOYL.js} +4 -4
  107. package/dist/{zhihu-Q7FNHEDM.js → zhihu-BKZIB3WA.js} +4 -4
  108. package/package.json +3 -3
@@ -3,6 +3,7 @@ import {
3
3
  CONTEXT_CAPABILITIES,
4
4
  CONTEXT_DIRECT_UPLOAD_CAPABILITY,
5
5
  DEFAULT_MAX_NODES,
6
+ DEFAULT_STORE_DRIVER_KEY_ROOT,
6
7
  DEVICE_CALL_TIMEOUT_MS,
7
8
  DeviceGatewaySession,
8
9
  FEEDBACK_HIDE_SCORE,
@@ -32,6 +33,7 @@ import {
32
33
  SKRegistryStore,
33
34
  SecretStoreImpl,
34
35
  SqlSearchIndex,
36
+ StoreService,
35
37
  TBError,
36
38
  TB_ERROR_CODES,
37
39
  TOOL_SEARCH_AUDIT_NODE_LIMIT,
@@ -79,6 +81,7 @@ import {
79
81
  createObjectContextProvider,
80
82
  createObjectContextUploadGrant,
81
83
  createSkillhubProvider,
84
+ createStoreModule,
82
85
  decodeDeviceFrame,
83
86
  derivePresence,
84
87
  deviceDirectoryHelpModel,
@@ -136,6 +139,7 @@ import {
136
139
  shouldRefresh,
137
140
  skillhubHelpModel,
138
141
  skillhubScopeForCmd,
142
+ storeUri,
139
143
  string,
140
144
  toJSONSchema,
141
145
  toolHelpModel,
@@ -146,12 +150,13 @@ import {
146
150
  url,
147
151
  validatePath,
148
152
  virtualizeTools
149
- } from "./chunk-DXXYNIXV.js";
153
+ } from "./chunk-YB2FLNWI.js";
150
154
 
151
155
  // src/config.ts
152
156
  var DEFAULT_PORT = 8787;
153
157
  var DEFAULT_MAX_HOPS = 4;
154
158
  var DEFAULT_DEVICE_RECLAIM_SEC = 24 * 60 * 60;
159
+ var DEFAULT_STORE_CLEANUP_INTERVAL_SEC = 15 * 60;
155
160
  function positiveIntEnv(value) {
156
161
  const n = Number(value);
157
162
  return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
@@ -183,6 +188,7 @@ function configFromEnv(env = process.env) {
183
188
  allowInsecure
184
189
  },
185
190
  deviceReclaimSec: positiveIntEnv(env.TB_DEVICE_RECLAIM_SEC) ?? DEFAULT_DEVICE_RECLAIM_SEC,
191
+ storeCleanupIntervalSec: positiveIntEnv(env.TB_STORE_CLEANUP_INTERVAL_SEC) ?? DEFAULT_STORE_CLEANUP_INTERVAL_SEC,
186
192
  // 0 合法(立即关停,本地/单机默认);positiveIntEnv 拒 0,故单独解析。
187
193
  shutdownDrainSec: (() => {
188
194
  const n = Number(env.TB_SHUTDOWN_DRAIN_SEC);
@@ -237,6 +243,34 @@ function configFromEnv(env = process.env) {
237
243
  if (refTtl !== void 0) config.refTtlSec = refTtl;
238
244
  const uploadGrantTtl = presignTtlEnv(env.TB_UPLOAD_GRANT_TTL_SEC);
239
245
  if (uploadGrantTtl !== void 0) config.uploadGrantTtlSec = uploadGrantTtl;
246
+ const storeMaxObjectBytes = positiveIntEnv(env.TB_STORE_MAX_OBJECT_BYTES);
247
+ if (storeMaxObjectBytes !== void 0) config.storeMaxObjectBytes = storeMaxObjectBytes;
248
+ const storeRelayMaxBytes = positiveIntEnv(env.TB_STORE_RELAY_MAX_BYTES);
249
+ if (storeRelayMaxBytes !== void 0) config.storeRelayMaxBytes = storeRelayMaxBytes;
250
+ const storeUploadTtlSec = presignTtlEnv(env.TB_STORE_UPLOAD_TTL_SEC);
251
+ if (storeUploadTtlSec !== void 0) config.storeUploadTtlSec = storeUploadTtlSec;
252
+ const storeShareTtlSec = presignTtlEnv(env.TB_STORE_SHARE_TTL_SEC);
253
+ if (storeShareTtlSec !== void 0) config.storeShareTtlSec = storeShareTtlSec;
254
+ const storeReadTtlSec = presignTtlEnv(env.TB_STORE_READ_TTL_SEC);
255
+ if (storeReadTtlSec !== void 0) config.storeReadTtlSec = storeReadTtlSec;
256
+ const storeCallMaxBytes = positiveIntEnv(env.TB_STORE_CALL_MAX_BYTES);
257
+ if (storeCallMaxBytes !== void 0) config.storeCallMaxBytes = storeCallMaxBytes;
258
+ const storeCallMaxObjectBytes = positiveIntEnv(env.TB_STORE_CALL_MAX_OBJECT_BYTES);
259
+ if (storeCallMaxObjectBytes !== void 0) {
260
+ config.storeCallMaxObjectBytes = storeCallMaxObjectBytes;
261
+ }
262
+ const storeCallMaxObjects = positiveIntEnv(env.TB_STORE_CALL_MAX_OBJECTS);
263
+ if (storeCallMaxObjects !== void 0) config.storeCallMaxObjects = storeCallMaxObjects;
264
+ const storeCallAllowedContentTypes = (env.TB_STORE_CALL_ALLOWED_CONTENT_TYPES ?? "").split(",").map((value) => value.trim()).filter((value) => value.length > 0);
265
+ if (storeCallAllowedContentTypes.length > 0) {
266
+ config.storeCallAllowedContentTypes = storeCallAllowedContentTypes;
267
+ }
268
+ if (env.TB_STORE_TOKEN_SECRET !== void 0 && env.TB_STORE_TOKEN_SECRET.length > 0) {
269
+ if (env.TB_STORE_TOKEN_SECRET.length < 16) {
270
+ throw new Error("TB_STORE_TOKEN_SECRET \u81F3\u5C11\u9700\u8981 16 \u4E2A\u5B57\u7B26");
271
+ }
272
+ config.storeTokenSecret = env.TB_STORE_TOKEN_SECRET;
273
+ }
240
274
  return config;
241
275
  }
242
276
 
@@ -439,7 +473,8 @@ var BUILTIN_MODULES = [
439
473
  "plugin",
440
474
  "catalog",
441
475
  "federation",
442
- "annotation"
476
+ "annotation",
477
+ "store"
443
478
  ];
444
479
  var BUILTIN_DESCRIPTIONS = {
445
480
  sk: "SecretKey registry",
@@ -449,7 +484,8 @@ var BUILTIN_DESCRIPTIONS = {
449
484
  plugin: "Plugin registry (external plugins)",
450
485
  catalog: "Built-in integration catalog (read-only)",
451
486
  federation: "Remote federation host allowlist",
452
- annotation: "Admin notes shown in ~help of any path"
487
+ annotation: "Admin notes shown in ~help of any path",
488
+ store: "Deployment-level private object Store"
453
489
  };
454
490
  async function mintAdminWithPlaintext(store, plaintext, now) {
455
491
  const { key } = await mintKey(adminBootstrapInput(), now);
@@ -554,10 +590,14 @@ async function presignS3Url(client, url2, ttlSec) {
554
590
  }
555
591
  async function presignS3Put(client, url2, ttlSec, opts) {
556
592
  assertPresignTtlSec(ttlSec);
593
+ if ("contentLength" in opts && (!Number.isSafeInteger(opts.contentLength) || opts.contentLength < 0)) {
594
+ throw new TBError("invalid_argument", "presigned PUT contentLength \u5FC5\u987B\u662F\u975E\u8D1F\u5B89\u5168\u6574\u6570");
595
+ }
557
596
  const target = new URL(url2);
558
597
  target.searchParams.set("X-Amz-Expires", String(ttlSec));
559
598
  const headers = {
560
599
  "content-type": opts.contentType,
600
+ ..."contentLength" in opts ? { "content-length": String(opts.contentLength) } : {},
561
601
  ...opts.ifNoneMatch === void 0 ? {} : { "if-none-match": opts.ifNoneMatch }
562
602
  };
563
603
  const signed = await client.sign(
@@ -602,12 +642,19 @@ function createS3ObjectStore(cfg, opts) {
602
642
  service: "s3",
603
643
  region: cfg.region ?? "auto"
604
644
  });
645
+ const putClient = new AwsClient({
646
+ accessKeyId: cfg.accessKeyId,
647
+ secretAccessKey: cfg.secretAccessKey,
648
+ service: "s3",
649
+ region: cfg.region ?? "auto",
650
+ retries: 0
651
+ });
605
652
  const base = cfg.endpoint.replace(/\/+$/, "");
606
653
  const bucketUrl = `${base}/${cfg.bucket}`;
607
654
  const urlFor = (key) => `${bucketUrl}/${encodeObjectKey(key)}`;
608
- const s3Fetch = async (url2, init) => {
655
+ const s3FetchWith = async (aws, url2, init) => {
609
656
  try {
610
- return await client.fetch(url2, init);
657
+ return await aws.fetch(url2, init);
611
658
  } catch (err) {
612
659
  throw normalizeUpstreamError({
613
660
  kind: "network",
@@ -615,6 +662,47 @@ function createS3ObjectStore(cfg, opts) {
615
662
  });
616
663
  }
617
664
  };
665
+ const s3Fetch = async (url2, init) => {
666
+ return await s3FetchWith(client, url2, init);
667
+ };
668
+ const fetchBody = (body) => {
669
+ if (typeof body === "string" || body instanceof ArrayBuffer) return body;
670
+ if (body instanceof Uint8Array) {
671
+ return body.buffer instanceof ArrayBuffer ? new Uint8Array(body.buffer, body.byteOffset, body.byteLength) : new Uint8Array(body);
672
+ }
673
+ const source = body;
674
+ const reader = source.getReader();
675
+ return new ReadableStream({
676
+ async pull(controller) {
677
+ try {
678
+ const chunk = await reader.read();
679
+ if (chunk.done) {
680
+ reader.releaseLock();
681
+ controller.close();
682
+ } else if (chunk.value !== void 0) {
683
+ controller.enqueue(chunk.value);
684
+ }
685
+ } catch (error) {
686
+ try {
687
+ reader.releaseLock();
688
+ } catch {
689
+ }
690
+ controller.error(error);
691
+ }
692
+ },
693
+ async cancel(reason) {
694
+ try {
695
+ if (reader.cancel !== void 0) await reader.cancel(reason);
696
+ else await source.cancel?.(reason);
697
+ } finally {
698
+ try {
699
+ reader.releaseLock();
700
+ } catch {
701
+ }
702
+ }
703
+ }
704
+ }, { highWaterMark: 0 });
705
+ };
618
706
  const metaFromHeaders = (key, headers) => {
619
707
  const metadata = {};
620
708
  headers.forEach((value, name) => {
@@ -633,13 +721,14 @@ function createS3ObjectStore(cfg, opts) {
633
721
  if (contentType !== null) meta.contentType = contentType;
634
722
  return meta;
635
723
  };
724
+ const headObject = async (key) => {
725
+ const resp = await s3Fetch(urlFor(key), { method: "HEAD" });
726
+ if (resp.status === 404) return null;
727
+ if (!resp.ok) throw s3Error("head", resp.status);
728
+ return metaFromHeaders(key, resp.headers);
729
+ };
636
730
  return {
637
- async head(key) {
638
- const resp = await s3Fetch(urlFor(key), { method: "HEAD" });
639
- if (resp.status === 404) return null;
640
- if (!resp.ok) throw s3Error("head", resp.status);
641
- return metaFromHeaders(key, resp.headers);
642
- },
731
+ head: headObject,
643
732
  async get(key) {
644
733
  const resp = await s3Fetch(urlFor(key), { method: "GET" });
645
734
  if (resp.status === 404) {
@@ -653,27 +742,31 @@ function createS3ObjectStore(cfg, opts) {
653
742
  return { meta: metaFromHeaders(key, resp.headers), body: resp.body ?? EMPTY_STREAM };
654
743
  },
655
744
  async put(key, body, putOpts) {
656
- const bytes = await objectBodyToBytes(body);
657
745
  const headers = {};
746
+ headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
658
747
  if (putOpts?.contentType !== void 0) headers["content-type"] = putOpts.contentType;
659
748
  for (const [name, value] of Object.entries(putOpts?.metadata ?? {})) {
660
749
  headers[`${META_HEADER_PREFIX}${name}`] = value;
661
750
  }
662
751
  if (putOpts?.ifMatchEtag !== void 0) headers["if-match"] = `"${putOpts.ifMatchEtag}"`;
663
- const resp = await s3Fetch(urlFor(key), { method: "PUT", headers, body: bytes });
752
+ if (putOpts?.ifNoneMatch !== void 0) headers["if-none-match"] = putOpts.ifNoneMatch;
753
+ const resp = await s3FetchWith(putClient, urlFor(key), {
754
+ method: "PUT",
755
+ headers,
756
+ body: fetchBody(body)
757
+ });
664
758
  await resp.body?.cancel();
665
- if (resp.status === 412 || putOpts?.ifMatchEtag !== void 0 && resp.status === 404) {
666
- throw new TBError("conflict", `etag \u4E0D\u5339\u914D:'${key}'`);
759
+ const conditional = putOpts?.ifMatchEtag !== void 0 || putOpts?.ifNoneMatch !== void 0;
760
+ if (conditional && (resp.status === 409 || resp.status === 412) || putOpts?.ifMatchEtag !== void 0 && resp.status === 404) {
761
+ throw new TBError("conflict", `\u5BF9\u8C61\u6761\u4EF6\u5199\u51B2\u7A81:'${key}'`);
667
762
  }
668
763
  if (!resp.ok) throw s3Error("put", resp.status);
669
- const meta = {
670
- key,
671
- etag: stripEtagQuotes(resp.headers.get("etag") ?? ""),
672
- size: bytes.byteLength,
673
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
674
- metadata: putOpts?.metadata ?? {}
675
- };
676
- if (putOpts?.contentType !== void 0) meta.contentType = putOpts.contentType;
764
+ const meta = await headObject(key);
765
+ if (meta === null) {
766
+ throw new TBError("unavailable", "s3 put succeeded but HEAD did not observe the object", {
767
+ retryable: true
768
+ });
769
+ }
677
770
  return meta;
678
771
  },
679
772
  async delete(key) {
@@ -730,6 +823,9 @@ function createS3ObjectStore(cfg, opts) {
730
823
  },
731
824
  presignPut(key, ttlSec, putOpts) {
732
825
  return presignS3Put(client, urlFor(key), ttlSec, putOpts);
826
+ },
827
+ presignPutExact(key, ttlSec, putOpts) {
828
+ return presignS3Put(client, urlFor(key), ttlSec, putOpts);
733
829
  }
734
830
  };
735
831
  }
@@ -1029,6 +1125,461 @@ var SearchSynchronizer = class {
1029
1125
  // ../app/src/deps.ts
1030
1126
  var TOOL_CACHE_TTL_DEFAULT = 300;
1031
1127
 
1128
+ // ../app/src/storeRefToken.ts
1129
+ async function hmacKey(secret) {
1130
+ const digest = await crypto.subtle.digest(
1131
+ "SHA-256",
1132
+ new TextEncoder().encode(`tb-store-read-ref:v1:${secret}`)
1133
+ );
1134
+ return crypto.subtle.importKey("raw", digest, { name: "HMAC", hash: "SHA-256" }, false, [
1135
+ "sign",
1136
+ "verify"
1137
+ ]);
1138
+ }
1139
+ async function signStoreRefToken(payload, secret) {
1140
+ const body = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
1141
+ const mac = await crypto.subtle.sign(
1142
+ "HMAC",
1143
+ await hmacKey(secret),
1144
+ new TextEncoder().encode(body)
1145
+ );
1146
+ return `${body}.${base64urlEncode(new Uint8Array(mac))}`;
1147
+ }
1148
+ async function verifyStoreRefToken(token, secret) {
1149
+ const dot = token.indexOf(".");
1150
+ if (dot <= 0) return null;
1151
+ const body = token.slice(0, dot);
1152
+ let signature;
1153
+ try {
1154
+ signature = base64urlDecode(token.slice(dot + 1));
1155
+ } catch {
1156
+ return null;
1157
+ }
1158
+ const valid = await crypto.subtle.verify(
1159
+ "HMAC",
1160
+ await hmacKey(secret),
1161
+ signature,
1162
+ new TextEncoder().encode(body)
1163
+ );
1164
+ if (!valid) return null;
1165
+ try {
1166
+ const payload = JSON.parse(
1167
+ new TextDecoder().decode(base64urlDecode(body))
1168
+ );
1169
+ if (payload.v !== 1 || typeof payload.objectId !== "string" || payload.objectId.length < 1 || typeof payload.exp !== "number" || !Number.isSafeInteger(payload.exp)) return null;
1170
+ return payload;
1171
+ } catch {
1172
+ return null;
1173
+ }
1174
+ }
1175
+
1176
+ // ../app/src/store.ts
1177
+ var STORE_CALL_CAPABILITY_HEADER = "x-tb-store-capability";
1178
+ var STORE_UPLOAD_HEADER = "x-tb-store-upload";
1179
+ var KEY_STORE_TOKEN_SECRET = "sys:store-token-secret:v1";
1180
+ var KEY_STORE_CLEANUP_PROGRESS = "sys:store-cleanup-progress:v1";
1181
+ var STORE_READ_TTL_SEC_DEFAULT = 15 * 60;
1182
+ var STORE_CALL_MAX_OBJECTS_DEFAULT = 4;
1183
+ var STORE_CALL_MAX_OBJECT_BYTES_DEFAULT = 256 * 1024 * 1024;
1184
+ var STORE_CALL_MAX_BYTES_DEFAULT = 512 * 1024 * 1024;
1185
+ function positiveInt(value, fallback, field) {
1186
+ const actual = value ?? fallback;
1187
+ if (!Number.isSafeInteger(actual) || actual < 1) {
1188
+ throw new TBError("invalid_argument", `${field} must be a positive integer`);
1189
+ }
1190
+ return actual;
1191
+ }
1192
+ function randomSecret() {
1193
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
1194
+ let out = "";
1195
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
1196
+ return out;
1197
+ }
1198
+ async function derivedTokenSecret(root) {
1199
+ const digest = await crypto.subtle.digest(
1200
+ "SHA-256",
1201
+ new TextEncoder().encode(`tb-store-capability-root:v1:${root}`)
1202
+ );
1203
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1204
+ }
1205
+ async function storeTokenSecret(state, configured) {
1206
+ if (configured !== void 0) {
1207
+ if (configured.length < 16) {
1208
+ throw new TBError("invalid_argument", "storeTokenSecret must contain at least 16 characters");
1209
+ }
1210
+ return await derivedTokenSecret(configured);
1211
+ }
1212
+ const current = await state.get(KEY_STORE_TOKEN_SECRET);
1213
+ if (typeof current === "string" && current.length >= 16) return current;
1214
+ if (current !== null) {
1215
+ throw new TBError("internal", "default Store token secret state is invalid", {
1216
+ retryable: false
1217
+ });
1218
+ }
1219
+ if (state.compareAndSwap === void 0) {
1220
+ throw new TBError("unavailable", "default Store requires StateStore.compareAndSwap", {
1221
+ retryable: false
1222
+ });
1223
+ }
1224
+ const candidate = randomSecret();
1225
+ if (await state.compareAndSwap(KEY_STORE_TOKEN_SECRET, null, candidate)) return candidate;
1226
+ const winner = await state.get(KEY_STORE_TOKEN_SECRET);
1227
+ if (typeof winner !== "string" || winner.length < 16) {
1228
+ throw new TBError("unavailable", "default Store token secret initialization conflicted", {
1229
+ retryable: true
1230
+ });
1231
+ }
1232
+ return winner;
1233
+ }
1234
+ function serviceOptions(deps, tokenSecret) {
1235
+ return {
1236
+ tokenSecret,
1237
+ ...deps.storeMaxObjectBytes === void 0 ? {} : { maxObjectBytes: deps.storeMaxObjectBytes },
1238
+ ...deps.storeRelayMaxBytes === void 0 ? {} : { relayMaxBytes: deps.storeRelayMaxBytes },
1239
+ ...deps.storeUploadTtlSec === void 0 ? {} : { uploadTtlSec: deps.storeUploadTtlSec },
1240
+ ...deps.storeShareTtlSec === void 0 ? {} : { shareTtlSec: deps.storeShareTtlSec }
1241
+ };
1242
+ }
1243
+ async function defaultStoreRuntime(deps) {
1244
+ if (deps.objects === void 0) {
1245
+ throw new TBError("unavailable", "default Store object driver is not configured", {
1246
+ retryable: false
1247
+ });
1248
+ }
1249
+ const [objects, tokenSecret] = await Promise.all([
1250
+ deps.objects(),
1251
+ storeTokenSecret(deps.state, deps.storeTokenSecret ?? deps.encryptionKey)
1252
+ ]);
1253
+ return {
1254
+ objects,
1255
+ tokenSecret,
1256
+ service: new StoreService(deps.state, objects, serviceOptions(deps, tokenSecret))
1257
+ };
1258
+ }
1259
+ function requestOrigin(runtime, deps) {
1260
+ const raw = deps.canonicalOrigin ?? runtime?.requestOrigin;
1261
+ if (raw === void 0) {
1262
+ throw new TBError("unavailable", "request origin is unavailable", { retryable: false });
1263
+ }
1264
+ let url2;
1265
+ try {
1266
+ url2 = new URL(raw);
1267
+ } catch {
1268
+ throw new TBError("unavailable", "request origin is invalid", { retryable: false });
1269
+ }
1270
+ if (url2.protocol !== "https:" && url2.protocol !== "http:" || url2.username !== "" || url2.password !== "") {
1271
+ throw new TBError("unavailable", "request origin is invalid", { retryable: false });
1272
+ }
1273
+ return url2.origin;
1274
+ }
1275
+ function storeUploadGrant(start, origin2) {
1276
+ const useRelay = start.alreadyCompleted || start.signedRequest === void 0;
1277
+ const url2 = useRelay ? `${origin2}/~store/uploads/${encodeURIComponent(start.uploadId)}` : start.signedRequest.url;
1278
+ const headers = useRelay ? { [STORE_UPLOAD_HEADER]: start.uploadToken } : start.signedRequest.headers;
1279
+ return {
1280
+ uploadId: start.uploadId,
1281
+ objectUri: start.objectUri,
1282
+ transport: useRelay ? "relay" : "presigned-put",
1283
+ method: "PUT",
1284
+ url: url2,
1285
+ headers,
1286
+ expiresAt: start.expiresAt,
1287
+ maxBytes: start.maxBytes,
1288
+ uploadToken: start.uploadToken,
1289
+ ...start.alreadyCompleted && start.descriptor !== void 0 ? { alreadyCompleted: true, descriptor: start.descriptor } : {}
1290
+ };
1291
+ }
1292
+ function readTtlSec(deps) {
1293
+ return positiveInt(
1294
+ deps.storeReadTtlSec,
1295
+ deps.refTtlSec ?? STORE_READ_TTL_SEC_DEFAULT,
1296
+ "storeReadTtlSec"
1297
+ );
1298
+ }
1299
+ async function ownerReadGrant(object2, runtime, deps, tokenSecret) {
1300
+ if (object2.status !== "ready" || object2.size === void 0) {
1301
+ throw TBError.notFound("Store object not found");
1302
+ }
1303
+ const ttlSec = readTtlSec(deps);
1304
+ const expiresAt = new Date(Date.now() + ttlSec * 1e3).toISOString();
1305
+ const token = await signStoreRefToken({
1306
+ v: 1,
1307
+ objectId: object2.id,
1308
+ exp: Math.floor(Date.parse(expiresAt) / 1e3)
1309
+ }, tokenSecret);
1310
+ return {
1311
+ $ref: `${requestOrigin(runtime, deps)}/~store/refs/${encodeURIComponent(token)}`,
1312
+ uri: storeUri(object2.id),
1313
+ contentType: object2.contentType,
1314
+ size: object2.size,
1315
+ expiresAt
1316
+ };
1317
+ }
1318
+ function shareReadGrant(result, runtime, deps) {
1319
+ return {
1320
+ shareId: result.shareId,
1321
+ uri: result.uri,
1322
+ expiresAt: result.expiresAt,
1323
+ $ref: `${requestOrigin(runtime, deps)}/~store/shares/${encodeURIComponent(result.token)}`
1324
+ };
1325
+ }
1326
+ function defaultStoreModuleDeps(deps, store) {
1327
+ return {
1328
+ service: store.service,
1329
+ callbacks: {
1330
+ createUpload: (start, runtime) => storeUploadGrant(start, requestOrigin(runtime, deps)),
1331
+ read: async (object2, runtime) => await ownerReadGrant(
1332
+ object2,
1333
+ runtime,
1334
+ deps,
1335
+ store.tokenSecret
1336
+ ),
1337
+ share: (result, runtime) => shareReadGrant(result, runtime, deps)
1338
+ }
1339
+ };
1340
+ }
1341
+ function lazyDefaultStoreModule(deps) {
1342
+ const helpOnly = createStoreModule({
1343
+ // createStoreModule.help is static and never reads these placeholders.
1344
+ service: void 0,
1345
+ callbacks: {
1346
+ createUpload: () => {
1347
+ throw new TBError("unavailable", "default Store is unavailable");
1348
+ },
1349
+ read: () => {
1350
+ throw new TBError("unavailable", "default Store is unavailable");
1351
+ },
1352
+ share: () => {
1353
+ throw new TBError("unavailable", "default Store is unavailable");
1354
+ }
1355
+ }
1356
+ });
1357
+ return {
1358
+ module: helpOnly.module,
1359
+ description: helpOnly.description,
1360
+ help: (path) => helpOnly.help(path),
1361
+ async dispatch(cmd, args, ctx, runtime) {
1362
+ const store = await defaultStoreRuntime(deps);
1363
+ return await createStoreModule(defaultStoreModuleDeps(deps, store)).dispatch(
1364
+ cmd,
1365
+ args,
1366
+ ctx,
1367
+ runtime
1368
+ );
1369
+ }
1370
+ };
1371
+ }
1372
+ async function beginCapabilityUpload(deps, input, capabilityToken, origin2) {
1373
+ const store = await defaultStoreRuntime(deps);
1374
+ const start = await store.service.beginCallUpload(input, capabilityToken);
1375
+ return storeUploadGrant(start, origin2);
1376
+ }
1377
+ async function completeCapabilityUpload(deps, uploadId2, uploadToken) {
1378
+ return await (await defaultStoreRuntime(deps)).service.completeUploadWithToken(
1379
+ uploadId2,
1380
+ uploadToken
1381
+ );
1382
+ }
1383
+ async function abortCapabilityUpload(deps, uploadId2, uploadToken) {
1384
+ return await (await defaultStoreRuntime(deps)).service.abortUploadWithToken(
1385
+ uploadId2,
1386
+ uploadToken
1387
+ );
1388
+ }
1389
+ async function relayStoreUpload(deps, uploadId2, uploadToken, body) {
1390
+ const store = await defaultStoreRuntime(deps);
1391
+ const session = await store.service.verifyUploadToken(uploadToken);
1392
+ if (session.id !== uploadId2) {
1393
+ throw new TBError("permission_denied", "upload capability does not match the URL");
1394
+ }
1395
+ return await store.service.commitRelayUpload({
1396
+ uploadToken,
1397
+ body: body ?? new Uint8Array()
1398
+ });
1399
+ }
1400
+ async function storeObjectResponse(objects, object2) {
1401
+ const got = await objects.get(object2.driverKey);
1402
+ if (got === null) throw TBError.notFound("not found");
1403
+ const reader = got.body.getReader();
1404
+ const body = new ReadableStream({
1405
+ async pull(controller) {
1406
+ try {
1407
+ const chunk = await reader.read();
1408
+ if (chunk.done) {
1409
+ reader.releaseLock();
1410
+ controller.close();
1411
+ } else if (chunk.value !== void 0) {
1412
+ controller.enqueue(chunk.value);
1413
+ }
1414
+ } catch (error) {
1415
+ try {
1416
+ reader.releaseLock();
1417
+ } catch {
1418
+ }
1419
+ controller.error(error);
1420
+ }
1421
+ },
1422
+ async cancel(reason) {
1423
+ try {
1424
+ if (reader.cancel !== void 0) await reader.cancel(reason);
1425
+ else await got.body.cancel?.(reason);
1426
+ } finally {
1427
+ try {
1428
+ reader.releaseLock();
1429
+ } catch {
1430
+ }
1431
+ }
1432
+ }
1433
+ });
1434
+ return new Response(body, {
1435
+ headers: {
1436
+ "content-type": object2.contentType,
1437
+ "content-length": String(got.meta.size),
1438
+ "cache-control": "private, no-store"
1439
+ }
1440
+ });
1441
+ }
1442
+ async function issueDeviceCallUpload(deps, deviceId, callId, context) {
1443
+ if (deps.objects === void 0) return null;
1444
+ const store = await defaultStoreRuntime(deps);
1445
+ const maxObjects = positiveInt(
1446
+ deps.storeCallMaxObjects,
1447
+ STORE_CALL_MAX_OBJECTS_DEFAULT,
1448
+ "storeCallMaxObjects"
1449
+ );
1450
+ const maxBytes = positiveInt(
1451
+ deps.storeCallMaxBytes,
1452
+ STORE_CALL_MAX_BYTES_DEFAULT,
1453
+ "storeCallMaxBytes"
1454
+ );
1455
+ const maxObjectBytes = Math.min(
1456
+ positiveInt(
1457
+ deps.storeCallMaxObjectBytes,
1458
+ deps.storeMaxObjectBytes ?? STORE_CALL_MAX_OBJECT_BYTES_DEFAULT,
1459
+ "storeCallMaxObjectBytes"
1460
+ ),
1461
+ maxBytes
1462
+ );
1463
+ const issued = await store.service.issueCallUploadCapability({
1464
+ callId,
1465
+ owner: context.caller.owner,
1466
+ producer: `device:${deviceId}`,
1467
+ expiresAt: context.expiresAt,
1468
+ maxObjects,
1469
+ maxBytes,
1470
+ maxObjectBytes,
1471
+ allowedContentTypes: deps.storeCallAllowedContentTypes ?? ["*/*"]
1472
+ });
1473
+ return {
1474
+ context: {
1475
+ ...context,
1476
+ upload: {
1477
+ token: issued.token,
1478
+ expiresAt: issued.capability.expiresAt,
1479
+ // The SDK uses this as an early per-object check; the server remains
1480
+ // authoritative for the aggregate call budget.
1481
+ maxBytes: issued.capability.maxObjectBytes,
1482
+ maxObjects: issued.capability.maxObjects
1483
+ }
1484
+ },
1485
+ revoke: async () => await store.service.revokeCallUploadCapability(issued.token)
1486
+ };
1487
+ }
1488
+ function cleanupProgress(value) {
1489
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1490
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1491
+ }
1492
+ const record2 = value;
1493
+ if (!Number.isSafeInteger(record2.revision) || record2.revision < 1) {
1494
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1495
+ }
1496
+ const cursors = record2.cursors;
1497
+ if (cursors === null || typeof cursors !== "object" || Array.isArray(cursors)) {
1498
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1499
+ }
1500
+ for (const field of [
1501
+ "uploads",
1502
+ "objects",
1503
+ "shares",
1504
+ "callCapabilities",
1505
+ "driverObjects",
1506
+ "idempotencyBindings"
1507
+ ]) {
1508
+ const cursor = cursors[field];
1509
+ if (cursor !== null && typeof cursor !== "string") {
1510
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1511
+ }
1512
+ }
1513
+ return value;
1514
+ }
1515
+ function emptyCleanupResult() {
1516
+ return {
1517
+ abandonedObjects: 0,
1518
+ deletedBytes: 0,
1519
+ deletedOrphans: 0,
1520
+ deletedStaging: 0,
1521
+ expiredCallCapabilities: 0,
1522
+ expiredIdempotencyBindings: 0,
1523
+ expiredShares: 0,
1524
+ expiredUploads: 0
1525
+ };
1526
+ }
1527
+ function addCleanupResult(target, page) {
1528
+ target.abandonedObjects += page.abandonedObjects;
1529
+ target.deletedBytes += page.deletedBytes;
1530
+ target.deletedOrphans += page.deletedOrphans;
1531
+ target.deletedStaging += page.deletedStaging;
1532
+ target.expiredCallCapabilities += page.expiredCallCapabilities;
1533
+ target.expiredIdempotencyBindings += page.expiredIdempotencyBindings;
1534
+ target.expiredShares += page.expiredShares;
1535
+ target.expiredUploads += page.expiredUploads;
1536
+ }
1537
+ async function cleanupDefaultStore(deps, opts = {}) {
1538
+ await deps.ensureReady?.();
1539
+ const store = await defaultStoreRuntime(deps);
1540
+ const maxPages = Math.min(positiveInt(opts.maxPages, 8, "maxPages"), 64);
1541
+ const limit = opts.limit === void 0 ? void 0 : positiveInt(opts.limit, 200, "limit");
1542
+ const rawProgress = await deps.state.get(KEY_STORE_CLEANUP_PROGRESS);
1543
+ let progress = rawProgress === null ? null : cleanupProgress(rawProgress);
1544
+ let cursors = progress?.cursors;
1545
+ const aggregate = emptyCleanupResult();
1546
+ for (let pageNumber = 0; pageNumber < maxPages; pageNumber++) {
1547
+ const page = await store.service.cleanup({
1548
+ ...limit === void 0 ? {} : { limit },
1549
+ ...cursors === void 0 ? {} : { cursors },
1550
+ // staging 不使用权威状态 cursor;同一 host tick 只运行一次,避免
1551
+ // maxPages 内重复扫描同一 driver maintenance 范围。
1552
+ runDriverMaintenance: pageNumber === 0
1553
+ });
1554
+ addCleanupResult(aggregate, page);
1555
+ if (page.cursors === void 0) {
1556
+ if (progress !== null) {
1557
+ await deps.state.compareAndSwap(
1558
+ KEY_STORE_CLEANUP_PROGRESS,
1559
+ progress.revision,
1560
+ null
1561
+ );
1562
+ }
1563
+ return aggregate;
1564
+ }
1565
+ const next = {
1566
+ cursors: page.cursors,
1567
+ revision: (progress?.revision ?? 0) + 1
1568
+ };
1569
+ const advanced = await deps.state.compareAndSwap(
1570
+ KEY_STORE_CLEANUP_PROGRESS,
1571
+ progress?.revision ?? null,
1572
+ next
1573
+ );
1574
+ if (!advanced) {
1575
+ return aggregate;
1576
+ }
1577
+ progress = next;
1578
+ cursors = next.cursors;
1579
+ }
1580
+ return { ...aggregate, ...cursors === void 0 ? {} : { cursors } };
1581
+ }
1582
+
1032
1583
  // ../app/src/deviceNodes.ts
1033
1584
  function tbErrorFromBody(body) {
1034
1585
  return new TBError(body.code, body.message, { retryable: body.retryable });
@@ -1041,7 +1592,19 @@ function requireDevice(deps) {
1041
1592
  }
1042
1593
  async function invokeDevice(deps, deviceId, req) {
1043
1594
  const id = crypto.randomUUID();
1044
- const body = await requireDevice(deps).invoke(deviceId, { id, ...req });
1595
+ const upload = req.context === void 0 ? null : await issueDeviceCallUpload(deps, deviceId, id, req.context);
1596
+ let body;
1597
+ try {
1598
+ body = await requireDevice(deps).invoke(deviceId, {
1599
+ id,
1600
+ path: req.path,
1601
+ arguments: req.arguments,
1602
+ ...req.context === void 0 ? {} : { context: upload?.context ?? req.context }
1603
+ });
1604
+ } finally {
1605
+ await upload?.revoke().catch(() => {
1606
+ });
1607
+ }
1045
1608
  if (!body || !("ok" in body)) {
1046
1609
  throw new TBError("unavailable", "device session returned invalid result");
1047
1610
  }
@@ -7158,9 +7721,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7158
7721
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
7159
7722
  return `${varKind} ${this.name}${rhs};` + _n;
7160
7723
  }
7161
- optimizeNames(names, constants) {
7724
+ optimizeNames(names, constants2) {
7162
7725
  if (!names[this.name.str]) return;
7163
- if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants);
7726
+ if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
7164
7727
  return this;
7165
7728
  }
7166
7729
  get names() {
@@ -7177,9 +7740,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7177
7740
  render({ _n }) {
7178
7741
  return `${this.lhs} = ${this.rhs};` + _n;
7179
7742
  }
7180
- optimizeNames(names, constants) {
7743
+ optimizeNames(names, constants2) {
7181
7744
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
7182
- this.rhs = optimizeExpr(this.rhs, names, constants);
7745
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
7183
7746
  return this;
7184
7747
  }
7185
7748
  get names() {
@@ -7238,8 +7801,8 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7238
7801
  optimizeNodes() {
7239
7802
  return `${this.code}` ? this : void 0;
7240
7803
  }
7241
- optimizeNames(names, constants) {
7242
- this.code = optimizeExpr(this.code, names, constants);
7804
+ optimizeNames(names, constants2) {
7805
+ this.code = optimizeExpr(this.code, names, constants2);
7243
7806
  return this;
7244
7807
  }
7245
7808
  get names() {
@@ -7265,12 +7828,12 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7265
7828
  }
7266
7829
  return nodes.length > 0 ? this : void 0;
7267
7830
  }
7268
- optimizeNames(names, constants) {
7831
+ optimizeNames(names, constants2) {
7269
7832
  const { nodes } = this;
7270
7833
  let i = nodes.length;
7271
7834
  while (i--) {
7272
7835
  const n = nodes[i];
7273
- if (n.optimizeNames(names, constants)) continue;
7836
+ if (n.optimizeNames(names, constants2)) continue;
7274
7837
  subtractNames(names, n.names);
7275
7838
  nodes.splice(i, 1);
7276
7839
  }
@@ -7317,11 +7880,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7317
7880
  if (cond === false || !this.nodes.length) return void 0;
7318
7881
  return this;
7319
7882
  }
7320
- optimizeNames(names, constants) {
7883
+ optimizeNames(names, constants2) {
7321
7884
  var _a;
7322
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
7323
- if (!(super.optimizeNames(names, constants) || this.else)) return;
7324
- this.condition = optimizeExpr(this.condition, names, constants);
7885
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
7886
+ if (!(super.optimizeNames(names, constants2) || this.else)) return;
7887
+ this.condition = optimizeExpr(this.condition, names, constants2);
7325
7888
  return this;
7326
7889
  }
7327
7890
  get names() {
@@ -7343,9 +7906,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7343
7906
  render(opts) {
7344
7907
  return `for(${this.iteration})` + super.render(opts);
7345
7908
  }
7346
- optimizeNames(names, constants) {
7347
- if (!super.optimizeNames(names, constants)) return;
7348
- this.iteration = optimizeExpr(this.iteration, names, constants);
7909
+ optimizeNames(names, constants2) {
7910
+ if (!super.optimizeNames(names, constants2)) return;
7911
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
7349
7912
  return this;
7350
7913
  }
7351
7914
  get names() {
@@ -7380,9 +7943,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7380
7943
  render(opts) {
7381
7944
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
7382
7945
  }
7383
- optimizeNames(names, constants) {
7384
- if (!super.optimizeNames(names, constants)) return;
7385
- this.iterable = optimizeExpr(this.iterable, names, constants);
7946
+ optimizeNames(names, constants2) {
7947
+ if (!super.optimizeNames(names, constants2)) return;
7948
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
7386
7949
  return this;
7387
7950
  }
7388
7951
  get names() {
@@ -7421,11 +7984,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7421
7984
  (_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
7422
7985
  return this;
7423
7986
  }
7424
- optimizeNames(names, constants) {
7987
+ optimizeNames(names, constants2) {
7425
7988
  var _a, _b;
7426
- super.optimizeNames(names, constants);
7427
- (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants);
7428
- (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants);
7989
+ super.optimizeNames(names, constants2);
7990
+ (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants2);
7991
+ (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants2);
7429
7992
  return this;
7430
7993
  }
7431
7994
  get names() {
@@ -7674,7 +8237,7 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7674
8237
  function addExprNames(names, from) {
7675
8238
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
7676
8239
  }
7677
- function optimizeExpr(expr, names, constants) {
8240
+ function optimizeExpr(expr, names, constants2) {
7678
8241
  if (expr instanceof code_1.Name) return replaceName(expr);
7679
8242
  if (!canOptimize(expr)) return expr;
7680
8243
  return new code_1._Code(expr._items.reduce((items, c) => {
@@ -7684,13 +8247,13 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7684
8247
  return items;
7685
8248
  }, []));
7686
8249
  function replaceName(n) {
7687
- const c = constants[n.str];
8250
+ const c = constants2[n.str];
7688
8251
  if (c === void 0 || names[n.str] !== 1) return n;
7689
8252
  delete names[n.str];
7690
8253
  return c;
7691
8254
  }
7692
8255
  function canOptimize(e) {
7693
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
8256
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
7694
8257
  }
7695
8258
  }
7696
8259
  function subtractNames(names, from) {
@@ -18725,8 +19288,8 @@ var GatewayMcpOAuthProvider = class {
18725
19288
  }
18726
19289
  get redirectUrl() {
18727
19290
  if (this.opts.redirectUri !== void 0) return this.opts.redirectUri;
18728
- const origin = this.opts.origin ?? "https://tool-bridge.invalid";
18729
- return `${origin}${OAUTH_CALLBACK_PATH}`;
19291
+ const origin2 = this.opts.origin ?? "https://tool-bridge.invalid";
19292
+ return `${origin2}${OAUTH_CALLBACK_PATH}`;
18730
19293
  }
18731
19294
  get clientMetadata() {
18732
19295
  return {
@@ -19608,7 +20171,7 @@ async function assertToolConfig(config, deps, ctx, mountPath) {
19608
20171
  }
19609
20172
 
19610
20173
  // ../app/src/refToken.ts
19611
- async function hmacKey(secret) {
20174
+ async function hmacKey2(secret) {
19612
20175
  const digest = await crypto.subtle.digest(
19613
20176
  "SHA-256",
19614
20177
  new TextEncoder().encode(`tb-ref-token:${secret}`)
@@ -19622,7 +20185,7 @@ async function signRefToken(payload, secret) {
19622
20185
  const body = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
19623
20186
  const mac = await crypto.subtle.sign(
19624
20187
  "HMAC",
19625
- await hmacKey(secret),
20188
+ await hmacKey2(secret),
19626
20189
  new TextEncoder().encode(body)
19627
20190
  );
19628
20191
  return `${body}.${base64urlEncode(new Uint8Array(mac))}`;
@@ -19639,7 +20202,7 @@ async function verifyRefToken(token, secret) {
19639
20202
  }
19640
20203
  const ok = await crypto.subtle.verify(
19641
20204
  "HMAC",
19642
- await hmacKey(secret),
20205
+ await hmacKey2(secret),
19643
20206
  // Node 的 WebCrypto 类型要求 Uint8Array<ArrayBuffer>;base64urlDecode 的产物满足但类型面是 ArrayBufferLike。
19644
20207
  sig,
19645
20208
  new TextEncoder().encode(body)
@@ -19750,11 +20313,11 @@ async function contextProviderFor(node, cfg, deps, requestUrl) {
19750
20313
  if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
19751
20314
  const encKey = deps.encryptionKey;
19752
20315
  if (encKey !== void 0) {
19753
- const origin = new URL(requestUrl).origin;
20316
+ const origin2 = new URL(requestUrl).origin;
19754
20317
  const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
19755
20318
  opts.relayRefUrl = async (key) => {
19756
20319
  const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
19757
- return `${origin}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
20320
+ return `${origin2}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
19758
20321
  };
19759
20322
  }
19760
20323
  return createObjectContextProvider(objects, opts);
@@ -19775,11 +20338,11 @@ async function skillhubProviderFor(node, cfg, deps, requestUrl) {
19775
20338
  if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
19776
20339
  const encKey = deps.encryptionKey;
19777
20340
  if (encKey !== void 0) {
19778
- const origin = new URL(requestUrl).origin;
20341
+ const origin2 = new URL(requestUrl).origin;
19779
20342
  const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
19780
20343
  opts.relayRefUrl = async (key) => {
19781
20344
  const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
19782
- return `${origin}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
20345
+ return `${origin2}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
19783
20346
  };
19784
20347
  }
19785
20348
  return createSkillhubProvider(objects, opts);
@@ -23282,8 +23845,8 @@ var LADDER_ERROR_HTTP_STATUS2 = {
23282
23845
  [ProtocolErrorCode2.MissingRequiredClientCapability]: 400,
23283
23846
  [HEADER_MISMATCH_ERROR_CODE2]: 400
23284
23847
  };
23285
- function httpStatusForErrorCode(code, origin) {
23286
- if (origin === "in-band") return code === ProtocolErrorCode2.MissingRequiredClientCapability ? 400 : 200;
23848
+ function httpStatusForErrorCode(code, origin2) {
23849
+ if (origin2 === "in-band") return code === ProtocolErrorCode2.MissingRequiredClientCapability ? 400 : 200;
23287
23850
  return LADDER_ERROR_HTTP_STATUS2[code] ?? 400;
23288
23851
  }
23289
23852
  function rejection(rung, cell, httpStatus, error, settled) {
@@ -25335,9 +25898,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25335
25898
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
25336
25899
  return `${varKind} ${this.name}${rhs};` + _n;
25337
25900
  }
25338
- optimizeNames(names, constants) {
25901
+ optimizeNames(names, constants2) {
25339
25902
  if (!names[this.name.str]) return;
25340
- if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants);
25903
+ if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
25341
25904
  return this;
25342
25905
  }
25343
25906
  get names() {
@@ -25354,9 +25917,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25354
25917
  render({ _n }) {
25355
25918
  return `${this.lhs} = ${this.rhs};` + _n;
25356
25919
  }
25357
- optimizeNames(names, constants) {
25920
+ optimizeNames(names, constants2) {
25358
25921
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
25359
- this.rhs = optimizeExpr(this.rhs, names, constants);
25922
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
25360
25923
  return this;
25361
25924
  }
25362
25925
  get names() {
@@ -25415,8 +25978,8 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25415
25978
  optimizeNodes() {
25416
25979
  return `${this.code}` ? this : void 0;
25417
25980
  }
25418
- optimizeNames(names, constants) {
25419
- this.code = optimizeExpr(this.code, names, constants);
25981
+ optimizeNames(names, constants2) {
25982
+ this.code = optimizeExpr(this.code, names, constants2);
25420
25983
  return this;
25421
25984
  }
25422
25985
  get names() {
@@ -25442,12 +26005,12 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25442
26005
  }
25443
26006
  return nodes.length > 0 ? this : void 0;
25444
26007
  }
25445
- optimizeNames(names, constants) {
26008
+ optimizeNames(names, constants2) {
25446
26009
  const { nodes } = this;
25447
26010
  let i = nodes.length;
25448
26011
  while (i--) {
25449
26012
  const n = nodes[i];
25450
- if (n.optimizeNames(names, constants)) continue;
26013
+ if (n.optimizeNames(names, constants2)) continue;
25451
26014
  subtractNames(names, n.names);
25452
26015
  nodes.splice(i, 1);
25453
26016
  }
@@ -25494,11 +26057,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25494
26057
  if (cond === false || !this.nodes.length) return void 0;
25495
26058
  return this;
25496
26059
  }
25497
- optimizeNames(names, constants) {
26060
+ optimizeNames(names, constants2) {
25498
26061
  var _a;
25499
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
25500
- if (!(super.optimizeNames(names, constants) || this.else)) return;
25501
- this.condition = optimizeExpr(this.condition, names, constants);
26062
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
26063
+ if (!(super.optimizeNames(names, constants2) || this.else)) return;
26064
+ this.condition = optimizeExpr(this.condition, names, constants2);
25502
26065
  return this;
25503
26066
  }
25504
26067
  get names() {
@@ -25520,9 +26083,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25520
26083
  render(opts) {
25521
26084
  return `for(${this.iteration})` + super.render(opts);
25522
26085
  }
25523
- optimizeNames(names, constants) {
25524
- if (!super.optimizeNames(names, constants)) return;
25525
- this.iteration = optimizeExpr(this.iteration, names, constants);
26086
+ optimizeNames(names, constants2) {
26087
+ if (!super.optimizeNames(names, constants2)) return;
26088
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
25526
26089
  return this;
25527
26090
  }
25528
26091
  get names() {
@@ -25557,9 +26120,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25557
26120
  render(opts) {
25558
26121
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
25559
26122
  }
25560
- optimizeNames(names, constants) {
25561
- if (!super.optimizeNames(names, constants)) return;
25562
- this.iterable = optimizeExpr(this.iterable, names, constants);
26123
+ optimizeNames(names, constants2) {
26124
+ if (!super.optimizeNames(names, constants2)) return;
26125
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
25563
26126
  return this;
25564
26127
  }
25565
26128
  get names() {
@@ -25598,11 +26161,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25598
26161
  (_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
25599
26162
  return this;
25600
26163
  }
25601
- optimizeNames(names, constants) {
26164
+ optimizeNames(names, constants2) {
25602
26165
  var _a, _b;
25603
- super.optimizeNames(names, constants);
25604
- (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants);
25605
- (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants);
26166
+ super.optimizeNames(names, constants2);
26167
+ (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants2);
26168
+ (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants2);
25606
26169
  return this;
25607
26170
  }
25608
26171
  get names() {
@@ -25851,7 +26414,7 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25851
26414
  function addExprNames(names, from) {
25852
26415
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
25853
26416
  }
25854
- function optimizeExpr(expr, names, constants) {
26417
+ function optimizeExpr(expr, names, constants2) {
25855
26418
  if (expr instanceof code_1.Name) return replaceName(expr);
25856
26419
  if (!canOptimize(expr)) return expr;
25857
26420
  return new code_1._Code(expr._items.reduce((items, c) => {
@@ -25861,13 +26424,13 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25861
26424
  return items;
25862
26425
  }, []));
25863
26426
  function replaceName(n) {
25864
- const c = constants[n.str];
26427
+ const c = constants2[n.str];
25865
26428
  if (c === void 0 || names[n.str] !== 1) return n;
25866
26429
  delete names[n.str];
25867
26430
  return c;
25868
26431
  }
25869
26432
  function canOptimize(e) {
25870
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
26433
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
25871
26434
  }
25872
26435
  }
25873
26436
  function subtractNames(names, from) {
@@ -32091,9 +32654,9 @@ function createListenRouter(options) {
32091
32654
  const { bus, onerror } = options;
32092
32655
  const maxSubscriptions = options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS;
32093
32656
  const keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
32094
- const open = /* @__PURE__ */ new Set();
32657
+ const open2 = /* @__PURE__ */ new Set();
32095
32658
  function serve2(message, signal, capabilities, serverInfo) {
32096
- if (open.size >= maxSubscriptions) {
32659
+ if (open2.size >= maxSubscriptions) {
32097
32660
  onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
32098
32661
  return jsonRpcError(message.id, -32603, "Subscription limit reached");
32099
32662
  }
@@ -32149,7 +32712,7 @@ data: ${JSON.stringify({
32149
32712
  }
32150
32713
  if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
32151
32714
  abortCleanup?.();
32152
- open.delete(teardown);
32715
+ open2.delete(teardown);
32153
32716
  try {
32154
32717
  controller.close();
32155
32718
  } catch {
@@ -32169,7 +32732,7 @@ data: ${JSON.stringify({
32169
32732
  writeNotification(note.method, note.params);
32170
32733
  });
32171
32734
  keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
32172
- open.add(teardown);
32735
+ open2.add(teardown);
32173
32736
  },
32174
32737
  cancel() {
32175
32738
  teardown(false);
@@ -32194,10 +32757,10 @@ data: ${JSON.stringify({
32194
32757
  return {
32195
32758
  serve: serve2,
32196
32759
  closeAll() {
32197
- for (const teardown of open) teardown(true);
32760
+ for (const teardown of open2) teardown(true);
32198
32761
  },
32199
32762
  get openCount() {
32200
- return open.size;
32763
+ return open2.size;
32201
32764
  }
32202
32765
  };
32203
32766
  }
@@ -36538,6 +37101,147 @@ async function handleDescribe(c, env) {
36538
37101
  throw TBError.notFound(`no capabilities for kind '${node.kind}'`);
36539
37102
  }
36540
37103
 
37104
+ // ../app/src/routes/store.ts
37105
+ var SECRET_BODY_FIELDS = [
37106
+ "callCapability",
37107
+ "capability",
37108
+ "uploadToken",
37109
+ "shareToken"
37110
+ ];
37111
+ function hideCapabilityFailure(error) {
37112
+ if (isTBError(error) && error.code !== "permission_denied" && error.code !== "not_found") throw error;
37113
+ throw TBError.notFound("not found");
37114
+ }
37115
+ async function jsonObject(c) {
37116
+ const parsed = await c.req.json().catch(() => null);
37117
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
37118
+ throw new TBError("invalid_argument", "body must be a JSON object");
37119
+ }
37120
+ const body = parsed;
37121
+ for (const field of SECRET_BODY_FIELDS) {
37122
+ if (field in body) {
37123
+ throw new TBError(
37124
+ "invalid_argument",
37125
+ `${field} is not accepted in the request body; use the dedicated capability header`
37126
+ );
37127
+ }
37128
+ }
37129
+ return body;
37130
+ }
37131
+ function requiredHeader(c, name) {
37132
+ const value = c.req.header(name);
37133
+ if (value === void 0 || value.trim() === "") {
37134
+ throw new TBError("permission_denied", "Store capability is missing or invalid");
37135
+ }
37136
+ return value;
37137
+ }
37138
+ function uploadId(body) {
37139
+ if (typeof body.uploadId !== "string" || body.uploadId.trim() === "") {
37140
+ throw new TBError("invalid_argument", "uploadId must be a non-empty string");
37141
+ }
37142
+ return body.uploadId;
37143
+ }
37144
+ function origin(c, canonicalOrigin) {
37145
+ let url2;
37146
+ try {
37147
+ url2 = new URL(canonicalOrigin ?? c.req.url);
37148
+ } catch {
37149
+ throw new TBError("unavailable", "request origin is invalid", { retryable: false });
37150
+ }
37151
+ if (url2.protocol !== "https:" && url2.protocol !== "http:" || url2.username !== "" || url2.password !== "") throw new TBError("unavailable", "request origin is invalid", { retryable: false });
37152
+ return url2.origin;
37153
+ }
37154
+ function registerStoreCapabilityRoutes(app, deps) {
37155
+ app.post("/system/store/create_upload", async (c, next) => {
37156
+ if (c.req.header(STORE_CALL_CAPABILITY_HEADER) === void 0) return await next();
37157
+ return await runHandler(async () => {
37158
+ await deps.ensureReady?.();
37159
+ const body = await jsonObject(c);
37160
+ const capability = requiredHeader(c, STORE_CALL_CAPABILITY_HEADER);
37161
+ const result = await beginCapabilityUpload(
37162
+ deps,
37163
+ body,
37164
+ capability,
37165
+ origin(c, deps.canonicalOrigin)
37166
+ );
37167
+ return c.json(result);
37168
+ });
37169
+ });
37170
+ for (const command of ["complete_upload", "abort_upload"]) {
37171
+ app.post(`/system/store/${command}`, async (c, next) => {
37172
+ if (c.req.header(STORE_UPLOAD_HEADER) === void 0) return await next();
37173
+ return await runHandler(async () => {
37174
+ await deps.ensureReady?.();
37175
+ const body = await jsonObject(c);
37176
+ const token = requiredHeader(c, STORE_UPLOAD_HEADER);
37177
+ const result = command === "complete_upload" ? await completeCapabilityUpload(deps, uploadId(body), token) : await abortCapabilityUpload(deps, uploadId(body), token);
37178
+ return c.json(result);
37179
+ });
37180
+ });
37181
+ }
37182
+ app.put(
37183
+ "/~store/uploads/:uploadId",
37184
+ (c) => runHandler(async () => {
37185
+ await deps.ensureReady?.();
37186
+ const descriptor = await relayStoreUpload(
37187
+ deps,
37188
+ c.req.param("uploadId"),
37189
+ requiredHeader(c, STORE_UPLOAD_HEADER),
37190
+ c.req.raw.body
37191
+ );
37192
+ return c.json(descriptor);
37193
+ })
37194
+ );
37195
+ app.get(
37196
+ "/~store/refs/:token",
37197
+ (c) => runHandler(async () => {
37198
+ await deps.ensureReady?.();
37199
+ const store = await defaultStoreRuntime(deps);
37200
+ const payload = await verifyStoreRefToken(c.req.param("token"), store.tokenSecret);
37201
+ if (payload === null || payload.exp * 1e3 <= Date.now()) {
37202
+ throw TBError.notFound("not found");
37203
+ }
37204
+ let object2;
37205
+ try {
37206
+ object2 = await store.service.authorizeRead(storeUri(payload.objectId), {
37207
+ admin: true
37208
+ });
37209
+ } catch (error) {
37210
+ hideCapabilityFailure(error);
37211
+ }
37212
+ return await storeObjectResponse(store.objects, object2);
37213
+ })
37214
+ );
37215
+ app.get(
37216
+ "/~store/shares/:token",
37217
+ (c) => runHandler(async () => {
37218
+ await deps.ensureReady?.();
37219
+ const token = c.req.param("token");
37220
+ const store = await defaultStoreRuntime(deps);
37221
+ let object2;
37222
+ try {
37223
+ const grant = await store.service.verifyShareToken(token);
37224
+ const uri3 = storeUri(grant.objectId);
37225
+ object2 = await store.service.authorizeSharedRead(uri3, token);
37226
+ } catch (error) {
37227
+ hideCapabilityFailure(error);
37228
+ }
37229
+ return await storeObjectResponse(store.objects, object2);
37230
+ })
37231
+ );
37232
+ }
37233
+ function rejectStoreCapabilityBodyFields(module, args) {
37234
+ if (module !== "store") return;
37235
+ for (const field of SECRET_BODY_FIELDS) {
37236
+ if (field in args) {
37237
+ throw new TBError(
37238
+ "invalid_argument",
37239
+ `${field} is not accepted in the request body; use the dedicated capability header`
37240
+ );
37241
+ }
37242
+ }
37243
+ }
37244
+
36541
37245
  // ../app/src/routes/publicRoutes.ts
36542
37246
  async function finishToolAuthorization(opts) {
36543
37247
  const config = opts.node.config;
@@ -37479,6 +38183,7 @@ async function handleInvoke(c, env) {
37479
38183
  if (!mod) throw TBError.unimplemented(`builtin module '${node.config.module}' not available`);
37480
38184
  const args = await readInvokeBody();
37481
38185
  const cmd = command;
38186
+ rejectStoreCapabilityBodyFields(node.config.module, args);
37482
38187
  const spec = scopeForCmd(mod, node.path, cmd);
37483
38188
  if (!spec) throw new TBError("invalid_argument", `unknown cmd '${cmd}' on '${node.path}'`);
37484
38189
  if (!check(ctx, node.path, spec.scope).allow) {
@@ -37522,7 +38227,9 @@ async function handleInvoke(c, env) {
37522
38227
  const registryMarker = registryTarget === void 0 ? void 0 : await searchSync?.markNode(registryTarget);
37523
38228
  let result;
37524
38229
  try {
37525
- result = await mod.dispatch(cmd, args, ctx);
38230
+ result = await mod.dispatch(cmd, args, ctx, {
38231
+ requestOrigin: deps.canonicalOrigin ?? new URL(c.req.url).origin
38232
+ });
37526
38233
  } catch (error) {
37527
38234
  await searchSync?.abort(registryMarker);
37528
38235
  await Promise.all(pluginMounts.map(async (mount) => await searchSync?.abort(mount.marker)));
@@ -37573,17 +38280,21 @@ async function handleInvoke(c, env) {
37573
38280
  // ../app/src/routes/env.ts
37574
38281
  function createRouteEnv(deps) {
37575
38282
  const searchSync = isMutableSearchIndex(deps.search) ? new SearchSynchronizer(deps.state, deps.search) : void 0;
37576
- const builtinsOf = (store) => createBuiltins(
37577
- buildDeps({
37578
- store,
37579
- secrets: deps.secrets,
37580
- version: deps.version,
37581
- allowInsecureHttp: deps.allowInsecureHttp,
37582
- remoteAllowlistBase: deps.remote.allowlist,
37583
- ...deps.pluginBindings !== void 0 ? { pluginBindings: deps.pluginBindings } : {},
37584
- ...deps.pluginCatalog !== void 0 ? { pluginCatalog: deps.pluginCatalog } : {}
37585
- })
37586
- );
38283
+ const builtinsOf = (store) => {
38284
+ const builtins = createBuiltins(
38285
+ buildDeps({
38286
+ store,
38287
+ secrets: deps.secrets,
38288
+ version: deps.version,
38289
+ allowInsecureHttp: deps.allowInsecureHttp,
38290
+ remoteAllowlistBase: deps.remote.allowlist,
38291
+ ...deps.pluginBindings !== void 0 ? { pluginBindings: deps.pluginBindings } : {},
38292
+ ...deps.pluginCatalog !== void 0 ? { pluginCatalog: deps.pluginCatalog } : {}
38293
+ })
38294
+ );
38295
+ builtins.set("store", lazyDefaultStoreModule(deps));
38296
+ return builtins;
38297
+ };
37587
38298
  const globalSearchCapabilities = () => {
37588
38299
  const declared = new Set(deps.search?.capabilities ?? []);
37589
38300
  if (!declared.has("search")) return [];
@@ -37724,6 +38435,7 @@ function createTbApp(deps) {
37724
38435
  if (secured !== response) c.res = secured;
37725
38436
  });
37726
38437
  registerPublicRoutes(app, env);
38438
+ registerStoreCapabilityRoutes(app, deps);
37727
38439
  app.use("*", async (c, next) => {
37728
38440
  const store = deps.state;
37729
38441
  try {
@@ -38250,6 +38962,19 @@ var DeviceRouter = class _DeviceRouter {
38250
38962
  }
38251
38963
  };
38252
38964
 
38965
+ // src/objects.ts
38966
+ import {
38967
+ link,
38968
+ mkdir as mkdir2,
38969
+ open,
38970
+ readdir as readdir2,
38971
+ realpath as realpath2,
38972
+ rename,
38973
+ rm as rm2,
38974
+ stat as stat2
38975
+ } from "fs/promises";
38976
+ import { constants, mkdirSync } from "fs";
38977
+
38253
38978
  // ../core/src/node/fsObjectStore.ts
38254
38979
  import { mkdir, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
38255
38980
  import { basename, dirname, join, resolve, sep } from "path";
@@ -38349,6 +39074,9 @@ var FsObjectStore = class {
38349
39074
  throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':\u672A\u77E5\u6839\u6216\u7F3A\u76F8\u5BF9\u8DEF\u5F84`);
38350
39075
  }
38351
39076
  await this.assertInRoot(r.root, r.full, key);
39077
+ if (opts?.ifMatchEtag !== void 0 && opts.ifNoneMatch !== void 0) {
39078
+ throw new TBError("invalid_argument", "ifMatchEtag \u4E0E ifNoneMatch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
39079
+ }
38352
39080
  if (opts?.ifMatchEtag !== void 0) {
38353
39081
  const existing = await this.head(key);
38354
39082
  if (opts.ifMatchEtag !== existing?.etag) {
@@ -38357,7 +39085,14 @@ var FsObjectStore = class {
38357
39085
  }
38358
39086
  const bytes = await objectBodyToBytes(body);
38359
39087
  await mkdir(dirname(r.full), { recursive: true });
38360
- await writeFile(r.full, bytes);
39088
+ try {
39089
+ await writeFile(r.full, bytes, opts?.ifNoneMatch === "*" ? { flag: "wx" } : void 0);
39090
+ } catch (e) {
39091
+ if (opts?.ifNoneMatch === "*" && isErrnoCode(e, "EEXIST")) {
39092
+ throw new TBError("conflict", `\u5BF9\u8C61\u5DF2\u5B58\u5728:'${key}'`);
39093
+ }
39094
+ throw e;
39095
+ }
38361
39096
  const st = await stat(r.full);
38362
39097
  return this.metaOf(r.key, st);
38363
39098
  }
@@ -38491,9 +39226,14 @@ import { spawn as nodeSpawn } from "child_process";
38491
39226
  var SHELL_OUTPUT_LIMIT_BYTES = 1024 * 1024;
38492
39227
 
38493
39228
  // src/objects.ts
38494
- import { mkdirSync } from "fs";
38495
- import { join as join2 } from "path";
39229
+ import { dirname as dirname2, join as join2, sep as sep2 } from "path";
39230
+ import { randomUUID } from "crypto";
39231
+ import { Readable } from "stream";
38496
39232
  var INTERNAL_ROOT = "objects";
39233
+ var TEMP_PREFIX = ".tb-object-upload-";
39234
+ var STORE_KEY_PREFIX = `${DEFAULT_STORE_DRIVER_KEY_ROOT}/`;
39235
+ var STORE_STAGING_DIR = ".tb-store-staging-v1";
39236
+ var UPLOAD_TEMP_NAME_RE = /^\.tb-object-upload-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.tmp$/i;
38497
39237
  function toInternal(key) {
38498
39238
  return `${INTERNAL_ROOT}/${key}`;
38499
39239
  }
@@ -38503,6 +39243,108 @@ function toExternal(key) {
38503
39243
  function externalMeta(meta) {
38504
39244
  return { ...meta, key: toExternal(meta.key) };
38505
39245
  }
39246
+ function isErrnoCode2(error, code) {
39247
+ return typeof error === "object" && error !== null && error.code === code;
39248
+ }
39249
+ function isUploadTempKey(key) {
39250
+ return key.split("/").some((segment) => segment.startsWith(TEMP_PREFIX));
39251
+ }
39252
+ async function writeChunk(handle, chunk) {
39253
+ let offset = 0;
39254
+ while (offset < chunk.byteLength) {
39255
+ const { bytesWritten } = await handle.write(chunk, offset, chunk.byteLength - offset, null);
39256
+ if (bytesWritten === 0) throw new Error("\u5BF9\u8C61\u4E34\u65F6\u6587\u4EF6\u5199\u5165\u672A\u53D6\u5F97\u8FDB\u5C55");
39257
+ offset += bytesWritten;
39258
+ }
39259
+ }
39260
+ async function writeStream(handle, stream) {
39261
+ const reader = stream.getReader();
39262
+ try {
39263
+ for (; ; ) {
39264
+ const { done, value } = await reader.read();
39265
+ if (done) break;
39266
+ if (value !== void 0 && value.byteLength > 0) await writeChunk(handle, value);
39267
+ }
39268
+ } catch (error) {
39269
+ try {
39270
+ await reader.cancel?.(error);
39271
+ } catch {
39272
+ }
39273
+ throw error;
39274
+ } finally {
39275
+ reader.releaseLock();
39276
+ }
39277
+ }
39278
+ async function writeBody(handle, body) {
39279
+ if (typeof body === "string") {
39280
+ await writeChunk(handle, Buffer.from(body));
39281
+ return;
39282
+ }
39283
+ if (body instanceof Uint8Array) {
39284
+ await writeChunk(handle, body);
39285
+ return;
39286
+ }
39287
+ if (body instanceof ArrayBuffer) {
39288
+ await writeChunk(handle, new Uint8Array(body));
39289
+ return;
39290
+ }
39291
+ await writeStream(handle, body);
39292
+ }
39293
+ async function assertParentInRoot(root, full, key) {
39294
+ const rootReal = await realpath2(root);
39295
+ const parentReal = await realpath2(dirname2(full));
39296
+ if (parentReal !== rootReal && !parentReal.startsWith(rootReal + sep2)) {
39297
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39298
+ }
39299
+ }
39300
+ async function assertAncestorInRoot(root, full, key) {
39301
+ const rootReal = await realpath2(root);
39302
+ let probe = dirname2(full);
39303
+ for (; ; ) {
39304
+ try {
39305
+ const ancestorReal = await realpath2(probe);
39306
+ if (ancestorReal !== rootReal && !ancestorReal.startsWith(rootReal + sep2)) {
39307
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39308
+ }
39309
+ return;
39310
+ } catch (error) {
39311
+ if (error instanceof TBError) throw error;
39312
+ if (!isErrnoCode2(error, "ENOENT")) throw error;
39313
+ const parent = dirname2(probe);
39314
+ if (parent === probe) {
39315
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39316
+ }
39317
+ probe = parent;
39318
+ }
39319
+ }
39320
+ }
39321
+ async function cleanupStagingTree(dir, cutoffMs) {
39322
+ let entries;
39323
+ try {
39324
+ entries = await readdir2(dir, { withFileTypes: true });
39325
+ } catch (error) {
39326
+ if (isErrnoCode2(error, "ENOENT")) return 0;
39327
+ throw error;
39328
+ }
39329
+ let deleted = 0;
39330
+ for (const entry of entries) {
39331
+ const path = join2(dir, entry.name);
39332
+ if (entry.isDirectory()) {
39333
+ deleted += await cleanupStagingTree(path, cutoffMs);
39334
+ continue;
39335
+ }
39336
+ if (!entry.isFile() || !UPLOAD_TEMP_NAME_RE.test(entry.name)) continue;
39337
+ try {
39338
+ const info = await stat2(path);
39339
+ if (info.mtimeMs > cutoffMs) continue;
39340
+ await rm2(path);
39341
+ deleted++;
39342
+ } catch (error) {
39343
+ if (!isErrnoCode2(error, "ENOENT")) throw error;
39344
+ }
39345
+ }
39346
+ return deleted;
39347
+ }
38506
39348
  function createDataObjectStore(dataDir) {
38507
39349
  const root = join2(dataDir, INTERNAL_ROOT);
38508
39350
  mkdirSync(root, { recursive: true });
@@ -38513,11 +39355,77 @@ function createDataObjectStore(dataDir) {
38513
39355
  return meta === null ? null : externalMeta(meta);
38514
39356
  },
38515
39357
  async get(key) {
38516
- const got = await fs.get(toInternal(key));
38517
- return got === null ? null : { meta: externalMeta(got.meta), body: got.body };
39358
+ const normalizedKey = normalizeEntryPath(key);
39359
+ const meta = await fs.head(toInternal(normalizedKey));
39360
+ if (meta === null) return null;
39361
+ const finalPath = join2(root, ...normalizedKey.split("/"));
39362
+ let handle;
39363
+ try {
39364
+ handle = await open(finalPath, constants.O_RDONLY | constants.O_NOFOLLOW);
39365
+ } catch (error) {
39366
+ if (isErrnoCode2(error, "ENOENT")) return null;
39367
+ if (isErrnoCode2(error, "ELOOP")) {
39368
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39369
+ }
39370
+ throw error;
39371
+ }
39372
+ const body = Readable.toWeb(handle.createReadStream());
39373
+ return { meta: externalMeta(meta), body };
38518
39374
  },
38519
39375
  async put(key, body, opts) {
38520
- return externalMeta(await fs.put(toInternal(key), body, opts));
39376
+ const normalizedKey = normalizeEntryPath(key);
39377
+ const internalKey = toInternal(normalizedKey);
39378
+ const finalPath = join2(root, ...normalizedKey.split("/"));
39379
+ const existing = await fs.head(internalKey);
39380
+ if (opts?.ifMatchEtag !== void 0 && opts.ifNoneMatch !== void 0) {
39381
+ throw new TBError("invalid_argument", "ifMatchEtag \u4E0E ifNoneMatch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
39382
+ }
39383
+ if (opts?.ifMatchEtag !== void 0) {
39384
+ if (opts.ifMatchEtag !== existing?.etag) {
39385
+ throw new TBError("conflict", `etag \u4E0D\u5339\u914D:'${key}'`);
39386
+ }
39387
+ }
39388
+ await assertAncestorInRoot(root, finalPath, key);
39389
+ await mkdir2(dirname2(finalPath), { recursive: true });
39390
+ await assertParentInRoot(root, finalPath, key);
39391
+ const tempDir = normalizedKey.startsWith(STORE_KEY_PREFIX) ? join2(root, STORE_STAGING_DIR) : dirname2(finalPath);
39392
+ await assertAncestorInRoot(root, join2(tempDir, "staging-probe"), key);
39393
+ await mkdir2(tempDir, { recursive: true });
39394
+ const tempPath = join2(tempDir, `${TEMP_PREFIX}${randomUUID()}.tmp`);
39395
+ await assertParentInRoot(root, tempPath, key);
39396
+ let handle;
39397
+ try {
39398
+ handle = await open(tempPath, "wx");
39399
+ await writeBody(handle, body);
39400
+ await handle.sync();
39401
+ await handle.close();
39402
+ handle = void 0;
39403
+ if (opts?.ifNoneMatch === "*") {
39404
+ try {
39405
+ await link(tempPath, finalPath);
39406
+ } catch (error) {
39407
+ if (isErrnoCode2(error, "EEXIST")) {
39408
+ throw new TBError("conflict", `\u5BF9\u8C61\u5DF2\u5B58\u5728:'${key}'`);
39409
+ }
39410
+ throw error;
39411
+ }
39412
+ await rm2(tempPath);
39413
+ } else {
39414
+ await rename(tempPath, finalPath);
39415
+ }
39416
+ } catch (error) {
39417
+ if (handle !== void 0) {
39418
+ try {
39419
+ await handle.close();
39420
+ } catch {
39421
+ }
39422
+ }
39423
+ await rm2(tempPath, { force: true });
39424
+ throw error;
39425
+ }
39426
+ const meta = await fs.head(internalKey);
39427
+ if (meta === null) throw new TBError("unavailable", `\u5BF9\u8C61\u63D0\u4EA4\u540E\u4E0D\u53EF\u89C1:'${key}'`);
39428
+ return externalMeta(meta);
38521
39429
  },
38522
39430
  async delete(key) {
38523
39431
  await fs.delete(toInternal(key));
@@ -38527,9 +39435,24 @@ function createDataObjectStore(dataDir) {
38527
39435
  return {
38528
39436
  items: result.items.map(
38529
39437
  (item) => "prefix" in item ? { prefix: toExternal(item.prefix) } : externalMeta(item)
38530
- ),
39438
+ ).filter((item) => !isUploadTempKey("prefix" in item ? item.prefix : item.key)),
38531
39439
  ...result.cursor !== void 0 ? { cursor: result.cursor } : {}
38532
39440
  };
39441
+ },
39442
+ async cleanupStaging(prefix, olderThan) {
39443
+ const cutoffMs = Date.parse(olderThan);
39444
+ if (!Number.isFinite(cutoffMs)) {
39445
+ throw new TBError("invalid_argument", "cleanupStaging olderThan \u5FC5\u987B\u662F ISO timestamp");
39446
+ }
39447
+ const normalizedPrefix = normalizeEntryPath(prefix.replace(/\/+$/, ""));
39448
+ const isDefaultStore = normalizedPrefix === STORE_KEY_PREFIX.slice(0, -1);
39449
+ const prefixDir = isDefaultStore ? join2(root, STORE_STAGING_DIR) : join2(root, ...normalizedPrefix.split("/"));
39450
+ await assertAncestorInRoot(root, join2(prefixDir, "staging-probe"), prefix);
39451
+ if (isDefaultStore) {
39452
+ await mkdir2(prefixDir, { recursive: true });
39453
+ await assertParentInRoot(root, join2(prefixDir, "staging-probe"), prefix);
39454
+ }
39455
+ return await cleanupStagingTree(prefixDir, cutoffMs);
38533
39456
  }
38534
39457
  };
38535
39458
  }
@@ -38652,6 +39575,29 @@ var PgStateStore = class {
38652
39575
  ON CONFLICT (key) DO UPDATE SET value = excluded.value
38653
39576
  `;
38654
39577
  }
39578
+ async compareAndSwap(key, expectedRevision, value) {
39579
+ if (expectedRevision === null) {
39580
+ if (value === null) return false;
39581
+ const result2 = await this.sql`
39582
+ INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
39583
+ ON CONFLICT (key) DO NOTHING
39584
+ `;
39585
+ return result2.count > 0;
39586
+ }
39587
+ if (!Number.isInteger(expectedRevision) || expectedRevision < 0) return false;
39588
+ const predicate = this.sql`
39589
+ key = ${key}
39590
+ AND jsonb_typeof(value) = 'object'
39591
+ AND jsonb_typeof(value -> 'revision') = 'number'
39592
+ AND (value ->> 'revision') ~ '^(0|[1-9][0-9]*)$'
39593
+ AND (value ->> 'revision')::numeric = ${expectedRevision}
39594
+ `;
39595
+ const result = value === null ? await this.sql`DELETE FROM tb_kv WHERE ${predicate}` : await this.sql`
39596
+ UPDATE tb_kv SET value = ${this.sql.json(value)}
39597
+ WHERE ${predicate}
39598
+ `;
39599
+ return result.count > 0;
39600
+ }
38655
39601
  async putIfAbsent(key, value) {
38656
39602
  const result = await this.sql`
38657
39603
  INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
@@ -38834,6 +39780,20 @@ var SqliteStateStore = class {
38834
39780
  async put(key, value) {
38835
39781
  this.stmtPut.run(key, JSON.stringify(value));
38836
39782
  }
39783
+ async compareAndSwap(key, expectedRevision, value) {
39784
+ if (expectedRevision === null) {
39785
+ if (value === null) return false;
39786
+ const info2 = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
39787
+ return info2.changes > 0;
39788
+ }
39789
+ if (!Number.isInteger(expectedRevision) || expectedRevision < 0) return false;
39790
+ const predicate = `key = ?
39791
+ AND json_type(value) = 'object'
39792
+ AND json_type(value, '$.revision') = 'integer'
39793
+ AND json_extract(value, '$.revision') = ?`;
39794
+ const info = value === null ? this.db.prepare(`DELETE FROM kv WHERE ${predicate}`).run(key, expectedRevision) : this.db.prepare(`UPDATE kv SET value = ? WHERE ${predicate}`).run(JSON.stringify(value), key, expectedRevision);
39795
+ return info.changes > 0;
39796
+ }
38837
39797
  async putIfAbsent(key, value) {
38838
39798
  const info = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
38839
39799
  return info.changes > 0;
@@ -38881,7 +39841,7 @@ import { join as join4 } from "path";
38881
39841
  // package.json
38882
39842
  var package_default = {
38883
39843
  name: "@tool-bridge/server",
38884
- version: "0.15.0",
39844
+ version: "0.16.0",
38885
39845
  description: "tool-bridge self-hosted server: run the HTBP gateway on Node with SQLite or PostgreSQL persistence, filesystem object storage and WebSocket device channel \u2014 single container via docker run",
38886
39846
  type: "module",
38887
39847
  license: "MIT",
@@ -38954,7 +39914,7 @@ var package_default = {
38954
39914
 
38955
39915
  // src/assets.ts
38956
39916
  import { brotliCompressSync, gzipSync, constants as zlibConstants } from "zlib";
38957
- import { dirname as dirname2, join as join3, resolve as resolve2, sep as sep2 } from "path";
39917
+ import { dirname as dirname3, join as join3, resolve as resolve2, sep as sep3 } from "path";
38958
39918
  import { readFile as readFile2 } from "fs/promises";
38959
39919
  import { createRequire } from "module";
38960
39920
  import { existsSync } from "fs";
@@ -38987,7 +39947,7 @@ function resolveUiDir(uiDirOverride) {
38987
39947
  try {
38988
39948
  const require2 = createRequire(import.meta.url);
38989
39949
  const pkgPath = require2.resolve("@tool-bridge/dashboard/package.json");
38990
- const dist = join3(dirname2(pkgPath), "dist");
39950
+ const dist = join3(dirname3(pkgPath), "dist");
38991
39951
  return existsSync(join3(dist, "index.html")) ? dist : void 0;
38992
39952
  } catch {
38993
39953
  return void 0;
@@ -39014,7 +39974,7 @@ function uiAssetsFetcher(uiDir) {
39014
39974
  let pathname = decodeURIComponent(url2.pathname);
39015
39975
  if (pathname.endsWith("/")) pathname += "index.html";
39016
39976
  const full = resolve2(join3(root, pathname));
39017
- if (full !== root && !full.startsWith(root + sep2)) {
39977
+ if (full !== root && !full.startsWith(root + sep3)) {
39018
39978
  return new Response("not found", { status: 404 });
39019
39979
  }
39020
39980
  let asset = cache.get(full);
@@ -39196,8 +40156,49 @@ function createTbServer(config) {
39196
40156
  if (config.uploadGrantTtlSec !== void 0) {
39197
40157
  deps.uploadGrantTtlSec = config.uploadGrantTtlSec;
39198
40158
  }
40159
+ if (config.storeTokenSecret !== void 0) deps.storeTokenSecret = config.storeTokenSecret;
40160
+ if (config.storeMaxObjectBytes !== void 0) {
40161
+ deps.storeMaxObjectBytes = config.storeMaxObjectBytes;
40162
+ }
40163
+ if (config.storeRelayMaxBytes !== void 0) {
40164
+ deps.storeRelayMaxBytes = config.storeRelayMaxBytes;
40165
+ }
40166
+ if (config.storeUploadTtlSec !== void 0) deps.storeUploadTtlSec = config.storeUploadTtlSec;
40167
+ if (config.storeShareTtlSec !== void 0) deps.storeShareTtlSec = config.storeShareTtlSec;
40168
+ if (config.storeReadTtlSec !== void 0) deps.storeReadTtlSec = config.storeReadTtlSec;
40169
+ if (config.storeCallMaxBytes !== void 0) deps.storeCallMaxBytes = config.storeCallMaxBytes;
40170
+ if (config.storeCallMaxObjectBytes !== void 0) {
40171
+ deps.storeCallMaxObjectBytes = config.storeCallMaxObjectBytes;
40172
+ }
40173
+ if (config.storeCallMaxObjects !== void 0) {
40174
+ deps.storeCallMaxObjects = config.storeCallMaxObjects;
40175
+ }
40176
+ if (config.storeCallAllowedContentTypes !== void 0) {
40177
+ deps.storeCallAllowedContentTypes = config.storeCallAllowedContentTypes;
40178
+ }
39199
40179
  const app = createTbApp(deps);
39200
40180
  let server;
40181
+ let storeCleanupTimer;
40182
+ let storeCleanupInFlight = false;
40183
+ const runStoreCleanup = async () => {
40184
+ if (storeCleanupInFlight) return;
40185
+ storeCleanupInFlight = true;
40186
+ try {
40187
+ await cleanupDefaultStore(deps);
40188
+ } finally {
40189
+ storeCleanupInFlight = false;
40190
+ }
40191
+ };
40192
+ const reportStoreCleanupFailure = () => {
40193
+ console.warn(JSON.stringify({ event: "tool_bridge_store_cleanup_failed" }));
40194
+ };
40195
+ const scheduleStoreCleanup = () => {
40196
+ void runStoreCleanup().catch(reportStoreCleanupFailure);
40197
+ storeCleanupTimer = setInterval(() => {
40198
+ void runStoreCleanup().catch(reportStoreCleanupFailure);
40199
+ }, config.storeCleanupIntervalSec * 1e3);
40200
+ storeCleanupTimer.unref?.();
40201
+ };
39201
40202
  return {
39202
40203
  app,
39203
40204
  search,
@@ -39214,6 +40215,7 @@ function createTbServer(config) {
39214
40215
  await hub.sweepOrphans();
39215
40216
  return await new Promise((resolve3) => {
39216
40217
  server = serve({ fetch: app.fetch, port: config.port, hostname: config.host }, (info) => {
40218
+ scheduleStoreCleanup();
39217
40219
  resolve3({ port: info.port });
39218
40220
  });
39219
40221
  hub.attach(server);
@@ -39224,6 +40226,10 @@ function createTbServer(config) {
39224
40226
  },
39225
40227
  async close() {
39226
40228
  draining = true;
40229
+ if (storeCleanupTimer !== void 0) {
40230
+ clearInterval(storeCleanupTimer);
40231
+ storeCleanupTimer = void 0;
40232
+ }
39227
40233
  let closed;
39228
40234
  if (server !== void 0) {
39229
40235
  const s = server;