@tool-bridge/server 0.14.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-NVGQVZJW.js → airtable-S3T4TAN7.js} +4 -4
  2. package/dist/{alt_text_generator_ai-WF7NHVWE.js → alt_text_generator_ai-4DIRIDKL.js} +4 -4
  3. package/dist/{amap-EPI7AQ4O.js → amap-LGVGNPML.js} +4 -4
  4. package/dist/{anthropic-KVYWBZIV.js → anthropic-27RFZO35.js} +4 -4
  5. package/dist/{apify-MB56RQIA.js → apify-6WLYAIAS.js} +4 -4
  6. package/dist/{brave_search-JDAGJ7PO.js → brave_search-K2SVJ66F.js} +4 -4
  7. package/dist/{chunk-HGIPJFHA.js → chunk-2O27H4Y2.js} +1 -1
  8. package/dist/{chunk-X6LLG5OZ.js → chunk-BB6ZLMTU.js} +1194 -120
  9. package/dist/{chunk-CDOWN2AB.js → chunk-GKX72JMJ.js} +1 -1
  10. package/dist/{chunk-BWGAV3MR.js → chunk-VK3V2WTY.js} +2 -2
  11. package/dist/{chunk-ABHHWYEV.js → chunk-YB2FLNWI.js} +1689 -39
  12. package/dist/{circleci-2IL5RU2O.js → circleci-WV3ITU7Q.js} +4 -4
  13. package/dist/{clerk-A5YYGWYM.js → clerk-NFQEFKS4.js} +4 -4
  14. package/dist/{cloudflare_dns-GUA5BUWO.js → cloudflare_dns-2RK5ASBJ.js} +4 -4
  15. package/dist/{cohere-BS27QMKR.js → cohere-5KEW7LWT.js} +4 -4
  16. package/dist/{confluence-675IWLHU.js → confluence-SZE63S46.js} +4 -4
  17. package/dist/{convertapi-DGKXP2T3.js → convertapi-QPG2JC5K.js} +4 -4
  18. package/dist/{currencyapi-PFH3UKVU.js → currencyapi-UO2APQRY.js} +4 -4
  19. package/dist/{deepseek-5U3STDGY.js → deepseek-LJELIMM3.js} +4 -4
  20. package/dist/{docker_hub-UVXRU2L3.js → docker_hub-2MFXSBOF.js} +4 -4
  21. package/dist/{dropbox-BC54XUXF.js → dropbox-GQYNTTVA.js} +4 -4
  22. package/dist/{dub-GV5M6PWD.js → dub-KI2LHXST.js} +4 -4
  23. package/dist/{e2b-KEQ3PYPV.js → e2b-WO6ISY3C.js} +4 -4
  24. package/dist/{exa-OGQR7D47.js → exa-IZ7B3BPT.js} +4 -4
  25. package/dist/{fal_ai-ULI6YF4O.js → fal_ai-LTMF36GJ.js} +4 -4
  26. package/dist/{feishu-YWLRRUGP.js → feishu-WM7SLSFD.js} +3 -3
  27. package/dist/{feishu_custom_bot-7HP4UHB7.js → feishu_custom_bot-66W3MXH2.js} +4 -4
  28. package/dist/{figma-BFGC24YI.js → figma-2Z274VQZ.js} +4 -4
  29. package/dist/{firehydrant-R6DG7SBL.js → firehydrant-IFI4R65V.js} +4 -4
  30. package/dist/{fixer-ZVXOVTMF.js → fixer-BCCZEB3D.js} +4 -4
  31. package/dist/{fly-EBKWMDWV.js → fly-755FXYB5.js} +4 -4
  32. package/dist/{front-CFWVIQYX.js → front-4FZKKXXK.js} +4 -4
  33. package/dist/{geocodio-N27RDBFV.js → geocodio-WYKDJ3DP.js} +4 -4
  34. package/dist/{ghost-2HT2SJYO.js → ghost-5BNQTCOH.js} +4 -4
  35. package/dist/{github-ZDLAIRXP.js → github-ULELF5VX.js} +4 -4
  36. package/dist/{gmail-TICKPP6S.js → gmail-G34WT64J.js} +4 -4
  37. package/dist/{googlecalendar-GUR47UTF.js → googlecalendar-BQU255SC.js} +4 -4
  38. package/dist/{googledocs-QGDIJA4S.js → googledocs-HPCNQXYJ.js} +4 -4
  39. package/dist/{grafana-33Y3WEU5.js → grafana-VOV24FIC.js} +4 -4
  40. package/dist/{graphhopper-KWTMYEVH.js → graphhopper-D5RXW6AP.js} +4 -4
  41. package/dist/index.d.ts +164 -61
  42. package/dist/index.js +2 -2
  43. package/dist/{ip2proxy-4SAYDJPQ.js → ip2proxy-DAYO7EKZ.js} +4 -4
  44. package/dist/{ipgeolocation_io-4WT2WKHF.js → ipgeolocation_io-XD4PSLII.js} +4 -4
  45. package/dist/{ipqualityscore-VGZCROJJ.js → ipqualityscore-K27QEAWK.js} +4 -4
  46. package/dist/{jina_ai-X5VJVORK.js → jina_ai-E6UXFV2P.js} +4 -4
  47. package/dist/{jira-XUAF2UPM.js → jira-ZDSXGQ6W.js} +4 -4
  48. package/dist/{langsmith-RRLFNL6D.js → langsmith-TIFJRREW.js} +4 -4
  49. package/dist/{lemlist-KQKG6JHM.js → lemlist-DASEMJMA.js} +4 -4
  50. package/dist/{linear-I32UBCBL.js → linear-KE6UP4SP.js} +4 -4
  51. package/dist/{logsnag-TVNUQEM7.js → logsnag-IMNDN4R5.js} +4 -4
  52. package/dist/main.js +101 -101
  53. package/dist/{mattermost-OCIA7Z4P.js → mattermost-HHHMUXQA.js} +4 -4
  54. package/dist/{mem0-FARYP2ZM.js → mem0-D2FGGOIY.js} +4 -4
  55. package/dist/{memos-6M6QEUYI.js → memos-4OVIGFPH.js} +4 -4
  56. package/dist/{metabase-VCNMPHIR.js → metabase-33WCX5YD.js} +4 -4
  57. package/dist/{mistral_ai-SMPVEWBM.js → mistral_ai-QIMZ2JYL.js} +4 -4
  58. package/dist/{mother_duck-CMHFH5A6.js → mother_duck-M6VWS2VH.js} +4 -4
  59. package/dist/{ngrok-WRQJ3X54.js → ngrok-Z2PKQBHH.js} +4 -4
  60. package/dist/{notes-HLPED7H7.js → notes-SS4RPC4K.js} +2 -2
  61. package/dist/{notion-FFCK2KZ4.js → notion-FJCNQ6I7.js} +4 -4
  62. package/dist/{open_exchange_rates-VKQICRS6.js → open_exchange_rates-WU57WTT5.js} +4 -4
  63. package/dist/{openai-PJDJ2AZ2.js → openai-JODZ4GKT.js} +4 -4
  64. package/dist/{opengraph_io-DMQQXMEC.js → opengraph_io-SQRAUSV5.js} +4 -4
  65. package/dist/{openrouter-I52ZZHQT.js → openrouter-Q5XXEJZ7.js} +4 -4
  66. package/dist/{outline-I6EIBEHQ.js → outline-RT2EHJ2X.js} +4 -4
  67. package/dist/{paddle-4DU7GKCS.js → paddle-OF2V7GAC.js} +4 -4
  68. package/dist/{perplexity-DE6K444O.js → perplexity-LNZEWLHG.js} +4 -4
  69. package/dist/{pinecone-KMRF5YWJ.js → pinecone-JSJIXZYN.js} +4 -4
  70. package/dist/{pipedrive-4OC5UU3L.js → pipedrive-D4CKVUXR.js} +4 -4
  71. package/dist/{polar-OACUFN7P.js → polar-M7ZYP6UR.js} +4 -4
  72. package/dist/{posthog-PSGNT66O.js → posthog-YV7CZOH4.js} +4 -4
  73. package/dist/{postmark-CSOVREPD.js → postmark-KYHLECRG.js} +4 -4
  74. package/dist/{prerender-B7M6SAIA.js → prerender-HCMHBKV2.js} +4 -4
  75. package/dist/{pubmed-JJ5URMS6.js → pubmed-WYVLCCN3.js} +4 -4
  76. package/dist/{railway-VO42EWHC.js → railway-TWIRD5KR.js} +4 -4
  77. package/dist/{readwise-O7I4HLU4.js → readwise-UZ467IQ7.js} +4 -4
  78. package/dist/{render-24X3H3WB.js → render-MECUC5CC.js} +4 -4
  79. package/dist/{replicate-TO6OPISC.js → replicate-A5MEK3SW.js} +4 -4
  80. package/dist/{resend-IBVGUV4X.js → resend-BC6EAWJQ.js} +4 -4
  81. package/dist/{rootly-MD5GFDUC.js → rootly-ZM3QDUIS.js} +4 -4
  82. package/dist/{scrapfly-ZKDGE4IL.js → scrapfly-WAML42QJ.js} +4 -4
  83. package/dist/{scrapingbee-2FVX2BFI.js → scrapingbee-DG7PET3V.js} +4 -4
  84. package/dist/{screenshot_fyi-HNPNS43Z.js → screenshot_fyi-HY6WTHIO.js} +4 -4
  85. package/dist/{semantic_scholar-EJ2CYAKO.js → semantic_scholar-C5IZA7IP.js} +4 -4
  86. package/dist/{sentry-DXB3CQZA.js → sentry-OKBPSTDM.js} +4 -4
  87. package/dist/{shodan-HEFZ6NHP.js → shodan-PPC6DYU4.js} +4 -4
  88. package/dist/{shopify-SUN5J257.js → shopify-EWGUHBXO.js} +4 -4
  89. package/dist/{stripe-N3HNTHD5.js → stripe-6RPDPC5W.js} +4 -4
  90. package/dist/{supabase-IMZ6YRES.js → supabase-SK7DQ42N.js} +4 -4
  91. package/dist/{tavily-QMPSOIGW.js → tavily-YCG5VQMH.js} +4 -4
  92. package/dist/{telegram-SHZX3C73.js → telegram-2OES5FYB.js} +4 -4
  93. package/dist/{telnyx-SF6KMQ7W.js → telnyx-POOSBBR4.js} +4 -4
  94. package/dist/{todoist-Y252DAZ2.js → todoist-M7YD6YSS.js} +4 -4
  95. package/dist/{trello-BTRC6I5Y.js → trello-IS6V5W3E.js} +4 -4
  96. package/dist/{turso-SB4KFTUP.js → turso-RCFHUFVH.js} +4 -4
  97. package/dist/{twilio-CRRJZ6MR.js → twilio-OS2RXFGO.js} +4 -4
  98. package/dist/{umami-7PJKITQ5.js → umami-PQ2SJ2LN.js} +4 -4
  99. package/dist/{unsplash-PYZXMXH2.js → unsplash-C6BPZO4E.js} +4 -4
  100. package/dist/{upstash_redis-L2IJTHFW.js → upstash_redis-WH6UER7H.js} +4 -4
  101. package/dist/{uptimerobot-XDYEYFJQ.js → uptimerobot-M3YZGS6Z.js} +4 -4
  102. package/dist/{v2ex-TNACDCAO.js → v2ex-R4JWTEHN.js} +4 -4
  103. package/dist/{vercel-QRU4UC5M.js → vercel-FZVYUOQD.js} +4 -4
  104. package/dist/{wolfram_alpha_api-TS663Q5L.js → wolfram_alpha_api-2KOLO5DC.js} +4 -4
  105. package/dist/{wordpress-RTEUS26Y.js → wordpress-XRWGO6LG.js} +4 -4
  106. package/dist/{workos-4NGPU4VP.js → workos-WR4NKOYL.js} +4 -4
  107. package/dist/{zhihu-GGLOXAVO.js → zhihu-BKZIB3WA.js} +4 -4
  108. package/package.json +3 -3
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  AnnotationStore,
3
3
  CONTEXT_CAPABILITIES,
4
+ CONTEXT_DIRECT_UPLOAD_CAPABILITY,
4
5
  DEFAULT_MAX_NODES,
6
+ DEFAULT_STORE_DRIVER_KEY_ROOT,
5
7
  DEVICE_CALL_TIMEOUT_MS,
6
8
  DeviceGatewaySession,
7
9
  FEEDBACK_HIDE_SCORE,
@@ -25,11 +27,13 @@ import {
25
27
  PG_SEARCH_TOOLS_TABLE,
26
28
  PG_SEARCH_WRITE_LOCK_KEY,
27
29
  PRESIGN_TTL_SEC_DEFAULT,
30
+ PRESIGN_TTL_SEC_MAX,
28
31
  RemoteAllowlistStore,
29
32
  SKILLHUB_CAPABILITIES,
30
33
  SKRegistryStore,
31
34
  SecretStoreImpl,
32
35
  SqlSearchIndex,
36
+ StoreService,
33
37
  TBError,
34
38
  TB_ERROR_CODES,
35
39
  TOOL_SEARCH_AUDIT_NODE_LIMIT,
@@ -47,6 +51,7 @@ import {
47
51
  appendVia,
48
52
  array,
49
53
  assertPluginPayloadSize,
54
+ assertPresignTtlSec,
50
55
  assertSecretRefUse,
51
56
  assertSecureUrl,
52
57
  authHeaderFor,
@@ -71,9 +76,12 @@ import {
71
76
  contextHelpModel,
72
77
  contextMethodsOf,
73
78
  contextScopeForCmd,
79
+ contextUploadCmd,
74
80
  createBuiltins,
75
81
  createObjectContextProvider,
82
+ createObjectContextUploadGrant,
76
83
  createSkillhubProvider,
84
+ createStoreModule,
77
85
  decodeDeviceFrame,
78
86
  derivePresence,
79
87
  deviceDirectoryHelpModel,
@@ -131,6 +139,7 @@ import {
131
139
  shouldRefresh,
132
140
  skillhubHelpModel,
133
141
  skillhubScopeForCmd,
142
+ storeUri,
134
143
  string,
135
144
  toJSONSchema,
136
145
  toolHelpModel,
@@ -141,16 +150,21 @@ import {
141
150
  url,
142
151
  validatePath,
143
152
  virtualizeTools
144
- } from "./chunk-ABHHWYEV.js";
153
+ } from "./chunk-YB2FLNWI.js";
145
154
 
146
155
  // src/config.ts
147
156
  var DEFAULT_PORT = 8787;
148
157
  var DEFAULT_MAX_HOPS = 4;
149
158
  var DEFAULT_DEVICE_RECLAIM_SEC = 24 * 60 * 60;
159
+ var DEFAULT_STORE_CLEANUP_INTERVAL_SEC = 15 * 60;
150
160
  function positiveIntEnv(value) {
151
161
  const n = Number(value);
152
162
  return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
153
163
  }
164
+ function presignTtlEnv(value) {
165
+ const ttl = positiveIntEnv(value);
166
+ return ttl === void 0 ? void 0 : Math.min(ttl, PRESIGN_TTL_SEC_MAX);
167
+ }
154
168
  function portEnv(value) {
155
169
  if (value === void 0 || value === "") return void 0;
156
170
  const n = Number(value);
@@ -174,6 +188,7 @@ function configFromEnv(env = process.env) {
174
188
  allowInsecure
175
189
  },
176
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,
177
192
  // 0 合法(立即关停,本地/单机默认);positiveIntEnv 拒 0,故单独解析。
178
193
  shutdownDrainSec: (() => {
179
194
  const n = Number(env.TB_SHUTDOWN_DRAIN_SEC);
@@ -224,8 +239,38 @@ function configFromEnv(env = process.env) {
224
239
  if (ttl !== void 0) config.toolCacheTtlSec = ttl;
225
240
  const refThreshold = positiveIntEnv(env.TB_REF_THRESHOLD_BYTES);
226
241
  if (refThreshold !== void 0) config.refThresholdBytes = refThreshold;
227
- const refTtl = positiveIntEnv(env.TB_REF_TTL_SEC);
242
+ const refTtl = presignTtlEnv(env.TB_REF_TTL_SEC);
228
243
  if (refTtl !== void 0) config.refTtlSec = refTtl;
244
+ const uploadGrantTtl = presignTtlEnv(env.TB_UPLOAD_GRANT_TTL_SEC);
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
+ }
229
274
  return config;
230
275
  }
231
276
 
@@ -428,7 +473,8 @@ var BUILTIN_MODULES = [
428
473
  "plugin",
429
474
  "catalog",
430
475
  "federation",
431
- "annotation"
476
+ "annotation",
477
+ "store"
432
478
  ];
433
479
  var BUILTIN_DESCRIPTIONS = {
434
480
  sk: "SecretKey registry",
@@ -438,7 +484,8 @@ var BUILTIN_DESCRIPTIONS = {
438
484
  plugin: "Plugin registry (external plugins)",
439
485
  catalog: "Built-in integration catalog (read-only)",
440
486
  federation: "Remote federation host allowlist",
441
- 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"
442
489
  };
443
490
  async function mintAdminWithPlaintext(store, plaintext, now) {
444
491
  const { key } = await mintKey(adminBootstrapInput(), now);
@@ -533,6 +580,7 @@ function encodeObjectKey(key) {
533
580
  return key.split("/").map(encodeURIComponent).join("/");
534
581
  }
535
582
  async function presignS3Url(client, url2, ttlSec) {
583
+ assertPresignTtlSec(ttlSec);
536
584
  const target = new URL(url2);
537
585
  target.searchParams.set("X-Amz-Expires", String(ttlSec));
538
586
  const signed = await client.sign(new Request(target.toString(), { method: "GET" }), {
@@ -540,6 +588,24 @@ async function presignS3Url(client, url2, ttlSec) {
540
588
  });
541
589
  return signed.url;
542
590
  }
591
+ async function presignS3Put(client, url2, ttlSec, opts) {
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
+ }
596
+ const target = new URL(url2);
597
+ target.searchParams.set("X-Amz-Expires", String(ttlSec));
598
+ const headers = {
599
+ "content-type": opts.contentType,
600
+ ..."contentLength" in opts ? { "content-length": String(opts.contentLength) } : {},
601
+ ...opts.ifNoneMatch === void 0 ? {} : { "if-none-match": opts.ifNoneMatch }
602
+ };
603
+ const signed = await client.sign(
604
+ new Request(target.toString(), { method: "PUT", headers }),
605
+ { aws: { signQuery: true, allHeaders: true } }
606
+ );
607
+ return { method: "PUT", url: signed.url, headers };
608
+ }
543
609
 
544
610
  // ../app/src/providers/s3Object.ts
545
611
  var META_HEADER_PREFIX = "x-amz-meta-";
@@ -576,12 +642,19 @@ function createS3ObjectStore(cfg, opts) {
576
642
  service: "s3",
577
643
  region: cfg.region ?? "auto"
578
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
+ });
579
652
  const base = cfg.endpoint.replace(/\/+$/, "");
580
653
  const bucketUrl = `${base}/${cfg.bucket}`;
581
654
  const urlFor = (key) => `${bucketUrl}/${encodeObjectKey(key)}`;
582
- const s3Fetch = async (url2, init) => {
655
+ const s3FetchWith = async (aws, url2, init) => {
583
656
  try {
584
- return await client.fetch(url2, init);
657
+ return await aws.fetch(url2, init);
585
658
  } catch (err) {
586
659
  throw normalizeUpstreamError({
587
660
  kind: "network",
@@ -589,6 +662,47 @@ function createS3ObjectStore(cfg, opts) {
589
662
  });
590
663
  }
591
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
+ };
592
706
  const metaFromHeaders = (key, headers) => {
593
707
  const metadata = {};
594
708
  headers.forEach((value, name) => {
@@ -607,13 +721,14 @@ function createS3ObjectStore(cfg, opts) {
607
721
  if (contentType !== null) meta.contentType = contentType;
608
722
  return meta;
609
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
+ };
610
730
  return {
611
- async head(key) {
612
- const resp = await s3Fetch(urlFor(key), { method: "HEAD" });
613
- if (resp.status === 404) return null;
614
- if (!resp.ok) throw s3Error("head", resp.status);
615
- return metaFromHeaders(key, resp.headers);
616
- },
731
+ head: headObject,
617
732
  async get(key) {
618
733
  const resp = await s3Fetch(urlFor(key), { method: "GET" });
619
734
  if (resp.status === 404) {
@@ -627,27 +742,31 @@ function createS3ObjectStore(cfg, opts) {
627
742
  return { meta: metaFromHeaders(key, resp.headers), body: resp.body ?? EMPTY_STREAM };
628
743
  },
629
744
  async put(key, body, putOpts) {
630
- const bytes = await objectBodyToBytes(body);
631
745
  const headers = {};
746
+ headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
632
747
  if (putOpts?.contentType !== void 0) headers["content-type"] = putOpts.contentType;
633
748
  for (const [name, value] of Object.entries(putOpts?.metadata ?? {})) {
634
749
  headers[`${META_HEADER_PREFIX}${name}`] = value;
635
750
  }
636
751
  if (putOpts?.ifMatchEtag !== void 0) headers["if-match"] = `"${putOpts.ifMatchEtag}"`;
637
- 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
+ });
638
758
  await resp.body?.cancel();
639
- if (resp.status === 412 || putOpts?.ifMatchEtag !== void 0 && resp.status === 404) {
640
- 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}'`);
641
762
  }
642
763
  if (!resp.ok) throw s3Error("put", resp.status);
643
- const meta = {
644
- key,
645
- etag: stripEtagQuotes(resp.headers.get("etag") ?? ""),
646
- size: bytes.byteLength,
647
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
648
- metadata: putOpts?.metadata ?? {}
649
- };
650
- 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
+ }
651
770
  return meta;
652
771
  },
653
772
  async delete(key) {
@@ -701,6 +820,12 @@ function createS3ObjectStore(cfg, opts) {
701
820
  },
702
821
  presign(key, ttlSec) {
703
822
  return presignS3Url(client, urlFor(key), ttlSec);
823
+ },
824
+ presignPut(key, ttlSec, putOpts) {
825
+ return presignS3Put(client, urlFor(key), ttlSec, putOpts);
826
+ },
827
+ presignPutExact(key, ttlSec, putOpts) {
828
+ return presignS3Put(client, urlFor(key), ttlSec, putOpts);
704
829
  }
705
830
  };
706
831
  }
@@ -1000,6 +1125,461 @@ var SearchSynchronizer = class {
1000
1125
  // ../app/src/deps.ts
1001
1126
  var TOOL_CACHE_TTL_DEFAULT = 300;
1002
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
+
1003
1583
  // ../app/src/deviceNodes.ts
1004
1584
  function tbErrorFromBody(body) {
1005
1585
  return new TBError(body.code, body.message, { retryable: body.retryable });
@@ -1012,7 +1592,19 @@ function requireDevice(deps) {
1012
1592
  }
1013
1593
  async function invokeDevice(deps, deviceId, req) {
1014
1594
  const id = crypto.randomUUID();
1015
- 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
+ }
1016
1608
  if (!body || !("ok" in body)) {
1017
1609
  throw new TBError("unavailable", "device session returned invalid result");
1018
1610
  }
@@ -7129,9 +7721,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7129
7721
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
7130
7722
  return `${varKind} ${this.name}${rhs};` + _n;
7131
7723
  }
7132
- optimizeNames(names, constants) {
7724
+ optimizeNames(names, constants2) {
7133
7725
  if (!names[this.name.str]) return;
7134
- if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants);
7726
+ if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
7135
7727
  return this;
7136
7728
  }
7137
7729
  get names() {
@@ -7148,9 +7740,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7148
7740
  render({ _n }) {
7149
7741
  return `${this.lhs} = ${this.rhs};` + _n;
7150
7742
  }
7151
- optimizeNames(names, constants) {
7743
+ optimizeNames(names, constants2) {
7152
7744
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
7153
- this.rhs = optimizeExpr(this.rhs, names, constants);
7745
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
7154
7746
  return this;
7155
7747
  }
7156
7748
  get names() {
@@ -7209,8 +7801,8 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7209
7801
  optimizeNodes() {
7210
7802
  return `${this.code}` ? this : void 0;
7211
7803
  }
7212
- optimizeNames(names, constants) {
7213
- this.code = optimizeExpr(this.code, names, constants);
7804
+ optimizeNames(names, constants2) {
7805
+ this.code = optimizeExpr(this.code, names, constants2);
7214
7806
  return this;
7215
7807
  }
7216
7808
  get names() {
@@ -7236,12 +7828,12 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7236
7828
  }
7237
7829
  return nodes.length > 0 ? this : void 0;
7238
7830
  }
7239
- optimizeNames(names, constants) {
7831
+ optimizeNames(names, constants2) {
7240
7832
  const { nodes } = this;
7241
7833
  let i = nodes.length;
7242
7834
  while (i--) {
7243
7835
  const n = nodes[i];
7244
- if (n.optimizeNames(names, constants)) continue;
7836
+ if (n.optimizeNames(names, constants2)) continue;
7245
7837
  subtractNames(names, n.names);
7246
7838
  nodes.splice(i, 1);
7247
7839
  }
@@ -7288,11 +7880,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7288
7880
  if (cond === false || !this.nodes.length) return void 0;
7289
7881
  return this;
7290
7882
  }
7291
- optimizeNames(names, constants) {
7883
+ optimizeNames(names, constants2) {
7292
7884
  var _a;
7293
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
7294
- if (!(super.optimizeNames(names, constants) || this.else)) return;
7295
- 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);
7296
7888
  return this;
7297
7889
  }
7298
7890
  get names() {
@@ -7314,9 +7906,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7314
7906
  render(opts) {
7315
7907
  return `for(${this.iteration})` + super.render(opts);
7316
7908
  }
7317
- optimizeNames(names, constants) {
7318
- if (!super.optimizeNames(names, constants)) return;
7319
- 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);
7320
7912
  return this;
7321
7913
  }
7322
7914
  get names() {
@@ -7351,9 +7943,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7351
7943
  render(opts) {
7352
7944
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
7353
7945
  }
7354
- optimizeNames(names, constants) {
7355
- if (!super.optimizeNames(names, constants)) return;
7356
- 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);
7357
7949
  return this;
7358
7950
  }
7359
7951
  get names() {
@@ -7392,11 +7984,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7392
7984
  (_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
7393
7985
  return this;
7394
7986
  }
7395
- optimizeNames(names, constants) {
7987
+ optimizeNames(names, constants2) {
7396
7988
  var _a, _b;
7397
- super.optimizeNames(names, constants);
7398
- (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants);
7399
- (_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);
7400
7992
  return this;
7401
7993
  }
7402
7994
  get names() {
@@ -7645,7 +8237,7 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7645
8237
  function addExprNames(names, from) {
7646
8238
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
7647
8239
  }
7648
- function optimizeExpr(expr, names, constants) {
8240
+ function optimizeExpr(expr, names, constants2) {
7649
8241
  if (expr instanceof code_1.Name) return replaceName(expr);
7650
8242
  if (!canOptimize(expr)) return expr;
7651
8243
  return new code_1._Code(expr._items.reduce((items, c) => {
@@ -7655,13 +8247,13 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7655
8247
  return items;
7656
8248
  }, []));
7657
8249
  function replaceName(n) {
7658
- const c = constants[n.str];
8250
+ const c = constants2[n.str];
7659
8251
  if (c === void 0 || names[n.str] !== 1) return n;
7660
8252
  delete names[n.str];
7661
8253
  return c;
7662
8254
  }
7663
8255
  function canOptimize(e) {
7664
- 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);
7665
8257
  }
7666
8258
  }
7667
8259
  function subtractNames(names, from) {
@@ -18696,8 +19288,8 @@ var GatewayMcpOAuthProvider = class {
18696
19288
  }
18697
19289
  get redirectUrl() {
18698
19290
  if (this.opts.redirectUri !== void 0) return this.opts.redirectUri;
18699
- const origin = this.opts.origin ?? "https://tool-bridge.invalid";
18700
- return `${origin}${OAUTH_CALLBACK_PATH}`;
19291
+ const origin2 = this.opts.origin ?? "https://tool-bridge.invalid";
19292
+ return `${origin2}${OAUTH_CALLBACK_PATH}`;
18701
19293
  }
18702
19294
  get clientMetadata() {
18703
19295
  return {
@@ -19579,7 +20171,7 @@ async function assertToolConfig(config, deps, ctx, mountPath) {
19579
20171
  }
19580
20172
 
19581
20173
  // ../app/src/refToken.ts
19582
- async function hmacKey(secret) {
20174
+ async function hmacKey2(secret) {
19583
20175
  const digest = await crypto.subtle.digest(
19584
20176
  "SHA-256",
19585
20177
  new TextEncoder().encode(`tb-ref-token:${secret}`)
@@ -19593,7 +20185,7 @@ async function signRefToken(payload, secret) {
19593
20185
  const body = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
19594
20186
  const mac = await crypto.subtle.sign(
19595
20187
  "HMAC",
19596
- await hmacKey(secret),
20188
+ await hmacKey2(secret),
19597
20189
  new TextEncoder().encode(body)
19598
20190
  );
19599
20191
  return `${body}.${base64urlEncode(new Uint8Array(mac))}`;
@@ -19610,7 +20202,7 @@ async function verifyRefToken(token, secret) {
19610
20202
  }
19611
20203
  const ok = await crypto.subtle.verify(
19612
20204
  "HMAC",
19613
- await hmacKey(secret),
20205
+ await hmacKey2(secret),
19614
20206
  // Node 的 WebCrypto 类型要求 Uint8Array<ArrayBuffer>;base64urlDecode 的产物满足但类型面是 ArrayBufferLike。
19615
20207
  sig,
19616
20208
  new TextEncoder().encode(body)
@@ -19692,6 +20284,24 @@ async function contextObjectStoreFor(cfg, deps) {
19692
20284
  }
19693
20285
  throw TBError.unimplemented(`context provider '${cfg.provider}' not implemented yet`);
19694
20286
  }
20287
+ async function contextDirectUploadAvailable(cfg, deps) {
20288
+ if (cfg.provider === "s3") return true;
20289
+ try {
20290
+ return (await contextObjectStoreFor(cfg, deps)).presignPut !== void 0;
20291
+ } catch {
20292
+ return false;
20293
+ }
20294
+ }
20295
+ async function createContextUploadGrant(node, cfg, deps, input) {
20296
+ const objects = await contextObjectStoreFor(cfg, deps);
20297
+ const uploadGrantTtlSec = deps.uploadGrantTtlSec ?? Math.min(deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT, PRESIGN_TTL_SEC_DEFAULT);
20298
+ return createObjectContextUploadGrant(objects, {
20299
+ nsPath: node.path,
20300
+ keyPrefix: contextKeyPrefix(cfg, node.path),
20301
+ readOnly: cfg.readOnly ?? false,
20302
+ uploadGrantTtlSec
20303
+ }, input);
20304
+ }
19695
20305
  async function contextProviderFor(node, cfg, deps, requestUrl) {
19696
20306
  const objects = await contextObjectStoreFor(cfg, deps);
19697
20307
  const opts = {
@@ -19703,11 +20313,11 @@ async function contextProviderFor(node, cfg, deps, requestUrl) {
19703
20313
  if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
19704
20314
  const encKey = deps.encryptionKey;
19705
20315
  if (encKey !== void 0) {
19706
- const origin = new URL(requestUrl).origin;
20316
+ const origin2 = new URL(requestUrl).origin;
19707
20317
  const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
19708
20318
  opts.relayRefUrl = async (key) => {
19709
20319
  const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
19710
- 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)}`;
19711
20321
  };
19712
20322
  }
19713
20323
  return createObjectContextProvider(objects, opts);
@@ -19728,11 +20338,11 @@ async function skillhubProviderFor(node, cfg, deps, requestUrl) {
19728
20338
  if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
19729
20339
  const encKey = deps.encryptionKey;
19730
20340
  if (encKey !== void 0) {
19731
- const origin = new URL(requestUrl).origin;
20341
+ const origin2 = new URL(requestUrl).origin;
19732
20342
  const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
19733
20343
  opts.relayRefUrl = async (key) => {
19734
20344
  const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
19735
- 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)}`;
19736
20346
  };
19737
20347
  }
19738
20348
  return createSkillhubProvider(objects, opts);
@@ -23235,8 +23845,8 @@ var LADDER_ERROR_HTTP_STATUS2 = {
23235
23845
  [ProtocolErrorCode2.MissingRequiredClientCapability]: 400,
23236
23846
  [HEADER_MISMATCH_ERROR_CODE2]: 400
23237
23847
  };
23238
- function httpStatusForErrorCode(code, origin) {
23239
- 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;
23240
23850
  return LADDER_ERROR_HTTP_STATUS2[code] ?? 400;
23241
23851
  }
23242
23852
  function rejection(rung, cell, httpStatus, error, settled) {
@@ -25288,9 +25898,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25288
25898
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
25289
25899
  return `${varKind} ${this.name}${rhs};` + _n;
25290
25900
  }
25291
- optimizeNames(names, constants) {
25901
+ optimizeNames(names, constants2) {
25292
25902
  if (!names[this.name.str]) return;
25293
- if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants);
25903
+ if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
25294
25904
  return this;
25295
25905
  }
25296
25906
  get names() {
@@ -25307,9 +25917,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25307
25917
  render({ _n }) {
25308
25918
  return `${this.lhs} = ${this.rhs};` + _n;
25309
25919
  }
25310
- optimizeNames(names, constants) {
25920
+ optimizeNames(names, constants2) {
25311
25921
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
25312
- this.rhs = optimizeExpr(this.rhs, names, constants);
25922
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
25313
25923
  return this;
25314
25924
  }
25315
25925
  get names() {
@@ -25368,8 +25978,8 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25368
25978
  optimizeNodes() {
25369
25979
  return `${this.code}` ? this : void 0;
25370
25980
  }
25371
- optimizeNames(names, constants) {
25372
- this.code = optimizeExpr(this.code, names, constants);
25981
+ optimizeNames(names, constants2) {
25982
+ this.code = optimizeExpr(this.code, names, constants2);
25373
25983
  return this;
25374
25984
  }
25375
25985
  get names() {
@@ -25395,12 +26005,12 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25395
26005
  }
25396
26006
  return nodes.length > 0 ? this : void 0;
25397
26007
  }
25398
- optimizeNames(names, constants) {
26008
+ optimizeNames(names, constants2) {
25399
26009
  const { nodes } = this;
25400
26010
  let i = nodes.length;
25401
26011
  while (i--) {
25402
26012
  const n = nodes[i];
25403
- if (n.optimizeNames(names, constants)) continue;
26013
+ if (n.optimizeNames(names, constants2)) continue;
25404
26014
  subtractNames(names, n.names);
25405
26015
  nodes.splice(i, 1);
25406
26016
  }
@@ -25447,11 +26057,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25447
26057
  if (cond === false || !this.nodes.length) return void 0;
25448
26058
  return this;
25449
26059
  }
25450
- optimizeNames(names, constants) {
26060
+ optimizeNames(names, constants2) {
25451
26061
  var _a;
25452
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
25453
- if (!(super.optimizeNames(names, constants) || this.else)) return;
25454
- 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);
25455
26065
  return this;
25456
26066
  }
25457
26067
  get names() {
@@ -25473,9 +26083,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25473
26083
  render(opts) {
25474
26084
  return `for(${this.iteration})` + super.render(opts);
25475
26085
  }
25476
- optimizeNames(names, constants) {
25477
- if (!super.optimizeNames(names, constants)) return;
25478
- 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);
25479
26089
  return this;
25480
26090
  }
25481
26091
  get names() {
@@ -25510,9 +26120,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25510
26120
  render(opts) {
25511
26121
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
25512
26122
  }
25513
- optimizeNames(names, constants) {
25514
- if (!super.optimizeNames(names, constants)) return;
25515
- 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);
25516
26126
  return this;
25517
26127
  }
25518
26128
  get names() {
@@ -25551,11 +26161,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25551
26161
  (_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
25552
26162
  return this;
25553
26163
  }
25554
- optimizeNames(names, constants) {
26164
+ optimizeNames(names, constants2) {
25555
26165
  var _a, _b;
25556
- super.optimizeNames(names, constants);
25557
- (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants);
25558
- (_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);
25559
26169
  return this;
25560
26170
  }
25561
26171
  get names() {
@@ -25804,7 +26414,7 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25804
26414
  function addExprNames(names, from) {
25805
26415
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
25806
26416
  }
25807
- function optimizeExpr(expr, names, constants) {
26417
+ function optimizeExpr(expr, names, constants2) {
25808
26418
  if (expr instanceof code_1.Name) return replaceName(expr);
25809
26419
  if (!canOptimize(expr)) return expr;
25810
26420
  return new code_1._Code(expr._items.reduce((items, c) => {
@@ -25814,13 +26424,13 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25814
26424
  return items;
25815
26425
  }, []));
25816
26426
  function replaceName(n) {
25817
- const c = constants[n.str];
26427
+ const c = constants2[n.str];
25818
26428
  if (c === void 0 || names[n.str] !== 1) return n;
25819
26429
  delete names[n.str];
25820
26430
  return c;
25821
26431
  }
25822
26432
  function canOptimize(e) {
25823
- 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);
25824
26434
  }
25825
26435
  }
25826
26436
  function subtractNames(names, from) {
@@ -32044,9 +32654,9 @@ function createListenRouter(options) {
32044
32654
  const { bus, onerror } = options;
32045
32655
  const maxSubscriptions = options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS;
32046
32656
  const keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
32047
- const open = /* @__PURE__ */ new Set();
32657
+ const open2 = /* @__PURE__ */ new Set();
32048
32658
  function serve2(message, signal, capabilities, serverInfo) {
32049
- if (open.size >= maxSubscriptions) {
32659
+ if (open2.size >= maxSubscriptions) {
32050
32660
  onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
32051
32661
  return jsonRpcError(message.id, -32603, "Subscription limit reached");
32052
32662
  }
@@ -32102,7 +32712,7 @@ data: ${JSON.stringify({
32102
32712
  }
32103
32713
  if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
32104
32714
  abortCleanup?.();
32105
- open.delete(teardown);
32715
+ open2.delete(teardown);
32106
32716
  try {
32107
32717
  controller.close();
32108
32718
  } catch {
@@ -32122,7 +32732,7 @@ data: ${JSON.stringify({
32122
32732
  writeNotification(note.method, note.params);
32123
32733
  });
32124
32734
  keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
32125
- open.add(teardown);
32735
+ open2.add(teardown);
32126
32736
  },
32127
32737
  cancel() {
32128
32738
  teardown(false);
@@ -32147,10 +32757,10 @@ data: ${JSON.stringify({
32147
32757
  return {
32148
32758
  serve: serve2,
32149
32759
  closeAll() {
32150
- for (const teardown of open) teardown(true);
32760
+ for (const teardown of open2) teardown(true);
32151
32761
  },
32152
32762
  get openCount() {
32153
- return open.size;
32763
+ return open2.size;
32154
32764
  }
32155
32765
  };
32156
32766
  }
@@ -36473,7 +37083,10 @@ async function handleDescribe(c, env) {
36473
37083
  await assertContextAlive(node, node.config, registry);
36474
37084
  const cfg = node.config;
36475
37085
  const local = cfg.provider !== "r2" && cfg.provider !== "s3" ? localContext(deps, node) : null;
36476
- const capabilities = cfg.provider === "r2" || cfg.provider === "s3" || cfg.provider === "device-fs" || deviceMarkerOf(cfg.providerConfig) !== null ? CONTEXT_CAPABILITIES : local !== null ? localCapabilities(local) : (await requirePluginExport(deps, cfg.provider, "context", "context", cfg.export)).export.capabilities ?? [];
37086
+ let capabilities = cfg.provider === "r2" || cfg.provider === "s3" || cfg.provider === "device-fs" || deviceMarkerOf(cfg.providerConfig) !== null ? CONTEXT_CAPABILITIES : local !== null ? localCapabilities(local) : (await requirePluginExport(deps, cfg.provider, "context", "context", cfg.export)).export.capabilities ?? [];
37087
+ if ((cfg.provider === "r2" || cfg.provider === "s3") && cfg.readOnly !== true && await contextDirectUploadAvailable(cfg, deps)) {
37088
+ capabilities = [...capabilities, CONTEXT_DIRECT_UPLOAD_CAPABILITY];
37089
+ }
36477
37090
  return new Response(JSON.stringify({ kind: "context", capabilities }), {
36478
37091
  headers: { "content-type": contentTypeFor("json") }
36479
37092
  });
@@ -36488,6 +37101,147 @@ async function handleDescribe(c, env) {
36488
37101
  throw TBError.notFound(`no capabilities for kind '${node.kind}'`);
36489
37102
  }
36490
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
+
36491
37245
  // ../app/src/routes/publicRoutes.ts
36492
37246
  async function finishToolAuthorization(opts) {
36493
37247
  const config = opts.node.config;
@@ -36857,7 +37611,7 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
36857
37611
  readOnly: (node.config.readOnly ?? false) || isReadOnlyProvider(local)
36858
37612
  });
36859
37613
  }
36860
- const model = contextHelpModel(node, { readOnly: node.config.readOnly ?? false });
37614
+ const model2 = contextHelpModel(node, { readOnly: node.config.readOnly ?? false });
36861
37615
  const { export: exported } = await requirePluginExport(
36862
37616
  deps,
36863
37617
  node.config.provider,
@@ -36872,9 +37626,13 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
36872
37626
  "update",
36873
37627
  ...optionalMethodsForCapabilities(exported.capabilities ?? [])
36874
37628
  ]);
36875
- return { ...model, cmds: model.cmds.filter((c) => declared.has(c.name)) };
37629
+ return { ...model2, cmds: model2.cmds.filter((c) => declared.has(c.name)) };
36876
37630
  }
36877
- return contextHelpModel(node, { readOnly: node.config.readOnly ?? false });
37631
+ const model = contextHelpModel(node, { readOnly: node.config.readOnly ?? false });
37632
+ if (node.config.readOnly !== true && opts.includeDirectUpload !== false && await contextDirectUploadAvailable(node.config, deps)) {
37633
+ return { ...model, cmds: [...model.cmds, contextUploadCmd(node.path)] };
37634
+ }
37635
+ return model;
36878
37636
  }
36879
37637
  if (node.kind === "skillhub" && node.config?.kind === "skillhub") {
36880
37638
  await assertContextAlive(node, node.config, registry);
@@ -37135,6 +37893,7 @@ function mcpBridgeFor(c, env, app) {
37135
37893
  continue;
37136
37894
  }
37137
37895
  const model = await helpModelFor(node, registry, ctx, builtinsOf(c.get("store")), deps, {
37896
+ includeDirectUpload: check(ctx, node.path, "write").allow,
37138
37897
  refresh: false,
37139
37898
  now
37140
37899
  });
@@ -37332,7 +38091,8 @@ async function handleInvoke(c, env) {
37332
38091
  await assertContextAlive(node, cfg, registry);
37333
38092
  const args2 = await readInvokeBody();
37334
38093
  const scope = contextScopeForCmd(command);
37335
- if (scope === null) {
38094
+ const directUpload = command === "create_upload";
38095
+ if (scope === null || directUpload && cfg.provider !== "r2" && cfg.provider !== "s3") {
37336
38096
  throw new TBError("invalid_argument", `unknown cmd '${command}' on '${node.path}'`);
37337
38097
  }
37338
38098
  if (!check(ctx, node.path, scope).allow) {
@@ -37341,6 +38101,15 @@ async function handleInvoke(c, env) {
37341
38101
  if (cfg.readOnly === true && scope === "write") {
37342
38102
  throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
37343
38103
  }
38104
+ if (directUpload) {
38105
+ const result3 = await createContextUploadGrant(
38106
+ node,
38107
+ cfg,
38108
+ deps,
38109
+ args2
38110
+ );
38111
+ return renderResult(result3, negotiate(c.req.header("accept")));
38112
+ }
37344
38113
  if (cfg.provider === "device-fs") {
37345
38114
  const result3 = await invokeDevice(deps, deviceIdForDeviceFs(cfg), {
37346
38115
  path: `fs/${command}`,
@@ -37414,6 +38183,7 @@ async function handleInvoke(c, env) {
37414
38183
  if (!mod) throw TBError.unimplemented(`builtin module '${node.config.module}' not available`);
37415
38184
  const args = await readInvokeBody();
37416
38185
  const cmd = command;
38186
+ rejectStoreCapabilityBodyFields(node.config.module, args);
37417
38187
  const spec = scopeForCmd(mod, node.path, cmd);
37418
38188
  if (!spec) throw new TBError("invalid_argument", `unknown cmd '${cmd}' on '${node.path}'`);
37419
38189
  if (!check(ctx, node.path, spec.scope).allow) {
@@ -37457,7 +38227,9 @@ async function handleInvoke(c, env) {
37457
38227
  const registryMarker = registryTarget === void 0 ? void 0 : await searchSync?.markNode(registryTarget);
37458
38228
  let result;
37459
38229
  try {
37460
- 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
+ });
37461
38233
  } catch (error) {
37462
38234
  await searchSync?.abort(registryMarker);
37463
38235
  await Promise.all(pluginMounts.map(async (mount) => await searchSync?.abort(mount.marker)));
@@ -37508,17 +38280,21 @@ async function handleInvoke(c, env) {
37508
38280
  // ../app/src/routes/env.ts
37509
38281
  function createRouteEnv(deps) {
37510
38282
  const searchSync = isMutableSearchIndex(deps.search) ? new SearchSynchronizer(deps.state, deps.search) : void 0;
37511
- const builtinsOf = (store) => createBuiltins(
37512
- buildDeps({
37513
- store,
37514
- secrets: deps.secrets,
37515
- version: deps.version,
37516
- allowInsecureHttp: deps.allowInsecureHttp,
37517
- remoteAllowlistBase: deps.remote.allowlist,
37518
- ...deps.pluginBindings !== void 0 ? { pluginBindings: deps.pluginBindings } : {},
37519
- ...deps.pluginCatalog !== void 0 ? { pluginCatalog: deps.pluginCatalog } : {}
37520
- })
37521
- );
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
+ };
37522
38298
  const globalSearchCapabilities = () => {
37523
38299
  const declared = new Set(deps.search?.capabilities ?? []);
37524
38300
  if (!declared.has("search")) return [];
@@ -37659,6 +38435,7 @@ function createTbApp(deps) {
37659
38435
  if (secured !== response) c.res = secured;
37660
38436
  });
37661
38437
  registerPublicRoutes(app, env);
38438
+ registerStoreCapabilityRoutes(app, deps);
37662
38439
  app.use("*", async (c, next) => {
37663
38440
  const store = deps.state;
37664
38441
  try {
@@ -38185,6 +38962,19 @@ var DeviceRouter = class _DeviceRouter {
38185
38962
  }
38186
38963
  };
38187
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
+
38188
38978
  // ../core/src/node/fsObjectStore.ts
38189
38979
  import { mkdir, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
38190
38980
  import { basename, dirname, join, resolve, sep } from "path";
@@ -38284,6 +39074,9 @@ var FsObjectStore = class {
38284
39074
  throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':\u672A\u77E5\u6839\u6216\u7F3A\u76F8\u5BF9\u8DEF\u5F84`);
38285
39075
  }
38286
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
+ }
38287
39080
  if (opts?.ifMatchEtag !== void 0) {
38288
39081
  const existing = await this.head(key);
38289
39082
  if (opts.ifMatchEtag !== existing?.etag) {
@@ -38292,7 +39085,14 @@ var FsObjectStore = class {
38292
39085
  }
38293
39086
  const bytes = await objectBodyToBytes(body);
38294
39087
  await mkdir(dirname(r.full), { recursive: true });
38295
- 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
+ }
38296
39096
  const st = await stat(r.full);
38297
39097
  return this.metaOf(r.key, st);
38298
39098
  }
@@ -38426,9 +39226,14 @@ import { spawn as nodeSpawn } from "child_process";
38426
39226
  var SHELL_OUTPUT_LIMIT_BYTES = 1024 * 1024;
38427
39227
 
38428
39228
  // src/objects.ts
38429
- import { mkdirSync } from "fs";
38430
- 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";
38431
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;
38432
39237
  function toInternal(key) {
38433
39238
  return `${INTERNAL_ROOT}/${key}`;
38434
39239
  }
@@ -38438,6 +39243,108 @@ function toExternal(key) {
38438
39243
  function externalMeta(meta) {
38439
39244
  return { ...meta, key: toExternal(meta.key) };
38440
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
+ }
38441
39348
  function createDataObjectStore(dataDir) {
38442
39349
  const root = join2(dataDir, INTERNAL_ROOT);
38443
39350
  mkdirSync(root, { recursive: true });
@@ -38448,11 +39355,77 @@ function createDataObjectStore(dataDir) {
38448
39355
  return meta === null ? null : externalMeta(meta);
38449
39356
  },
38450
39357
  async get(key) {
38451
- const got = await fs.get(toInternal(key));
38452
- 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 };
38453
39374
  },
38454
39375
  async put(key, body, opts) {
38455
- 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);
38456
39429
  },
38457
39430
  async delete(key) {
38458
39431
  await fs.delete(toInternal(key));
@@ -38462,9 +39435,24 @@ function createDataObjectStore(dataDir) {
38462
39435
  return {
38463
39436
  items: result.items.map(
38464
39437
  (item) => "prefix" in item ? { prefix: toExternal(item.prefix) } : externalMeta(item)
38465
- ),
39438
+ ).filter((item) => !isUploadTempKey("prefix" in item ? item.prefix : item.key)),
38466
39439
  ...result.cursor !== void 0 ? { cursor: result.cursor } : {}
38467
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);
38468
39456
  }
38469
39457
  };
38470
39458
  }
@@ -38587,6 +39575,29 @@ var PgStateStore = class {
38587
39575
  ON CONFLICT (key) DO UPDATE SET value = excluded.value
38588
39576
  `;
38589
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
+ }
38590
39601
  async putIfAbsent(key, value) {
38591
39602
  const result = await this.sql`
38592
39603
  INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
@@ -38769,6 +39780,20 @@ var SqliteStateStore = class {
38769
39780
  async put(key, value) {
38770
39781
  this.stmtPut.run(key, JSON.stringify(value));
38771
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
+ }
38772
39797
  async putIfAbsent(key, value) {
38773
39798
  const info = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
38774
39799
  return info.changes > 0;
@@ -38816,7 +39841,7 @@ import { join as join4 } from "path";
38816
39841
  // package.json
38817
39842
  var package_default = {
38818
39843
  name: "@tool-bridge/server",
38819
- version: "0.14.0",
39844
+ version: "0.16.0",
38820
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",
38821
39846
  type: "module",
38822
39847
  license: "MIT",
@@ -38889,7 +39914,7 @@ var package_default = {
38889
39914
 
38890
39915
  // src/assets.ts
38891
39916
  import { brotliCompressSync, gzipSync, constants as zlibConstants } from "zlib";
38892
- 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";
38893
39918
  import { readFile as readFile2 } from "fs/promises";
38894
39919
  import { createRequire } from "module";
38895
39920
  import { existsSync } from "fs";
@@ -38922,7 +39947,7 @@ function resolveUiDir(uiDirOverride) {
38922
39947
  try {
38923
39948
  const require2 = createRequire(import.meta.url);
38924
39949
  const pkgPath = require2.resolve("@tool-bridge/dashboard/package.json");
38925
- const dist = join3(dirname2(pkgPath), "dist");
39950
+ const dist = join3(dirname3(pkgPath), "dist");
38926
39951
  return existsSync(join3(dist, "index.html")) ? dist : void 0;
38927
39952
  } catch {
38928
39953
  return void 0;
@@ -38949,7 +39974,7 @@ function uiAssetsFetcher(uiDir) {
38949
39974
  let pathname = decodeURIComponent(url2.pathname);
38950
39975
  if (pathname.endsWith("/")) pathname += "index.html";
38951
39976
  const full = resolve2(join3(root, pathname));
38952
- if (full !== root && !full.startsWith(root + sep2)) {
39977
+ if (full !== root && !full.startsWith(root + sep3)) {
38953
39978
  return new Response("not found", { status: 404 });
38954
39979
  }
38955
39980
  let asset = cache.get(full);
@@ -39128,8 +40153,52 @@ function createTbServer(config) {
39128
40153
  if (config.toolCacheTtlSec !== void 0) deps.toolCacheTtlSec = config.toolCacheTtlSec;
39129
40154
  if (config.refThresholdBytes !== void 0) deps.refThresholdBytes = config.refThresholdBytes;
39130
40155
  if (config.refTtlSec !== void 0) deps.refTtlSec = config.refTtlSec;
40156
+ if (config.uploadGrantTtlSec !== void 0) {
40157
+ deps.uploadGrantTtlSec = config.uploadGrantTtlSec;
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
+ }
39131
40179
  const app = createTbApp(deps);
39132
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
+ };
39133
40202
  return {
39134
40203
  app,
39135
40204
  search,
@@ -39146,6 +40215,7 @@ function createTbServer(config) {
39146
40215
  await hub.sweepOrphans();
39147
40216
  return await new Promise((resolve3) => {
39148
40217
  server = serve({ fetch: app.fetch, port: config.port, hostname: config.host }, (info) => {
40218
+ scheduleStoreCleanup();
39149
40219
  resolve3({ port: info.port });
39150
40220
  });
39151
40221
  hub.attach(server);
@@ -39156,6 +40226,10 @@ function createTbServer(config) {
39156
40226
  },
39157
40227
  async close() {
39158
40228
  draining = true;
40229
+ if (storeCleanupTimer !== void 0) {
40230
+ clearInterval(storeCleanupTimer);
40231
+ storeCleanupTimer = void 0;
40232
+ }
39159
40233
  let closed;
39160
40234
  if (server !== void 0) {
39161
40235
  const s = server;