@tool-bridge/server 0.15.0 → 0.17.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 (115) hide show
  1. package/dist/{airtable-QYTOPRFV.js → airtable-NOSOMOVB.js} +106 -114
  2. package/dist/{alt_text_generator_ai-2CFYNSZV.js → alt_text_generator_ai-BHIJ4LW5.js} +25 -16
  3. package/dist/{amap-HGOBOHSZ.js → amap-QRLP4TWZ.js} +114 -123
  4. package/dist/{anthropic-3OXOQNSR.js → anthropic-IEOI36EV.js} +34 -56
  5. package/dist/{apify-PUV2QGKL.js → apify-3CCYTQVK.js} +31 -49
  6. package/dist/{brave_search-DG75AVKA.js → brave_search-PZNYDQG7.js} +52 -77
  7. package/dist/chunk-JXHMDBAY.js +45 -0
  8. package/dist/chunk-LMLX3DJF.js +50 -0
  9. package/dist/{chunk-DXXYNIXV.js → chunk-RVWZN3CI.js} +3337 -1440
  10. package/dist/{chunk-NLPTB3IL.js → chunk-TLNUIPFZ.js} +1475 -277
  11. package/dist/chunk-U5UHT7CX.js +977 -0
  12. package/dist/chunk-U67V476Y.js +35 -0
  13. package/dist/chunk-WW2IMPQK.js +283 -0
  14. package/dist/{chunk-NQ67PIIS.js → chunk-YASUMV2K.js} +4 -6
  15. package/dist/{circleci-EFNLX5AW.js → circleci-VSJ4O5FC.js} +36 -55
  16. package/dist/{clerk-UWDLMZVM.js → clerk-CC2JZBZI.js} +60 -80
  17. package/dist/{cloudflare_dns-WQXVDSPU.js → cloudflare_dns-PJEBSEYQ.js} +90 -113
  18. package/dist/{cohere-VPDIKUDP.js → cohere-KBACYSPF.js} +33 -49
  19. package/dist/{confluence-NBOOQNTR.js → confluence-2PX3O7G5.js} +79 -115
  20. package/dist/{convertapi-6Z6OP25T.js → convertapi-BVOTD2PJ.js} +13 -16
  21. package/dist/{currencyapi-CAMUWCTV.js → currencyapi-6ZQ2VNQ7.js} +30 -53
  22. package/dist/{deepseek-GRALXYXR.js → deepseek-MB3WVVTB.js} +30 -38
  23. package/dist/{docker_hub-5AASF3TJ.js → docker_hub-7CLQL2IJ.js} +141 -157
  24. package/dist/{dropbox-HS3VGRD3.js → dropbox-VFYDVUNK.js} +134 -141
  25. package/dist/{dub-KHERUTW6.js → dub-QN3EETJY.js} +61 -93
  26. package/dist/{e2b-4MGHPFXY.js → e2b-NKC3Y776.js} +39 -69
  27. package/dist/{exa-O77PKTBT.js → exa-KKZ6F25K.js} +49 -77
  28. package/dist/{fal_ai-TX2TZFMP.js → fal_ai-25LYJD2O.js} +37 -44
  29. package/dist/{feishu-I7VPWO3C.js → feishu-NRT55B3Q.js} +4 -6
  30. package/dist/{feishu_custom_bot-D4XZDLES.js → feishu_custom_bot-3W3XXUG4.js} +4 -5
  31. package/dist/{figma-SW3HZCZ5.js → figma-4FLP35TC.js} +56 -81
  32. package/dist/{firehydrant-ND5DEW3L.js → firehydrant-BLXPLZ3X.js} +40 -53
  33. package/dist/{fixer-ZTSV7ZK3.js → fixer-4OR62TGE.js} +31 -43
  34. package/dist/{fly-RZ7SZKFT.js → fly-MYCKOZZK.js} +50 -66
  35. package/dist/{front-Q5MZVUGX.js → front-3DFYVMF3.js} +43 -66
  36. package/dist/{geocodio-QPHHODHG.js → geocodio-CXMD6VKA.js} +37 -63
  37. package/dist/{ghost-3Q6O7EWZ.js → ghost-4UUE737W.js} +51 -57
  38. package/dist/{github-LDMGJHXV.js → github-WFS2ON2N.js} +48 -41
  39. package/dist/{gmail-6QBCI3TT.js → gmail-KW7FXA7A.js} +26 -36
  40. package/dist/{googlecalendar-LLSYY4ZJ.js → googlecalendar-AMH5JQMC.js} +57 -78
  41. package/dist/{googledocs-KRGVBGWY.js → googledocs-R5VA6CKD.js} +14 -22
  42. package/dist/{grafana-OHPZMRHT.js → grafana-IT6DWIVB.js} +95 -122
  43. package/dist/{graphhopper-2PBY7OX4.js → graphhopper-TYFDJZWC.js} +32 -46
  44. package/dist/index.d.ts +199 -106
  45. package/dist/index.js +4 -3
  46. package/dist/{ip2proxy-GPBQZKKG.js → ip2proxy-GJ4HXVXO.js} +35 -42
  47. package/dist/{ipgeolocation_io-YNTOLOFH.js → ipgeolocation_io-OAGR6PRF.js} +40 -60
  48. package/dist/{ipqualityscore-CZLQUAQ7.js → ipqualityscore-BCKAMDGL.js} +30 -42
  49. package/dist/{jina_ai-JIFEYH5C.js → jina_ai-N3NXRVIZ.js} +32 -58
  50. package/dist/{jira-2DKTIWHK.js → jira-CHLBKWLO.js} +116 -126
  51. package/dist/{langsmith-UCX7NHKP.js → langsmith-OIKOTPY3.js} +101 -121
  52. package/dist/{lemlist-IS6K2VJG.js → lemlist-VAKYPKS3.js} +57 -68
  53. package/dist/{linear-AK6547ZS.js → linear-Q6QTPJ5R.js} +109 -118
  54. package/dist/{logsnag-EB5LICOH.js → logsnag-SQOQBTES.js} +31 -48
  55. package/dist/main.js +103 -102
  56. package/dist/{mattermost-YOSWUA3R.js → mattermost-G2KBEICM.js} +45 -62
  57. package/dist/{mem0-IICTSHLB.js → mem0-D2FPLYDX.js} +72 -98
  58. package/dist/{memos-DIV6MZPB.js → memos-76PTWV5V.js} +68 -122
  59. package/dist/{metabase-ZGHNTCAT.js → metabase-TWSIDVBM.js} +47 -60
  60. package/dist/{mistral_ai-IAG3WZYO.js → mistral_ai-TP7TQUEJ.js} +35 -40
  61. package/dist/{mother_duck-PYKG4ZHO.js → mother_duck-UXY5THYU.js} +42 -62
  62. package/dist/{ngrok-BXN3DGTK.js → ngrok-RZW2ISJK.js} +42 -58
  63. package/dist/{notes-L2UO36HQ.js → notes-B2I6FWK2.js} +3 -5
  64. package/dist/{notion-PVHVOV4R.js → notion-QOKRUE6M.js} +83 -97
  65. package/dist/{open_exchange_rates-DVMOPFNQ.js → open_exchange_rates-ODLFWEJ7.js} +35 -39
  66. package/dist/{openai-VW6F5YTK.js → openai-ZPBPPPGT.js} +51 -55
  67. package/dist/{opengraph_io-MQUREYWM.js → opengraph_io-EOI5UJKT.js} +61 -79
  68. package/dist/{openrouter-5MI3ODCU.js → openrouter-BUWLF3BG.js} +41 -55
  69. package/dist/{outline-B2U7NIVA.js → outline-NXS3QRXO.js} +89 -104
  70. package/dist/{paddle-LDZTVYQO.js → paddle-T4MSKQ76.js} +34 -49
  71. package/dist/{perplexity-P7BNWFMH.js → perplexity-WW5N2LW7.js} +33 -50
  72. package/dist/{pinecone-3Y67EJYP.js → pinecone-RY5AZPQM.js} +65 -91
  73. package/dist/{pipedrive-4KFLGCJW.js → pipedrive-OHYQNZTU.js} +37 -59
  74. package/dist/{polar-NIULQPUD.js → polar-XZIAHXS5.js} +43 -53
  75. package/dist/{posthog-UXGCIHRP.js → posthog-DTPTWORY.js} +58 -77
  76. package/dist/{postmark-XT77YNQM.js → postmark-F4UCQKC3.js} +52 -71
  77. package/dist/{prerender-37E6ZN6Z.js → prerender-P3Y2MLIC.js} +32 -50
  78. package/dist/{pubmed-BO2W4IR6.js → pubmed-RHU3GXFB.js} +28 -60
  79. package/dist/{railway-5BACZNYC.js → railway-VB6SRLI6.js} +32 -39
  80. package/dist/{readwise-DXABSDKY.js → readwise-DIMSKJZR.js} +62 -78
  81. package/dist/{render-4MCO6RLA.js → render-XCCDCEEK.js} +48 -64
  82. package/dist/{replicate-IVEPY4EP.js → replicate-IUEOQ4JC.js} +41 -58
  83. package/dist/{resend-XJBJO46B.js → resend-N5MJZH5E.js} +27 -37
  84. package/dist/{rootly-R6QDVF3D.js → rootly-25T4NBTU.js} +64 -76
  85. package/dist/{scrapfly-E24XNN32.js → scrapfly-PK4XFXUA.js} +45 -51
  86. package/dist/{scrapingbee-QT3O334C.js → scrapingbee-ITPBMWQU.js} +25 -33
  87. package/dist/{screenshot_fyi-MJNFPUFV.js → screenshot_fyi-5U6ZYPKR.js} +29 -47
  88. package/dist/{semantic_scholar-BVKWS72R.js → semantic_scholar-BJGCI2MU.js} +37 -56
  89. package/dist/{sentry-52T5XARK.js → sentry-63IC263P.js} +66 -85
  90. package/dist/{shodan-RTOTTCX2.js → shodan-2WBIFHFN.js} +42 -53
  91. package/dist/{shopify-3RVWQDZ6.js → shopify-XLLJRAGM.js} +32 -49
  92. package/dist/{stripe-M3T2K3EW.js → stripe-WLVCY3UN.js} +4 -5
  93. package/dist/{supabase-UUUXAEYR.js → supabase-BKNZ35ER.js} +69 -88
  94. package/dist/{tavily-LVOZQQKJ.js → tavily-YFSDEUTL.js} +37 -62
  95. package/dist/{telegram-WJQPCKQT.js → telegram-FC35W74G.js} +168 -177
  96. package/dist/{telnyx-THKSKAI2.js → telnyx-MI32XZUG.js} +30 -50
  97. package/dist/{todoist-YTRGFFRC.js → todoist-4ZFJKZC6.js} +77 -89
  98. package/dist/{trello-QBK6WKN2.js → trello-UOGFNMK4.js} +108 -122
  99. package/dist/{turso-EA44ZKLR.js → turso-YTSTW3ZS.js} +52 -64
  100. package/dist/{twilio-GSW3V55S.js → twilio-XAJDFUQH.js} +48 -55
  101. package/dist/{umami-DVZ7XUYC.js → umami-ZVIUBPAI.js} +49 -69
  102. package/dist/{unsplash-ZWUWQ7OS.js → unsplash-IU3PLDNS.js} +35 -52
  103. package/dist/{upstash_redis-BQIQB5NM.js → upstash_redis-3EYJW7SY.js} +28 -40
  104. package/dist/{uptimerobot-2OJHTUPV.js → uptimerobot-PJG2ASRU.js} +11 -12
  105. package/dist/{v2ex-IZF4BB4O.js → v2ex-KFQWWGFC.js} +36 -61
  106. package/dist/{vercel-PI3BJENX.js → vercel-I2ZDOQRL.js} +124 -150
  107. package/dist/{webapi-VLM4IKVB.js → webapi-GW5QXWXE.js} +1 -1
  108. package/dist/{wolfram_alpha_api-EYQ2QCHH.js → wolfram_alpha_api-BNXOJMB6.js} +26 -34
  109. package/dist/{wordpress-ETDA6XLX.js → wordpress-36D7BSMP.js} +87 -104
  110. package/dist/{workos-D7INO4Y4.js → workos-6X4JJRRC.js} +37 -56
  111. package/dist/{zhihu-Q7FNHEDM.js → zhihu-CIXKCHIU.js} +35 -57
  112. package/package.json +5 -5
  113. package/dist/chunk-KH663T3A.js +0 -183
  114. package/dist/chunk-MLKGABMK.js +0 -9
  115. package/dist/chunk-UI4E4T5Q.js +0 -378
@@ -1,8 +1,12 @@
1
+ import {
2
+ createGuardedFetch
3
+ } from "./chunk-U5UHT7CX.js";
1
4
  import {
2
5
  AnnotationStore,
3
6
  CONTEXT_CAPABILITIES,
4
7
  CONTEXT_DIRECT_UPLOAD_CAPABILITY,
5
8
  DEFAULT_MAX_NODES,
9
+ DEFAULT_STORE_DRIVER_KEY_ROOT,
6
10
  DEVICE_CALL_TIMEOUT_MS,
7
11
  DeviceGatewaySession,
8
12
  FEEDBACK_HIDE_SCORE,
@@ -26,12 +30,12 @@ import {
26
30
  PG_SEARCH_TOOLS_TABLE,
27
31
  PG_SEARCH_WRITE_LOCK_KEY,
28
32
  PRESIGN_TTL_SEC_DEFAULT,
29
- PRESIGN_TTL_SEC_MAX,
30
33
  RemoteAllowlistStore,
31
34
  SKILLHUB_CAPABILITIES,
32
35
  SKRegistryStore,
33
36
  SecretStoreImpl,
34
37
  SqlSearchIndex,
38
+ StoreService,
35
39
  TBError,
36
40
  TB_ERROR_CODES,
37
41
  TOOL_SEARCH_AUDIT_NODE_LIMIT,
@@ -79,17 +83,31 @@ import {
79
83
  createObjectContextProvider,
80
84
  createObjectContextUploadGrant,
81
85
  createSkillhubProvider,
86
+ createStoreModule,
82
87
  decodeDeviceFrame,
83
88
  derivePresence,
84
89
  deviceDirectoryHelpModel,
85
90
  deviceFsHelpModel,
86
91
  deviceShellHelpModel,
87
92
  discriminatedUnion,
93
+ dispatchContextCmd,
94
+ dispatchContextUploadCmd,
95
+ dispatchSkillhubCmd,
88
96
  effectFor,
89
97
  email,
90
98
  encodeCallContext,
91
99
  encodeDeviceFrame,
92
100
  encodePluginCall,
101
+ external_exports,
102
+ feedbackDetailSchema,
103
+ feedbackListSchema,
104
+ feedbackRemoveResponseSchema,
105
+ feedbackSubmitRequestSchema,
106
+ feedbackSubmitResponseSchema,
107
+ feedbackViewSchema,
108
+ feedbackVoteRequestSchema,
109
+ healthResponseSchema,
110
+ helpJsonSchema,
93
111
  identify,
94
112
  intersection,
95
113
  isContextExpired,
@@ -98,23 +116,30 @@ import {
98
116
  iso_exports,
99
117
  lazy,
100
118
  literal,
119
+ livenessResponseSchema,
101
120
  looseObject,
102
121
  mintKey,
103
122
  negotiate,
104
- normalizeCanonicalOrigin,
123
+ nodeInputSchema,
105
124
  normalizeEntryPath,
106
125
  normalizePath,
107
126
  normalizeToolSearchLimit,
108
127
  normalizeToolSearchPath,
109
128
  normalizeUpstreamError,
110
129
  number,
130
+ oauthAuthorizeRequestSchema,
131
+ oauthAuthorizeResponseSchema,
111
132
  object,
112
133
  objectBodyToBytes,
113
134
  optional,
114
135
  optionalMethodsForCapabilities,
136
+ parseContextCmdArgs,
115
137
  parseCredentialValues,
116
138
  parseFeedbackEntries,
117
139
  parseNodeInput,
140
+ parseNonNegativeIntEnv,
141
+ parsePortEnv,
142
+ parseRuntimeEnv,
118
143
  parseTokenResponse,
119
144
  parseVia,
120
145
  pgBulkInsertSql,
@@ -122,7 +147,9 @@ import {
122
147
  pluginTokenSecretName,
123
148
  prefixUpperBound,
124
149
  preprocess,
150
+ readinessResponseSchema,
125
151
  record,
152
+ registryNodeSchema,
126
153
  renderHelpDsl,
127
154
  renderHelpJson,
128
155
  renderHelpMarkdown,
@@ -136,58 +163,41 @@ import {
136
163
  shouldRefresh,
137
164
  skillhubHelpModel,
138
165
  skillhubScopeForCmd,
166
+ storeUri,
139
167
  string,
168
+ tbErrorBodySchema,
140
169
  toJSONSchema,
141
170
  toolHelpModel,
171
+ toolSearchPageSchema,
172
+ toolSearchRequestSchema,
142
173
  toolSearchSnapshotDigest,
143
174
  toolsToHelpModel,
175
+ treeJsonSchema,
144
176
  union,
145
177
  unknown,
146
178
  url,
147
179
  validatePath,
148
180
  virtualizeTools
149
- } from "./chunk-DXXYNIXV.js";
181
+ } from "./chunk-RVWZN3CI.js";
150
182
 
151
183
  // src/config.ts
152
184
  var DEFAULT_PORT = 8787;
153
- var DEFAULT_MAX_HOPS = 4;
154
185
  var DEFAULT_DEVICE_RECLAIM_SEC = 24 * 60 * 60;
155
- function positiveIntEnv(value) {
156
- const n = Number(value);
157
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
158
- }
159
- function presignTtlEnv(value) {
160
- const ttl = positiveIntEnv(value);
161
- return ttl === void 0 ? void 0 : Math.min(ttl, PRESIGN_TTL_SEC_MAX);
162
- }
163
- function portEnv(value) {
164
- if (value === void 0 || value === "") return void 0;
165
- const n = Number(value);
166
- return Number.isInteger(n) && n >= 0 && n <= 65535 ? n : void 0;
167
- }
186
+ var DEFAULT_STORE_CLEANUP_INTERVAL_SEC = 15 * 60;
168
187
  function configFromEnv(env = process.env) {
169
- const allowInsecure = env.TB_ALLOW_INSECURE_HTTP === "true";
170
- const allowlist = (env.TB_REMOTE_ALLOWLIST ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
188
+ const runtime = parseRuntimeEnv(env);
171
189
  const config = {
190
+ ...runtime,
172
191
  // TB_PORT 优先,PORT 兜底:Railway / Fly / Cloud Run / CF Container 等 PaaS 只注入
173
192
  // PORT,不认识 TB_PORT —— 不兜底的话容器会监听 8787 而平台探活另一个端口,部署直接失败。
174
- port: portEnv(env.TB_PORT) ?? portEnv(env.PORT) ?? DEFAULT_PORT,
193
+ port: parsePortEnv(env.TB_PORT) ?? parsePortEnv(env.PORT) ?? DEFAULT_PORT,
175
194
  host: env.TB_HOST !== void 0 && env.TB_HOST.length > 0 ? env.TB_HOST : "0.0.0.0",
176
195
  dataDir: env.TB_DATA_DIR !== void 0 && env.TB_DATA_DIR.length > 0 ? env.TB_DATA_DIR : "./data",
177
- allowInsecureHttp: allowInsecure,
178
196
  allowInsecureBootstrap: env.TB_ALLOW_INSECURE_BOOTSTRAP === "true",
179
- remote: {
180
- allowlist,
181
- maxHops: positiveIntEnv(env.TB_MAX_HOPS) ?? DEFAULT_MAX_HOPS,
182
- ...env.TB_INSTANCE_ID !== void 0 && env.TB_INSTANCE_ID.length > 0 ? { instanceId: env.TB_INSTANCE_ID } : {},
183
- allowInsecure
184
- },
185
- deviceReclaimSec: positiveIntEnv(env.TB_DEVICE_RECLAIM_SEC) ?? DEFAULT_DEVICE_RECLAIM_SEC,
186
- // 0 合法(立即关停,本地/单机默认);positiveIntEnv 拒 0,故单独解析。
187
- shutdownDrainSec: (() => {
188
- const n = Number(env.TB_SHUTDOWN_DRAIN_SEC);
189
- return Number.isInteger(n) && n >= 0 ? n : 0;
190
- })()
197
+ deviceReclaimSec: runtime.deviceReclaimSec ?? DEFAULT_DEVICE_RECLAIM_SEC,
198
+ storeCleanupIntervalSec: runtime.storeCleanupIntervalSec ?? DEFAULT_STORE_CLEANUP_INTERVAL_SEC,
199
+ // 0 合法(立即关停,本地/单机默认)
200
+ shutdownDrainSec: parseNonNegativeIntEnv(env.TB_SHUTDOWN_DRAIN_SEC) ?? 0
191
201
  };
192
202
  if (env.TB_UI_DIR !== void 0 && env.TB_UI_DIR.length > 0) config.uiDir = env.TB_UI_DIR;
193
203
  if (env.TB_DATABASE_URL !== void 0 && env.TB_DATABASE_URL.length > 0) {
@@ -224,19 +234,9 @@ function configFromEnv(env = process.env) {
224
234
  if (env.TB_BOOTSTRAP_ADMIN_SK !== void 0 && env.TB_BOOTSTRAP_ADMIN_SK.length > 0) {
225
235
  config.adminSk = env.TB_BOOTSTRAP_ADMIN_SK;
226
236
  }
227
- const canonicalOrigin = normalizeCanonicalOrigin(env.TB_CANONICAL_ORIGIN);
228
- if (canonicalOrigin !== void 0) config.canonicalOrigin = canonicalOrigin;
229
237
  if (env.TB_SECRET_ENCRYPTION_KEY !== void 0 && env.TB_SECRET_ENCRYPTION_KEY.length > 0) {
230
238
  config.encryptionKey = env.TB_SECRET_ENCRYPTION_KEY;
231
239
  }
232
- const ttl = positiveIntEnv(env.TB_TOOL_CACHE_TTL);
233
- if (ttl !== void 0) config.toolCacheTtlSec = ttl;
234
- const refThreshold = positiveIntEnv(env.TB_REF_THRESHOLD_BYTES);
235
- if (refThreshold !== void 0) config.refThresholdBytes = refThreshold;
236
- const refTtl = presignTtlEnv(env.TB_REF_TTL_SEC);
237
- if (refTtl !== void 0) config.refTtlSec = refTtl;
238
- const uploadGrantTtl = presignTtlEnv(env.TB_UPLOAD_GRANT_TTL_SEC);
239
- if (uploadGrantTtl !== void 0) config.uploadGrantTtlSec = uploadGrantTtl;
240
240
  return config;
241
241
  }
242
242
 
@@ -439,7 +439,8 @@ var BUILTIN_MODULES = [
439
439
  "plugin",
440
440
  "catalog",
441
441
  "federation",
442
- "annotation"
442
+ "annotation",
443
+ "store"
443
444
  ];
444
445
  var BUILTIN_DESCRIPTIONS = {
445
446
  sk: "SecretKey registry",
@@ -449,7 +450,8 @@ var BUILTIN_DESCRIPTIONS = {
449
450
  plugin: "Plugin registry (external plugins)",
450
451
  catalog: "Built-in integration catalog (read-only)",
451
452
  federation: "Remote federation host allowlist",
452
- annotation: "Admin notes shown in ~help of any path"
453
+ annotation: "Admin notes shown in ~help of any path",
454
+ store: "Deployment-level private object Store"
453
455
  };
454
456
  async function mintAdminWithPlaintext(store, plaintext, now) {
455
457
  const { key } = await mintKey(adminBootstrapInput(), now);
@@ -554,10 +556,14 @@ async function presignS3Url(client, url2, ttlSec) {
554
556
  }
555
557
  async function presignS3Put(client, url2, ttlSec, opts) {
556
558
  assertPresignTtlSec(ttlSec);
559
+ if ("contentLength" in opts && (!Number.isSafeInteger(opts.contentLength) || opts.contentLength < 0)) {
560
+ throw new TBError("invalid_argument", "presigned PUT contentLength \u5FC5\u987B\u662F\u975E\u8D1F\u5B89\u5168\u6574\u6570");
561
+ }
557
562
  const target = new URL(url2);
558
563
  target.searchParams.set("X-Amz-Expires", String(ttlSec));
559
564
  const headers = {
560
565
  "content-type": opts.contentType,
566
+ ..."contentLength" in opts ? { "content-length": String(opts.contentLength) } : {},
561
567
  ...opts.ifNoneMatch === void 0 ? {} : { "if-none-match": opts.ifNoneMatch }
562
568
  };
563
569
  const signed = await client.sign(
@@ -567,6 +573,48 @@ async function presignS3Put(client, url2, ttlSec, opts) {
567
573
  return { method: "PUT", url: signed.url, headers };
568
574
  }
569
575
 
576
+ // ../app/src/objectBodyStream.ts
577
+ function toWebObjectBodyStream(source, options = {}) {
578
+ const reader = source.getReader();
579
+ let released = false;
580
+ const release = () => {
581
+ if (released) return;
582
+ released = true;
583
+ reader.releaseLock();
584
+ };
585
+ const strategy = options.highWaterMark === void 0 ? void 0 : { highWaterMark: options.highWaterMark };
586
+ return new ReadableStream({
587
+ async pull(controller) {
588
+ try {
589
+ const { done, value } = await reader.read();
590
+ if (done) {
591
+ release();
592
+ controller.close();
593
+ } else if (value !== void 0) {
594
+ controller.enqueue(value);
595
+ }
596
+ } catch (error) {
597
+ try {
598
+ release();
599
+ } catch {
600
+ }
601
+ controller.error(error);
602
+ }
603
+ },
604
+ async cancel(reason) {
605
+ try {
606
+ if (reader.cancel !== void 0) await reader.cancel(reason);
607
+ else await source.cancel?.(reason);
608
+ } finally {
609
+ try {
610
+ release();
611
+ } catch {
612
+ }
613
+ }
614
+ }
615
+ }, strategy);
616
+ }
617
+
570
618
  // ../app/src/providers/s3Object.ts
571
619
  var META_HEADER_PREFIX = "x-amz-meta-";
572
620
  function stripEtagQuotes(etag) {
@@ -600,14 +648,17 @@ function createS3ObjectStore(cfg, opts) {
600
648
  accessKeyId: cfg.accessKeyId,
601
649
  secretAccessKey: cfg.secretAccessKey,
602
650
  service: "s3",
603
- region: cfg.region ?? "auto"
651
+ region: cfg.region ?? "auto",
652
+ // Store 的重试由上层按操作幂等性统一决定。传输层不得自动重放写入,
653
+ // 也不能让读/删路径悄悄继承 aws4fetch 的默认 10 次重试。
654
+ retries: 0
604
655
  });
605
656
  const base = cfg.endpoint.replace(/\/+$/, "");
606
657
  const bucketUrl = `${base}/${cfg.bucket}`;
607
658
  const urlFor = (key) => `${bucketUrl}/${encodeObjectKey(key)}`;
608
- const s3Fetch = async (url2, init) => {
659
+ const s3FetchWith = async (aws, url2, init) => {
609
660
  try {
610
- return await client.fetch(url2, init);
661
+ return await aws.fetch(url2, init);
611
662
  } catch (err) {
612
663
  throw normalizeUpstreamError({
613
664
  kind: "network",
@@ -615,6 +666,16 @@ function createS3ObjectStore(cfg, opts) {
615
666
  });
616
667
  }
617
668
  };
669
+ const s3Fetch = async (url2, init) => {
670
+ return await s3FetchWith(client, url2, init);
671
+ };
672
+ const fetchBody = (body) => {
673
+ if (typeof body === "string" || body instanceof ArrayBuffer) return body;
674
+ if (body instanceof Uint8Array) {
675
+ return body.buffer instanceof ArrayBuffer ? new Uint8Array(body.buffer, body.byteOffset, body.byteLength) : new Uint8Array(body);
676
+ }
677
+ return toWebObjectBodyStream(body, { highWaterMark: 0 });
678
+ };
618
679
  const metaFromHeaders = (key, headers) => {
619
680
  const metadata = {};
620
681
  headers.forEach((value, name) => {
@@ -633,13 +694,14 @@ function createS3ObjectStore(cfg, opts) {
633
694
  if (contentType !== null) meta.contentType = contentType;
634
695
  return meta;
635
696
  };
697
+ const headObject = async (key) => {
698
+ const resp = await s3Fetch(urlFor(key), { method: "HEAD" });
699
+ if (resp.status === 404) return null;
700
+ if (!resp.ok) throw s3Error("head", resp.status);
701
+ return metaFromHeaders(key, resp.headers);
702
+ };
636
703
  return {
637
- async head(key) {
638
- const resp = await s3Fetch(urlFor(key), { method: "HEAD" });
639
- if (resp.status === 404) return null;
640
- if (!resp.ok) throw s3Error("head", resp.status);
641
- return metaFromHeaders(key, resp.headers);
642
- },
704
+ head: headObject,
643
705
  async get(key) {
644
706
  const resp = await s3Fetch(urlFor(key), { method: "GET" });
645
707
  if (resp.status === 404) {
@@ -653,27 +715,31 @@ function createS3ObjectStore(cfg, opts) {
653
715
  return { meta: metaFromHeaders(key, resp.headers), body: resp.body ?? EMPTY_STREAM };
654
716
  },
655
717
  async put(key, body, putOpts) {
656
- const bytes = await objectBodyToBytes(body);
657
718
  const headers = {};
719
+ headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
658
720
  if (putOpts?.contentType !== void 0) headers["content-type"] = putOpts.contentType;
659
721
  for (const [name, value] of Object.entries(putOpts?.metadata ?? {})) {
660
722
  headers[`${META_HEADER_PREFIX}${name}`] = value;
661
723
  }
662
724
  if (putOpts?.ifMatchEtag !== void 0) headers["if-match"] = `"${putOpts.ifMatchEtag}"`;
663
- const resp = await s3Fetch(urlFor(key), { method: "PUT", headers, body: bytes });
725
+ if (putOpts?.ifNoneMatch !== void 0) headers["if-none-match"] = putOpts.ifNoneMatch;
726
+ const resp = await s3Fetch(urlFor(key), {
727
+ method: "PUT",
728
+ headers,
729
+ body: fetchBody(body)
730
+ });
664
731
  await resp.body?.cancel();
665
- if (resp.status === 412 || putOpts?.ifMatchEtag !== void 0 && resp.status === 404) {
666
- throw new TBError("conflict", `etag \u4E0D\u5339\u914D:'${key}'`);
732
+ const conditional = putOpts?.ifMatchEtag !== void 0 || putOpts?.ifNoneMatch !== void 0;
733
+ if (conditional && (resp.status === 409 || resp.status === 412) || putOpts?.ifMatchEtag !== void 0 && resp.status === 404) {
734
+ throw new TBError("conflict", `\u5BF9\u8C61\u6761\u4EF6\u5199\u51B2\u7A81:'${key}'`);
667
735
  }
668
736
  if (!resp.ok) throw s3Error("put", resp.status);
669
- const meta = {
670
- key,
671
- etag: stripEtagQuotes(resp.headers.get("etag") ?? ""),
672
- size: bytes.byteLength,
673
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
674
- metadata: putOpts?.metadata ?? {}
675
- };
676
- if (putOpts?.contentType !== void 0) meta.contentType = putOpts.contentType;
737
+ const meta = await headObject(key);
738
+ if (meta === null) {
739
+ throw new TBError("unavailable", "s3 put succeeded but HEAD did not observe the object", {
740
+ retryable: true
741
+ });
742
+ }
677
743
  return meta;
678
744
  },
679
745
  async delete(key) {
@@ -730,6 +796,9 @@ function createS3ObjectStore(cfg, opts) {
730
796
  },
731
797
  presignPut(key, ttlSec, putOpts) {
732
798
  return presignS3Put(client, urlFor(key), ttlSec, putOpts);
799
+ },
800
+ presignPutExact(key, ttlSec, putOpts) {
801
+ return presignS3Put(client, urlFor(key), ttlSec, putOpts);
733
802
  }
734
803
  };
735
804
  }
@@ -1029,6 +1098,443 @@ var SearchSynchronizer = class {
1029
1098
  // ../app/src/deps.ts
1030
1099
  var TOOL_CACHE_TTL_DEFAULT = 300;
1031
1100
 
1101
+ // ../app/src/storeRefToken.ts
1102
+ async function hmacKey(secret) {
1103
+ const digest = await crypto.subtle.digest(
1104
+ "SHA-256",
1105
+ new TextEncoder().encode(`tb-store-read-ref:v1:${secret}`)
1106
+ );
1107
+ return crypto.subtle.importKey("raw", digest, { name: "HMAC", hash: "SHA-256" }, false, [
1108
+ "sign",
1109
+ "verify"
1110
+ ]);
1111
+ }
1112
+ async function signStoreRefToken(payload, secret) {
1113
+ const body = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
1114
+ const mac = await crypto.subtle.sign(
1115
+ "HMAC",
1116
+ await hmacKey(secret),
1117
+ new TextEncoder().encode(body)
1118
+ );
1119
+ return `${body}.${base64urlEncode(new Uint8Array(mac))}`;
1120
+ }
1121
+ async function verifyStoreRefToken(token, secret) {
1122
+ const dot = token.indexOf(".");
1123
+ if (dot <= 0) return null;
1124
+ const body = token.slice(0, dot);
1125
+ let signature;
1126
+ try {
1127
+ signature = base64urlDecode(token.slice(dot + 1));
1128
+ } catch {
1129
+ return null;
1130
+ }
1131
+ const valid = await crypto.subtle.verify(
1132
+ "HMAC",
1133
+ await hmacKey(secret),
1134
+ signature,
1135
+ new TextEncoder().encode(body)
1136
+ );
1137
+ if (!valid) return null;
1138
+ try {
1139
+ const payload = JSON.parse(
1140
+ new TextDecoder().decode(base64urlDecode(body))
1141
+ );
1142
+ if (payload.v !== 1 || typeof payload.objectId !== "string" || payload.objectId.length < 1 || typeof payload.exp !== "number" || !Number.isSafeInteger(payload.exp)) return null;
1143
+ return payload;
1144
+ } catch {
1145
+ return null;
1146
+ }
1147
+ }
1148
+
1149
+ // ../app/src/store.ts
1150
+ var STORE_CALL_CAPABILITY_HEADER = "x-tb-store-capability";
1151
+ var STORE_UPLOAD_HEADER = "x-tb-store-upload";
1152
+ var KEY_STORE_TOKEN_SECRET = "sys:store-token-secret:v1";
1153
+ var KEY_STORE_CLEANUP_PROGRESS = "sys:store-cleanup-progress:v1";
1154
+ var STORE_READ_TTL_SEC_DEFAULT = 15 * 60;
1155
+ var STORE_CALL_MAX_OBJECTS_DEFAULT = 4;
1156
+ var STORE_CALL_MAX_OBJECT_BYTES_DEFAULT = 256 * 1024 * 1024;
1157
+ var STORE_CALL_MAX_BYTES_DEFAULT = 512 * 1024 * 1024;
1158
+ function positiveInt(value, fallback, field) {
1159
+ const actual = value ?? fallback;
1160
+ if (!Number.isSafeInteger(actual) || actual < 1) {
1161
+ throw new TBError("invalid_argument", `${field} must be a positive integer`);
1162
+ }
1163
+ return actual;
1164
+ }
1165
+ function randomSecret() {
1166
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
1167
+ let out = "";
1168
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
1169
+ return out;
1170
+ }
1171
+ async function derivedTokenSecret(root) {
1172
+ const digest = await crypto.subtle.digest(
1173
+ "SHA-256",
1174
+ new TextEncoder().encode(`tb-store-capability-root:v1:${root}`)
1175
+ );
1176
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1177
+ }
1178
+ async function storeTokenSecret(state, configured) {
1179
+ if (configured !== void 0) {
1180
+ if (configured.length < 16) {
1181
+ throw new TBError("invalid_argument", "storeTokenSecret must contain at least 16 characters");
1182
+ }
1183
+ return await derivedTokenSecret(configured);
1184
+ }
1185
+ const current = await state.get(KEY_STORE_TOKEN_SECRET);
1186
+ if (typeof current === "string" && current.length >= 16) return current;
1187
+ if (current !== null) {
1188
+ throw new TBError("internal", "default Store token secret state is invalid", {
1189
+ retryable: false
1190
+ });
1191
+ }
1192
+ if (state.compareAndSwap === void 0) {
1193
+ throw new TBError("unavailable", "default Store requires StateStore.compareAndSwap", {
1194
+ retryable: false
1195
+ });
1196
+ }
1197
+ const candidate = randomSecret();
1198
+ if (await state.compareAndSwap(KEY_STORE_TOKEN_SECRET, null, candidate)) return candidate;
1199
+ const winner = await state.get(KEY_STORE_TOKEN_SECRET);
1200
+ if (typeof winner !== "string" || winner.length < 16) {
1201
+ throw new TBError("unavailable", "default Store token secret initialization conflicted", {
1202
+ retryable: true
1203
+ });
1204
+ }
1205
+ return winner;
1206
+ }
1207
+ function serviceOptions(deps, tokenSecret) {
1208
+ return {
1209
+ tokenSecret,
1210
+ ...deps.storeMaxObjectBytes === void 0 ? {} : { maxObjectBytes: deps.storeMaxObjectBytes },
1211
+ ...deps.storeRelayMaxBytes === void 0 ? {} : { relayMaxBytes: deps.storeRelayMaxBytes },
1212
+ ...deps.storeUploadTtlSec === void 0 ? {} : { uploadTtlSec: deps.storeUploadTtlSec },
1213
+ ...deps.storeShareTtlSec === void 0 ? {} : { shareTtlSec: deps.storeShareTtlSec }
1214
+ };
1215
+ }
1216
+ async function defaultStoreRuntime(deps) {
1217
+ if (deps.objects === void 0) {
1218
+ throw new TBError("unavailable", "default Store object driver is not configured", {
1219
+ retryable: false
1220
+ });
1221
+ }
1222
+ const [objects, tokenSecret] = await Promise.all([
1223
+ deps.objects(),
1224
+ storeTokenSecret(deps.state, deps.storeTokenSecret ?? deps.encryptionKey)
1225
+ ]);
1226
+ return {
1227
+ objects,
1228
+ tokenSecret,
1229
+ service: new StoreService(deps.state, objects, serviceOptions(deps, tokenSecret))
1230
+ };
1231
+ }
1232
+ function resolveStoreRequestOrigin(rawRequestOrigin, canonicalOrigin) {
1233
+ const raw = rawRequestOrigin ?? canonicalOrigin;
1234
+ if (raw === void 0) {
1235
+ throw new TBError("unavailable", "request origin is unavailable", { retryable: false });
1236
+ }
1237
+ let url2;
1238
+ try {
1239
+ url2 = new URL(raw);
1240
+ } catch {
1241
+ throw new TBError("unavailable", "request origin is invalid", { retryable: false });
1242
+ }
1243
+ if (url2.protocol !== "https:" && url2.protocol !== "http:" || url2.username !== "" || url2.password !== "") {
1244
+ throw new TBError("unavailable", "request origin is invalid", { retryable: false });
1245
+ }
1246
+ if (rawRequestOrigin !== void 0 && canonicalOrigin !== void 0) {
1247
+ let canonical;
1248
+ try {
1249
+ canonical = new URL(canonicalOrigin);
1250
+ } catch {
1251
+ throw new TBError("unavailable", "canonical origin is invalid", { retryable: false });
1252
+ }
1253
+ if (canonical.protocol !== "https:" && canonical.protocol !== "http:" || canonical.username !== "" || canonical.password !== "") {
1254
+ throw new TBError("unavailable", "canonical origin is invalid", { retryable: false });
1255
+ }
1256
+ url2.protocol = canonical.protocol;
1257
+ }
1258
+ return url2.origin;
1259
+ }
1260
+ function requestOrigin(runtime, deps) {
1261
+ return resolveStoreRequestOrigin(runtime?.requestOrigin, deps.canonicalOrigin);
1262
+ }
1263
+ function storeUploadGrant(start, origin) {
1264
+ const useRelay = start.alreadyCompleted || start.signedRequest === void 0;
1265
+ const url2 = useRelay ? `${origin}/~store/uploads/${encodeURIComponent(start.uploadId)}` : start.signedRequest.url;
1266
+ const headers = useRelay ? { [STORE_UPLOAD_HEADER]: start.uploadToken } : start.signedRequest.headers;
1267
+ return {
1268
+ uploadId: start.uploadId,
1269
+ objectUri: start.objectUri,
1270
+ transport: useRelay ? "relay" : "presigned-put",
1271
+ method: "PUT",
1272
+ url: url2,
1273
+ headers,
1274
+ expiresAt: start.expiresAt,
1275
+ maxBytes: start.maxBytes,
1276
+ uploadToken: start.uploadToken,
1277
+ ...start.alreadyCompleted && start.descriptor !== void 0 ? { alreadyCompleted: true, descriptor: start.descriptor } : {}
1278
+ };
1279
+ }
1280
+ function readTtlSec(deps) {
1281
+ return positiveInt(
1282
+ deps.storeReadTtlSec,
1283
+ deps.refTtlSec ?? STORE_READ_TTL_SEC_DEFAULT,
1284
+ "storeReadTtlSec"
1285
+ );
1286
+ }
1287
+ async function ownerReadGrant(object2, runtime, deps, tokenSecret) {
1288
+ if (object2.status !== "ready" || object2.size === void 0) {
1289
+ throw TBError.notFound("Store object not found");
1290
+ }
1291
+ const ttlSec = readTtlSec(deps);
1292
+ const expiresAt = new Date(Date.now() + ttlSec * 1e3).toISOString();
1293
+ const token = await signStoreRefToken({
1294
+ v: 1,
1295
+ objectId: object2.id,
1296
+ exp: Math.floor(Date.parse(expiresAt) / 1e3)
1297
+ }, tokenSecret);
1298
+ return {
1299
+ $ref: `${requestOrigin(runtime, deps)}/~store/refs/${encodeURIComponent(token)}`,
1300
+ uri: storeUri(object2.id),
1301
+ contentType: object2.contentType,
1302
+ size: object2.size,
1303
+ expiresAt
1304
+ };
1305
+ }
1306
+ function shareReadGrant(result, runtime, deps) {
1307
+ return {
1308
+ shareId: result.shareId,
1309
+ uri: result.uri,
1310
+ expiresAt: result.expiresAt,
1311
+ $ref: `${requestOrigin(runtime, deps)}/~store/shares/${encodeURIComponent(result.token)}`
1312
+ };
1313
+ }
1314
+ function defaultStoreModuleDeps(deps, store) {
1315
+ return {
1316
+ service: store.service,
1317
+ callbacks: {
1318
+ createUpload: (start, runtime) => storeUploadGrant(start, requestOrigin(runtime, deps)),
1319
+ read: async (object2, runtime) => await ownerReadGrant(
1320
+ object2,
1321
+ runtime,
1322
+ deps,
1323
+ store.tokenSecret
1324
+ ),
1325
+ share: (result, runtime) => shareReadGrant(result, runtime, deps)
1326
+ }
1327
+ };
1328
+ }
1329
+ function lazyDefaultStoreModule(deps) {
1330
+ const helpOnly = createStoreModule({
1331
+ // createStoreModule.help is static and never reads these placeholders.
1332
+ service: void 0,
1333
+ callbacks: {
1334
+ createUpload: () => {
1335
+ throw new TBError("unavailable", "default Store is unavailable");
1336
+ },
1337
+ read: () => {
1338
+ throw new TBError("unavailable", "default Store is unavailable");
1339
+ },
1340
+ share: () => {
1341
+ throw new TBError("unavailable", "default Store is unavailable");
1342
+ }
1343
+ }
1344
+ });
1345
+ return {
1346
+ module: helpOnly.module,
1347
+ description: helpOnly.description,
1348
+ help: (path) => helpOnly.help(path),
1349
+ async dispatch(cmd, args, ctx, runtime) {
1350
+ const store = await defaultStoreRuntime(deps);
1351
+ return await createStoreModule(defaultStoreModuleDeps(deps, store)).dispatch(
1352
+ cmd,
1353
+ args,
1354
+ ctx,
1355
+ runtime
1356
+ );
1357
+ }
1358
+ };
1359
+ }
1360
+ async function beginCapabilityUpload(deps, input, capabilityToken, origin) {
1361
+ const store = await defaultStoreRuntime(deps);
1362
+ const start = await store.service.beginCallUpload(input, capabilityToken);
1363
+ return storeUploadGrant(start, origin);
1364
+ }
1365
+ async function completeCapabilityUpload(deps, uploadId2, uploadToken) {
1366
+ return await (await defaultStoreRuntime(deps)).service.completeUploadWithToken(
1367
+ uploadId2,
1368
+ uploadToken
1369
+ );
1370
+ }
1371
+ async function abortCapabilityUpload(deps, uploadId2, uploadToken) {
1372
+ return await (await defaultStoreRuntime(deps)).service.abortUploadWithToken(
1373
+ uploadId2,
1374
+ uploadToken
1375
+ );
1376
+ }
1377
+ async function relayStoreUpload(deps, uploadId2, uploadToken, body) {
1378
+ const store = await defaultStoreRuntime(deps);
1379
+ const session = await store.service.verifyUploadToken(uploadToken);
1380
+ if (session.id !== uploadId2) {
1381
+ throw new TBError("permission_denied", "upload capability does not match the URL");
1382
+ }
1383
+ return await store.service.commitRelayUpload({
1384
+ uploadToken,
1385
+ body: body ?? new Uint8Array()
1386
+ });
1387
+ }
1388
+ async function storeObjectResponse(objects, object2) {
1389
+ const got = await objects.get(object2.driverKey);
1390
+ if (got === null) throw TBError.notFound("not found");
1391
+ const body = toWebObjectBodyStream(got.body);
1392
+ return new Response(body, {
1393
+ headers: {
1394
+ "content-type": object2.contentType,
1395
+ "content-length": String(got.meta.size),
1396
+ "cache-control": "private, no-store"
1397
+ }
1398
+ });
1399
+ }
1400
+ async function issueDeviceCallUpload(deps, deviceId, callId, context) {
1401
+ if (deps.objects === void 0) return null;
1402
+ const store = await defaultStoreRuntime(deps);
1403
+ const maxObjects = positiveInt(
1404
+ deps.storeCallMaxObjects,
1405
+ STORE_CALL_MAX_OBJECTS_DEFAULT,
1406
+ "storeCallMaxObjects"
1407
+ );
1408
+ const maxBytes = positiveInt(
1409
+ deps.storeCallMaxBytes,
1410
+ STORE_CALL_MAX_BYTES_DEFAULT,
1411
+ "storeCallMaxBytes"
1412
+ );
1413
+ const maxObjectBytes = Math.min(
1414
+ positiveInt(
1415
+ deps.storeCallMaxObjectBytes,
1416
+ deps.storeMaxObjectBytes ?? STORE_CALL_MAX_OBJECT_BYTES_DEFAULT,
1417
+ "storeCallMaxObjectBytes"
1418
+ ),
1419
+ maxBytes
1420
+ );
1421
+ const issued = await store.service.issueCallUploadCapability({
1422
+ callId,
1423
+ owner: context.caller.owner,
1424
+ producer: `device:${deviceId}`,
1425
+ expiresAt: context.expiresAt,
1426
+ maxObjects,
1427
+ maxBytes,
1428
+ maxObjectBytes,
1429
+ allowedContentTypes: deps.storeCallAllowedContentTypes ?? ["*/*"]
1430
+ });
1431
+ return {
1432
+ context: {
1433
+ ...context,
1434
+ upload: {
1435
+ token: issued.token,
1436
+ expiresAt: issued.capability.expiresAt,
1437
+ // The SDK uses this as an early per-object check; the server remains
1438
+ // authoritative for the aggregate call budget.
1439
+ maxBytes: issued.capability.maxObjectBytes,
1440
+ maxObjects: issued.capability.maxObjects
1441
+ }
1442
+ },
1443
+ revoke: async () => await store.service.revokeCallUploadCapability(issued.token)
1444
+ };
1445
+ }
1446
+ function cleanupProgress(value) {
1447
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1448
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1449
+ }
1450
+ const record2 = value;
1451
+ if (!Number.isSafeInteger(record2.revision) || record2.revision < 1) {
1452
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1453
+ }
1454
+ const cursors = record2.cursors;
1455
+ if (cursors === null || typeof cursors !== "object" || Array.isArray(cursors)) {
1456
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1457
+ }
1458
+ for (const field of [
1459
+ "uploads",
1460
+ "objects",
1461
+ "shares",
1462
+ "callCapabilities",
1463
+ "idempotencyBindings"
1464
+ ]) {
1465
+ const cursor = cursors[field];
1466
+ if (cursor !== null && typeof cursor !== "string") {
1467
+ throw new TBError("internal", "default Store cleanup progress is invalid");
1468
+ }
1469
+ }
1470
+ return value;
1471
+ }
1472
+ function emptyCleanupResult() {
1473
+ return {
1474
+ abandonedObjects: 0,
1475
+ deletedBytes: 0,
1476
+ deletedStaging: 0,
1477
+ expiredCallCapabilities: 0,
1478
+ expiredIdempotencyBindings: 0,
1479
+ expiredShares: 0,
1480
+ expiredUploads: 0
1481
+ };
1482
+ }
1483
+ function addCleanupResult(target, page) {
1484
+ target.abandonedObjects += page.abandonedObjects;
1485
+ target.deletedBytes += page.deletedBytes;
1486
+ target.deletedStaging += page.deletedStaging;
1487
+ target.expiredCallCapabilities += page.expiredCallCapabilities;
1488
+ target.expiredIdempotencyBindings += page.expiredIdempotencyBindings;
1489
+ target.expiredShares += page.expiredShares;
1490
+ target.expiredUploads += page.expiredUploads;
1491
+ }
1492
+ async function cleanupDefaultStore(deps, opts = {}) {
1493
+ await deps.ensureReady?.();
1494
+ const store = await defaultStoreRuntime(deps);
1495
+ const maxPages = Math.min(positiveInt(opts.maxPages, 8, "maxPages"), 64);
1496
+ const limit = opts.limit === void 0 ? void 0 : positiveInt(opts.limit, 200, "limit");
1497
+ const rawProgress = await deps.state.get(KEY_STORE_CLEANUP_PROGRESS);
1498
+ let progress = rawProgress === null ? null : cleanupProgress(rawProgress);
1499
+ let cursors = progress?.cursors;
1500
+ const aggregate = emptyCleanupResult();
1501
+ for (let pageNumber = 0; pageNumber < maxPages; pageNumber++) {
1502
+ const page = await store.service.cleanup({
1503
+ ...limit === void 0 ? {} : { limit },
1504
+ ...cursors === void 0 ? {} : { cursors },
1505
+ // staging 不使用权威状态 cursor;同一 host tick 只运行一次,避免
1506
+ // maxPages 内重复扫描同一 driver maintenance 范围。
1507
+ runDriverMaintenance: pageNumber === 0
1508
+ });
1509
+ addCleanupResult(aggregate, page);
1510
+ if (page.cursors === void 0) {
1511
+ if (progress !== null) {
1512
+ await deps.state.compareAndSwap(
1513
+ KEY_STORE_CLEANUP_PROGRESS,
1514
+ progress.revision,
1515
+ null
1516
+ );
1517
+ }
1518
+ return aggregate;
1519
+ }
1520
+ const next = {
1521
+ cursors: page.cursors,
1522
+ revision: (progress?.revision ?? 0) + 1
1523
+ };
1524
+ const advanced = await deps.state.compareAndSwap(
1525
+ KEY_STORE_CLEANUP_PROGRESS,
1526
+ progress?.revision ?? null,
1527
+ next
1528
+ );
1529
+ if (!advanced) {
1530
+ return aggregate;
1531
+ }
1532
+ progress = next;
1533
+ cursors = next.cursors;
1534
+ }
1535
+ return { ...aggregate, ...cursors === void 0 ? {} : { cursors } };
1536
+ }
1537
+
1032
1538
  // ../app/src/deviceNodes.ts
1033
1539
  function tbErrorFromBody(body) {
1034
1540
  return new TBError(body.code, body.message, { retryable: body.retryable });
@@ -1041,7 +1547,19 @@ function requireDevice(deps) {
1041
1547
  }
1042
1548
  async function invokeDevice(deps, deviceId, req) {
1043
1549
  const id = crypto.randomUUID();
1044
- const body = await requireDevice(deps).invoke(deviceId, { id, ...req });
1550
+ const upload = req.context === void 0 ? null : await issueDeviceCallUpload(deps, deviceId, id, req.context);
1551
+ let body;
1552
+ try {
1553
+ body = await requireDevice(deps).invoke(deviceId, {
1554
+ id,
1555
+ path: req.path,
1556
+ arguments: req.arguments,
1557
+ ...req.context === void 0 ? {} : { context: upload?.context ?? req.context }
1558
+ });
1559
+ } finally {
1560
+ await upload?.revoke().catch(() => {
1561
+ });
1562
+ }
1045
1563
  if (!body || !("ok" in body)) {
1046
1564
  throw new TBError("unavailable", "device session returned invalid result");
1047
1565
  }
@@ -5241,8 +5759,8 @@ var LADDER_ERROR_HTTP_STATUS = {
5241
5759
  function parseSchema(schema, data) {
5242
5760
  return safeParse(schema, data);
5243
5761
  }
5244
- function shapeKeys(schemas) {
5245
- return new Set(schemas.flatMap((schema) => Object.keys(schema.shape)));
5762
+ function shapeKeys(schemas2) {
5763
+ return new Set(schemas2.flatMap((schema) => Object.keys(schema.shape)));
5246
5764
  }
5247
5765
  function isStandardSchema(schema) {
5248
5766
  if (schema == null) return false;
@@ -7158,9 +7676,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7158
7676
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
7159
7677
  return `${varKind} ${this.name}${rhs};` + _n;
7160
7678
  }
7161
- optimizeNames(names, constants) {
7679
+ optimizeNames(names, constants2) {
7162
7680
  if (!names[this.name.str]) return;
7163
- if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants);
7681
+ if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
7164
7682
  return this;
7165
7683
  }
7166
7684
  get names() {
@@ -7177,9 +7695,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7177
7695
  render({ _n }) {
7178
7696
  return `${this.lhs} = ${this.rhs};` + _n;
7179
7697
  }
7180
- optimizeNames(names, constants) {
7698
+ optimizeNames(names, constants2) {
7181
7699
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
7182
- this.rhs = optimizeExpr(this.rhs, names, constants);
7700
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
7183
7701
  return this;
7184
7702
  }
7185
7703
  get names() {
@@ -7238,8 +7756,8 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7238
7756
  optimizeNodes() {
7239
7757
  return `${this.code}` ? this : void 0;
7240
7758
  }
7241
- optimizeNames(names, constants) {
7242
- this.code = optimizeExpr(this.code, names, constants);
7759
+ optimizeNames(names, constants2) {
7760
+ this.code = optimizeExpr(this.code, names, constants2);
7243
7761
  return this;
7244
7762
  }
7245
7763
  get names() {
@@ -7265,12 +7783,12 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7265
7783
  }
7266
7784
  return nodes.length > 0 ? this : void 0;
7267
7785
  }
7268
- optimizeNames(names, constants) {
7786
+ optimizeNames(names, constants2) {
7269
7787
  const { nodes } = this;
7270
7788
  let i = nodes.length;
7271
7789
  while (i--) {
7272
7790
  const n = nodes[i];
7273
- if (n.optimizeNames(names, constants)) continue;
7791
+ if (n.optimizeNames(names, constants2)) continue;
7274
7792
  subtractNames(names, n.names);
7275
7793
  nodes.splice(i, 1);
7276
7794
  }
@@ -7317,11 +7835,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7317
7835
  if (cond === false || !this.nodes.length) return void 0;
7318
7836
  return this;
7319
7837
  }
7320
- optimizeNames(names, constants) {
7838
+ optimizeNames(names, constants2) {
7321
7839
  var _a;
7322
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
7323
- if (!(super.optimizeNames(names, constants) || this.else)) return;
7324
- this.condition = optimizeExpr(this.condition, names, constants);
7840
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
7841
+ if (!(super.optimizeNames(names, constants2) || this.else)) return;
7842
+ this.condition = optimizeExpr(this.condition, names, constants2);
7325
7843
  return this;
7326
7844
  }
7327
7845
  get names() {
@@ -7343,9 +7861,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7343
7861
  render(opts) {
7344
7862
  return `for(${this.iteration})` + super.render(opts);
7345
7863
  }
7346
- optimizeNames(names, constants) {
7347
- if (!super.optimizeNames(names, constants)) return;
7348
- this.iteration = optimizeExpr(this.iteration, names, constants);
7864
+ optimizeNames(names, constants2) {
7865
+ if (!super.optimizeNames(names, constants2)) return;
7866
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
7349
7867
  return this;
7350
7868
  }
7351
7869
  get names() {
@@ -7380,9 +7898,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7380
7898
  render(opts) {
7381
7899
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
7382
7900
  }
7383
- optimizeNames(names, constants) {
7384
- if (!super.optimizeNames(names, constants)) return;
7385
- this.iterable = optimizeExpr(this.iterable, names, constants);
7901
+ optimizeNames(names, constants2) {
7902
+ if (!super.optimizeNames(names, constants2)) return;
7903
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
7386
7904
  return this;
7387
7905
  }
7388
7906
  get names() {
@@ -7421,11 +7939,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7421
7939
  (_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
7422
7940
  return this;
7423
7941
  }
7424
- optimizeNames(names, constants) {
7942
+ optimizeNames(names, constants2) {
7425
7943
  var _a, _b;
7426
- super.optimizeNames(names, constants);
7427
- (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants);
7428
- (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants);
7944
+ super.optimizeNames(names, constants2);
7945
+ (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants2);
7946
+ (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants2);
7429
7947
  return this;
7430
7948
  }
7431
7949
  get names() {
@@ -7674,7 +8192,7 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7674
8192
  function addExprNames(names, from) {
7675
8193
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
7676
8194
  }
7677
- function optimizeExpr(expr, names, constants) {
8195
+ function optimizeExpr(expr, names, constants2) {
7678
8196
  if (expr instanceof code_1.Name) return replaceName(expr);
7679
8197
  if (!canOptimize(expr)) return expr;
7680
8198
  return new code_1._Code(expr._items.reduce((items, c) => {
@@ -7684,13 +8202,13 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
7684
8202
  return items;
7685
8203
  }, []));
7686
8204
  function replaceName(n) {
7687
- const c = constants[n.str];
8205
+ const c = constants2[n.str];
7688
8206
  if (c === void 0 || names[n.str] !== 1) return n;
7689
8207
  delete names[n.str];
7690
8208
  return c;
7691
8209
  }
7692
8210
  function canOptimize(e) {
7693
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
8211
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
7694
8212
  }
7695
8213
  }
7696
8214
  function subtractNames(names, from) {
@@ -10417,14 +10935,14 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
10417
10935
  }
10418
10936
  return metaSchema;
10419
10937
  }
10420
- _removeAllSchemas(schemas, regex3) {
10421
- for (const keyRef in schemas) {
10422
- const sch = schemas[keyRef];
10938
+ _removeAllSchemas(schemas2, regex3) {
10939
+ for (const keyRef in schemas2) {
10940
+ const sch = schemas2[keyRef];
10423
10941
  if (!regex3 || regex3.test(keyRef)) {
10424
- if (typeof sch == "string") delete schemas[keyRef];
10942
+ if (typeof sch == "string") delete schemas2[keyRef];
10425
10943
  else if (sch && !sch.meta) {
10426
10944
  this._cache.delete(sch.schema);
10427
- delete schemas[keyRef];
10945
+ delete schemas2[keyRef];
10428
10946
  }
10429
10947
  }
10430
10948
  }
@@ -19175,15 +19693,6 @@ async function clientCredential(secrets, authRef) {
19175
19693
  ...values.clientSecret === void 0 ? {} : { clientSecret: values.clientSecret }
19176
19694
  };
19177
19695
  }
19178
- function parseTokenErrorMessage(payload) {
19179
- if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return void 0;
19180
- const record2 = payload;
19181
- for (const key of ["error_description", "error", "msg", "message"]) {
19182
- const value = record2[key];
19183
- if (typeof value === "string" && value !== "") return value;
19184
- }
19185
- return void 0;
19186
- }
19187
19696
  async function writeTokens(store, nodePath, next, previous) {
19188
19697
  const merged = {
19189
19698
  ...next,
@@ -19207,10 +19716,10 @@ async function requestTokens(opts) {
19207
19716
  let response;
19208
19717
  try {
19209
19718
  response = await opts.fetcher(endpoint, { method: "POST", headers, body });
19210
- } catch (err) {
19719
+ } catch {
19211
19720
  throw new TBError(
19212
19721
  "unavailable",
19213
- `\u4EE4\u724C\u7AEF\u70B9\u4E0D\u53EF\u8FBE:${err instanceof Error ? err.message : String(err)}`,
19722
+ "\u4EE4\u724C\u7AEF\u70B9\u4E0D\u53EF\u8FBE",
19214
19723
  { retryable: true }
19215
19724
  );
19216
19725
  }
@@ -19222,14 +19731,20 @@ async function requestTokens(opts) {
19222
19731
  payload = null;
19223
19732
  }
19224
19733
  if (!response.ok) {
19225
- const parsed = payload === null ? void 0 : parseTokenErrorMessage(payload);
19226
- const detail = parsed ?? `HTTP ${response.status}`;
19734
+ const detail = `HTTP ${response.status}`;
19227
19735
  if (response.status >= 500) {
19228
19736
  throw new TBError("unavailable", `\u4EE4\u724C\u7AEF\u70B9\u8FD4\u56DE ${detail}`, { retryable: true });
19229
19737
  }
19230
19738
  throw new TBError("invalid_argument", `\u4EE4\u724C\u7AEF\u70B9\u62D2\u7EDD\u4E86\u8BF7\u6C42:${detail}`);
19231
19739
  }
19232
- return parseTokenResponse(payload, opts.config, opts.now);
19740
+ try {
19741
+ return parseTokenResponse(payload, opts.config, opts.now);
19742
+ } catch (err) {
19743
+ if (isTBError(err) && err.code === "invalid_argument") {
19744
+ throw new TBError("invalid_argument", "\u4EE4\u724C\u7AEF\u70B9\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94");
19745
+ }
19746
+ throw err;
19747
+ }
19233
19748
  }
19234
19749
  function base64url(bytes) {
19235
19750
  let binary = "";
@@ -19241,7 +19756,14 @@ async function createPkce() {
19241
19756
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
19242
19757
  return { verifier, challenge: base64url(new Uint8Array(digest)) };
19243
19758
  }
19759
+ function requireProviderOAuthFetch(fetcher) {
19760
+ if (fetcher === void 0) {
19761
+ throw new TBError("unavailable", "Provider OAuth \u51FA\u7AD9\u901A\u9053\u672A\u914D\u7F6E", { retryable: false });
19762
+ }
19763
+ return fetcher;
19764
+ }
19244
19765
  async function startProviderAuthorization(opts) {
19766
+ const fetcher = requireProviderOAuthFetch(opts.fetcher);
19245
19767
  const existing = await readTokens(opts.store, opts.nodePath);
19246
19768
  if (existing !== void 0 && !shouldRefresh(existing, opts.now)) {
19247
19769
  return { status: "authorized" };
@@ -19251,7 +19773,7 @@ async function startProviderAuthorization(opts) {
19251
19773
  const client2 = await clientCredential(opts.secrets, opts.authRef);
19252
19774
  const refreshed = await requestTokens({
19253
19775
  config: opts.config,
19254
- fetcher: opts.fetcher,
19776
+ fetcher,
19255
19777
  now: opts.now,
19256
19778
  grant: { refreshToken: existing.refreshToken },
19257
19779
  ...client2
@@ -19284,10 +19806,11 @@ async function startProviderAuthorization(opts) {
19284
19806
  };
19285
19807
  }
19286
19808
  async function finishProviderAuthorization(opts) {
19809
+ const fetcher = requireProviderOAuthFetch(opts.fetcher);
19287
19810
  const client = await clientCredential(opts.secrets, opts.authRef);
19288
19811
  const tokens = await requestTokens({
19289
19812
  config: opts.config,
19290
- fetcher: opts.fetcher,
19813
+ fetcher,
19291
19814
  now: opts.now,
19292
19815
  grant: { code: opts.code, redirectUri: opts.origin + PROVIDER_OAUTH_CALLBACK_PATH },
19293
19816
  ...opts.codeVerifier === "" ? {} : { codeVerifier: opts.codeVerifier },
@@ -19296,6 +19819,7 @@ async function finishProviderAuthorization(opts) {
19296
19819
  await writeTokens(opts.store, opts.nodePath, tokens);
19297
19820
  }
19298
19821
  async function resolveProviderAccessToken(opts) {
19822
+ const fetcher = requireProviderOAuthFetch(opts.fetcher);
19299
19823
  const tokens = await readTokens(opts.store, opts.nodePath);
19300
19824
  if (tokens === void 0) throw providerReauthorizeRequired(opts.nodePath);
19301
19825
  if (opts.force !== true && !shouldRefresh(tokens, opts.now)) return tokens.accessToken;
@@ -19305,7 +19829,7 @@ async function resolveProviderAccessToken(opts) {
19305
19829
  try {
19306
19830
  refreshed = await requestTokens({
19307
19831
  config: opts.config,
19308
- fetcher: opts.fetcher,
19832
+ fetcher,
19309
19833
  now: opts.now,
19310
19834
  grant: { refreshToken: tokens.refreshToken },
19311
19835
  ...client
@@ -19409,7 +19933,7 @@ async function providerAccessTokenFor(opts) {
19409
19933
  config: opts.config,
19410
19934
  encryptionKey,
19411
19935
  ...opts.force === true ? { force: true } : {},
19412
- fetcher: fetch,
19936
+ fetcher: opts.deps.providerOAuthFetch,
19413
19937
  nodePath: opts.nodePath,
19414
19938
  now: /* @__PURE__ */ new Date(),
19415
19939
  // 调用期不需要 origin(只在构造授权 URL 时用);传空串会被 buildAuthorizationUrl 用到,
@@ -19608,7 +20132,7 @@ async function assertToolConfig(config, deps, ctx, mountPath) {
19608
20132
  }
19609
20133
 
19610
20134
  // ../app/src/refToken.ts
19611
- async function hmacKey(secret) {
20135
+ async function hmacKey2(secret) {
19612
20136
  const digest = await crypto.subtle.digest(
19613
20137
  "SHA-256",
19614
20138
  new TextEncoder().encode(`tb-ref-token:${secret}`)
@@ -19622,7 +20146,7 @@ async function signRefToken(payload, secret) {
19622
20146
  const body = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
19623
20147
  const mac = await crypto.subtle.sign(
19624
20148
  "HMAC",
19625
- await hmacKey(secret),
20149
+ await hmacKey2(secret),
19626
20150
  new TextEncoder().encode(body)
19627
20151
  );
19628
20152
  return `${body}.${base64urlEncode(new Uint8Array(mac))}`;
@@ -19639,7 +20163,7 @@ async function verifyRefToken(token, secret) {
19639
20163
  }
19640
20164
  const ok = await crypto.subtle.verify(
19641
20165
  "HMAC",
19642
- await hmacKey(secret),
20166
+ await hmacKey2(secret),
19643
20167
  // Node 的 WebCrypto 类型要求 Uint8Array<ArrayBuffer>;base64urlDecode 的产物满足但类型面是 ArrayBufferLike。
19644
20168
  sig,
19645
20169
  new TextEncoder().encode(body)
@@ -19784,60 +20308,11 @@ async function skillhubProviderFor(node, cfg, deps, requestUrl) {
19784
20308
  }
19785
20309
  return createSkillhubProvider(objects, opts);
19786
20310
  }
19787
- async function dispatchSkillhubCmd(provider, command, args) {
19788
- switch (command.toLowerCase()) {
19789
- case "list":
19790
- return await provider.list(args.opts);
19791
- case "get":
19792
- return typeof args.file === "string" ? await provider.get_file(args.id, args.file) : await provider.get(args.id);
19793
- case "search":
19794
- return await provider.search(args.query, args.opts);
19795
- case "publish":
19796
- if (!Array.isArray(args.files)) {
19797
- throw new TBError("invalid_argument", "publish \u9700\u8981\u6570\u7EC4 'files'");
19798
- }
19799
- return await provider.publish({
19800
- ...typeof args.id === "string" ? { id: args.id } : {},
19801
- files: args.files
19802
- });
19803
- case "remove":
19804
- return await provider.remove(args.id);
19805
- default:
19806
- throw new TBError("invalid_argument", `unknown cmd '${command}'`);
19807
- }
19808
- }
19809
- async function dispatchContextCmd(provider, command, args) {
19810
- const unimplemented = () => {
19811
- throw new TBError("invalid_argument", `unknown cmd '${command}'(provider \u672A\u5B9E\u73B0)`);
19812
- };
19813
- switch (command.toLowerCase()) {
19814
- case "list":
19815
- if (provider.list === void 0) return unimplemented();
19816
- return await provider.list(args.path ?? "", args.opts);
19817
- case "get":
19818
- if (provider.get === void 0) return unimplemented();
19819
- return await provider.get(args.path);
19820
- case "write":
19821
- if (provider.write === void 0) return unimplemented();
19822
- if (typeof args.entry !== "object" || args.entry === null) {
19823
- throw new TBError("invalid_argument", "write \u9700\u8981\u5BF9\u8C61 'entry'");
19824
- }
19825
- return await provider.write(args.path, args.entry);
19826
- case "update":
19827
- if (provider.update === void 0) return unimplemented();
19828
- if (typeof args.patch !== "object" || args.patch === null) {
19829
- throw new TBError("invalid_argument", "update \u9700\u8981\u5BF9\u8C61 'patch'");
19830
- }
19831
- return await provider.update(args.path, args.patch);
19832
- case "delete":
19833
- if (provider.delete === void 0) return unimplemented();
19834
- return await provider.delete(args.path);
19835
- case "search":
19836
- if (provider.search === void 0) return unimplemented();
19837
- return await provider.search(args.query, args.opts);
19838
- default:
19839
- throw new TBError("invalid_argument", `unknown cmd '${command}'`);
19840
- }
20311
+ async function dispatchSkillhubCmd2(provider, command, args) {
20312
+ return await dispatchSkillhubCmd(provider, command, args);
20313
+ }
20314
+ async function dispatchContextCmd2(provider, command, args) {
20315
+ return await dispatchContextCmd(provider, command, args);
19841
20316
  }
19842
20317
  async function assertContextAlive(node, cfg, registry) {
19843
20318
  if (!isContextExpired(node.createdAt, cfg.ttl, Date.now())) return;
@@ -23496,8 +23971,8 @@ function modernOnlyStrictRejection(route, supportedVersions) {
23496
23971
  function parseSchema2(schema, data) {
23497
23972
  return safeParse(schema, data);
23498
23973
  }
23499
- function shapeKeys2(schemas) {
23500
- return new Set(schemas.flatMap((schema) => Object.keys(schema.shape)));
23974
+ function shapeKeys2(schemas2) {
23975
+ return new Set(schemas2.flatMap((schema) => Object.keys(schema.shape)));
23501
23976
  }
23502
23977
  function isStandardSchema2(schema) {
23503
23978
  if (schema == null) return false;
@@ -25335,9 +25810,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25335
25810
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
25336
25811
  return `${varKind} ${this.name}${rhs};` + _n;
25337
25812
  }
25338
- optimizeNames(names, constants) {
25813
+ optimizeNames(names, constants2) {
25339
25814
  if (!names[this.name.str]) return;
25340
- if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants);
25815
+ if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
25341
25816
  return this;
25342
25817
  }
25343
25818
  get names() {
@@ -25354,9 +25829,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25354
25829
  render({ _n }) {
25355
25830
  return `${this.lhs} = ${this.rhs};` + _n;
25356
25831
  }
25357
- optimizeNames(names, constants) {
25832
+ optimizeNames(names, constants2) {
25358
25833
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
25359
- this.rhs = optimizeExpr(this.rhs, names, constants);
25834
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
25360
25835
  return this;
25361
25836
  }
25362
25837
  get names() {
@@ -25415,8 +25890,8 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25415
25890
  optimizeNodes() {
25416
25891
  return `${this.code}` ? this : void 0;
25417
25892
  }
25418
- optimizeNames(names, constants) {
25419
- this.code = optimizeExpr(this.code, names, constants);
25893
+ optimizeNames(names, constants2) {
25894
+ this.code = optimizeExpr(this.code, names, constants2);
25420
25895
  return this;
25421
25896
  }
25422
25897
  get names() {
@@ -25442,12 +25917,12 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25442
25917
  }
25443
25918
  return nodes.length > 0 ? this : void 0;
25444
25919
  }
25445
- optimizeNames(names, constants) {
25920
+ optimizeNames(names, constants2) {
25446
25921
  const { nodes } = this;
25447
25922
  let i = nodes.length;
25448
25923
  while (i--) {
25449
25924
  const n = nodes[i];
25450
- if (n.optimizeNames(names, constants)) continue;
25925
+ if (n.optimizeNames(names, constants2)) continue;
25451
25926
  subtractNames(names, n.names);
25452
25927
  nodes.splice(i, 1);
25453
25928
  }
@@ -25494,11 +25969,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25494
25969
  if (cond === false || !this.nodes.length) return void 0;
25495
25970
  return this;
25496
25971
  }
25497
- optimizeNames(names, constants) {
25972
+ optimizeNames(names, constants2) {
25498
25973
  var _a;
25499
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
25500
- if (!(super.optimizeNames(names, constants) || this.else)) return;
25501
- this.condition = optimizeExpr(this.condition, names, constants);
25974
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
25975
+ if (!(super.optimizeNames(names, constants2) || this.else)) return;
25976
+ this.condition = optimizeExpr(this.condition, names, constants2);
25502
25977
  return this;
25503
25978
  }
25504
25979
  get names() {
@@ -25520,9 +25995,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25520
25995
  render(opts) {
25521
25996
  return `for(${this.iteration})` + super.render(opts);
25522
25997
  }
25523
- optimizeNames(names, constants) {
25524
- if (!super.optimizeNames(names, constants)) return;
25525
- this.iteration = optimizeExpr(this.iteration, names, constants);
25998
+ optimizeNames(names, constants2) {
25999
+ if (!super.optimizeNames(names, constants2)) return;
26000
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
25526
26001
  return this;
25527
26002
  }
25528
26003
  get names() {
@@ -25557,9 +26032,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25557
26032
  render(opts) {
25558
26033
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
25559
26034
  }
25560
- optimizeNames(names, constants) {
25561
- if (!super.optimizeNames(names, constants)) return;
25562
- this.iterable = optimizeExpr(this.iterable, names, constants);
26035
+ optimizeNames(names, constants2) {
26036
+ if (!super.optimizeNames(names, constants2)) return;
26037
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
25563
26038
  return this;
25564
26039
  }
25565
26040
  get names() {
@@ -25598,11 +26073,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25598
26073
  (_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
25599
26074
  return this;
25600
26075
  }
25601
- optimizeNames(names, constants) {
26076
+ optimizeNames(names, constants2) {
25602
26077
  var _a, _b;
25603
- super.optimizeNames(names, constants);
25604
- (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants);
25605
- (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants);
26078
+ super.optimizeNames(names, constants2);
26079
+ (_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants2);
26080
+ (_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants2);
25606
26081
  return this;
25607
26082
  }
25608
26083
  get names() {
@@ -25851,7 +26326,7 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25851
26326
  function addExprNames(names, from) {
25852
26327
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
25853
26328
  }
25854
- function optimizeExpr(expr, names, constants) {
26329
+ function optimizeExpr(expr, names, constants2) {
25855
26330
  if (expr instanceof code_1.Name) return replaceName(expr);
25856
26331
  if (!canOptimize(expr)) return expr;
25857
26332
  return new code_1._Code(expr._items.reduce((items, c) => {
@@ -25861,13 +26336,13 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
25861
26336
  return items;
25862
26337
  }, []));
25863
26338
  function replaceName(n) {
25864
- const c = constants[n.str];
26339
+ const c = constants2[n.str];
25865
26340
  if (c === void 0 || names[n.str] !== 1) return n;
25866
26341
  delete names[n.str];
25867
26342
  return c;
25868
26343
  }
25869
26344
  function canOptimize(e) {
25870
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
26345
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
25871
26346
  }
25872
26347
  }
25873
26348
  function subtractNames(names, from) {
@@ -28594,14 +29069,14 @@ var require_core$32 = /* @__PURE__ */ __commonJSMin2(((exports) => {
28594
29069
  }
28595
29070
  return metaSchema;
28596
29071
  }
28597
- _removeAllSchemas(schemas, regex3) {
28598
- for (const keyRef in schemas) {
28599
- const sch = schemas[keyRef];
29072
+ _removeAllSchemas(schemas2, regex3) {
29073
+ for (const keyRef in schemas2) {
29074
+ const sch = schemas2[keyRef];
28600
29075
  if (!regex3 || regex3.test(keyRef)) {
28601
- if (typeof sch == "string") delete schemas[keyRef];
29076
+ if (typeof sch == "string") delete schemas2[keyRef];
28602
29077
  else if (sch && !sch.meta) {
28603
29078
  this._cache.delete(sch.schema);
28604
- delete schemas[keyRef];
29079
+ delete schemas2[keyRef];
28605
29080
  }
28606
29081
  }
28607
29082
  }
@@ -32091,9 +32566,9 @@ function createListenRouter(options) {
32091
32566
  const { bus, onerror } = options;
32092
32567
  const maxSubscriptions = options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS;
32093
32568
  const keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
32094
- const open = /* @__PURE__ */ new Set();
32569
+ const open2 = /* @__PURE__ */ new Set();
32095
32570
  function serve2(message, signal, capabilities, serverInfo) {
32096
- if (open.size >= maxSubscriptions) {
32571
+ if (open2.size >= maxSubscriptions) {
32097
32572
  onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
32098
32573
  return jsonRpcError(message.id, -32603, "Subscription limit reached");
32099
32574
  }
@@ -32149,7 +32624,7 @@ data: ${JSON.stringify({
32149
32624
  }
32150
32625
  if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
32151
32626
  abortCleanup?.();
32152
- open.delete(teardown);
32627
+ open2.delete(teardown);
32153
32628
  try {
32154
32629
  controller.close();
32155
32630
  } catch {
@@ -32169,7 +32644,7 @@ data: ${JSON.stringify({
32169
32644
  writeNotification(note.method, note.params);
32170
32645
  });
32171
32646
  keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
32172
- open.add(teardown);
32647
+ open2.add(teardown);
32173
32648
  },
32174
32649
  cancel() {
32175
32650
  teardown(false);
@@ -32194,10 +32669,10 @@ data: ${JSON.stringify({
32194
32669
  return {
32195
32670
  serve: serve2,
32196
32671
  closeAll() {
32197
- for (const teardown of open) teardown(true);
32672
+ for (const teardown of open2) teardown(true);
32198
32673
  },
32199
32674
  get openCount() {
32200
- return open.size;
32675
+ return open2.size;
32201
32676
  }
32202
32677
  };
32203
32678
  }
@@ -35960,6 +36435,273 @@ async function passthroughRemote(opts) {
35960
36435
  // ../app/src/tbApp.ts
35961
36436
  import { Hono } from "hono";
35962
36437
 
36438
+ // ../core/src/protocol/openapi.ts
36439
+ var schemaRegistry = external_exports.registry();
36440
+ var schemas = {
36441
+ Error: tbErrorBodySchema,
36442
+ FeedbackDetail: feedbackDetailSchema,
36443
+ FeedbackList: feedbackListSchema,
36444
+ FeedbackRemoveResponse: feedbackRemoveResponseSchema,
36445
+ FeedbackSubmitRequest: feedbackSubmitRequestSchema,
36446
+ FeedbackSubmitResponse: feedbackSubmitResponseSchema,
36447
+ FeedbackView: feedbackViewSchema,
36448
+ FeedbackVoteRequest: feedbackVoteRequestSchema,
36449
+ Health: healthResponseSchema,
36450
+ Help: helpJsonSchema,
36451
+ Liveness: livenessResponseSchema,
36452
+ NodeInput: nodeInputSchema,
36453
+ OAuthAuthorizeRequest: oauthAuthorizeRequestSchema,
36454
+ OAuthAuthorizeResponse: oauthAuthorizeResponseSchema,
36455
+ Readiness: readinessResponseSchema,
36456
+ RegistryNode: registryNodeSchema,
36457
+ SearchRequest: toolSearchRequestSchema,
36458
+ SearchResponse: toolSearchPageSchema,
36459
+ Tree: treeJsonSchema
36460
+ };
36461
+ for (const [id, schema] of Object.entries(schemas)) schemaRegistry.add(schema, { id });
36462
+ function asOpenApiSchema(value) {
36463
+ if (Array.isArray(value)) return value.map(asOpenApiSchema);
36464
+ if (value === null || typeof value !== "object") return value;
36465
+ const out = {};
36466
+ for (const [key, entry] of Object.entries(value)) {
36467
+ if (key === "$schema") continue;
36468
+ if (key === "$ref" && typeof entry === "string" && !entry.startsWith("#/")) {
36469
+ out[key] = `#/components/schemas/${entry}`;
36470
+ } else {
36471
+ out[key] = asOpenApiSchema(entry);
36472
+ }
36473
+ }
36474
+ return out;
36475
+ }
36476
+ var generated = external_exports.toJSONSchema(schemaRegistry);
36477
+ var componentSchemas = Object.fromEntries(
36478
+ Object.entries(generated.schemas).map(([name, schema]) => [name, asOpenApiSchema(schema)])
36479
+ );
36480
+ var jsonContent = (schema) => ({
36481
+ "application/json": { schema: { $ref: `#/components/schemas/${schema}` } }
36482
+ });
36483
+ var jsonResponse = (description, schema) => ({
36484
+ description,
36485
+ content: jsonContent(schema)
36486
+ });
36487
+ var errorResponses = {
36488
+ 400: jsonResponse("Invalid request", "Error"),
36489
+ 401: jsonResponse("Unauthenticated", "Error"),
36490
+ 403: jsonResponse("Permission denied", "Error"),
36491
+ 404: jsonResponse("Not found or not visible", "Error"),
36492
+ 409: jsonResponse("Conflict", "Error"),
36493
+ 429: jsonResponse("Rate limited", "Error"),
36494
+ 500: jsonResponse("Internal error", "Error"),
36495
+ 503: jsonResponse("Unavailable", "Error")
36496
+ };
36497
+ var bearerSecurity = [{ bearerAuth: [] }];
36498
+ var pathParameter = {
36499
+ in: "path",
36500
+ name: "path",
36501
+ required: true,
36502
+ description: "Slash-delimited Tool Bridge tree path; each segment is URL encoded.",
36503
+ schema: { type: "string" }
36504
+ };
36505
+ var fixedControlPlaneOpenApi = {
36506
+ openapi: "3.1.0",
36507
+ info: {
36508
+ title: "Tool Bridge fixed control plane",
36509
+ version: "1.0.0"
36510
+ },
36511
+ components: {
36512
+ securitySchemes: {
36513
+ bearerAuth: { type: "http", scheme: "bearer" }
36514
+ },
36515
+ schemas: componentSchemas
36516
+ },
36517
+ paths: {
36518
+ "/healthz": {
36519
+ get: {
36520
+ operationId: "getHealth",
36521
+ security: [],
36522
+ responses: { 200: jsonResponse("Deployment health", "Health") }
36523
+ }
36524
+ },
36525
+ "/livez": {
36526
+ get: {
36527
+ operationId: "getLiveness",
36528
+ security: [],
36529
+ responses: { 200: jsonResponse("Process liveness", "Liveness") }
36530
+ }
36531
+ },
36532
+ "/readyz": {
36533
+ get: {
36534
+ operationId: "getReadiness",
36535
+ security: [],
36536
+ responses: {
36537
+ 200: jsonResponse("Ready", "Readiness"),
36538
+ 503: jsonResponse("Not ready", "Readiness")
36539
+ }
36540
+ }
36541
+ },
36542
+ "/~help": {
36543
+ get: {
36544
+ operationId: "getRootHelp",
36545
+ security: bearerSecurity,
36546
+ parameters: [{
36547
+ in: "query",
36548
+ name: "schemas",
36549
+ schema: { type: "string", enum: ["1"] }
36550
+ }],
36551
+ responses: { 200: jsonResponse("Root HTBP help", "Help"), ...errorResponses }
36552
+ }
36553
+ },
36554
+ "/{path}/~help": {
36555
+ get: {
36556
+ "operationId": "getHelp",
36557
+ "security": bearerSecurity,
36558
+ "parameters": [pathParameter, {
36559
+ in: "query",
36560
+ name: "schemas",
36561
+ schema: { type: "string", enum: ["1"] }
36562
+ }],
36563
+ "responses": { 200: jsonResponse("HTBP help", "Help"), ...errorResponses },
36564
+ "x-tool-bridge-greedy-path": true
36565
+ }
36566
+ },
36567
+ "/~tree": {
36568
+ get: {
36569
+ operationId: "getRootTree",
36570
+ security: bearerSecurity,
36571
+ parameters: [{ in: "query", name: "depth", schema: { type: "integer", minimum: 1, maximum: 8 } }],
36572
+ responses: { 200: jsonResponse("Root tree", "Tree"), ...errorResponses }
36573
+ }
36574
+ },
36575
+ "/{path}/~tree": {
36576
+ get: {
36577
+ "operationId": "getTree",
36578
+ "security": bearerSecurity,
36579
+ "parameters": [pathParameter, {
36580
+ in: "query",
36581
+ name: "depth",
36582
+ schema: { type: "integer", minimum: 1, maximum: 8 }
36583
+ }],
36584
+ "responses": { 200: jsonResponse("Subtree", "Tree"), ...errorResponses },
36585
+ "x-tool-bridge-greedy-path": true
36586
+ }
36587
+ },
36588
+ "/~search": {
36589
+ post: {
36590
+ operationId: "searchTools",
36591
+ security: bearerSecurity,
36592
+ requestBody: { required: true, content: jsonContent("SearchRequest") },
36593
+ responses: { 200: jsonResponse("Visible tool page", "SearchResponse"), ...errorResponses }
36594
+ }
36595
+ },
36596
+ "/{path}/~authorize": {
36597
+ post: {
36598
+ "operationId": "startOAuthAuthorization",
36599
+ "security": bearerSecurity,
36600
+ "parameters": [pathParameter],
36601
+ "requestBody": { required: false, content: jsonContent("OAuthAuthorizeRequest") },
36602
+ "responses": {
36603
+ 200: jsonResponse("Authorization status or redirect", "OAuthAuthorizeResponse"),
36604
+ ...errorResponses
36605
+ },
36606
+ "x-tool-bridge-greedy-path": true
36607
+ }
36608
+ },
36609
+ "/{path}/~register": {
36610
+ post: {
36611
+ "operationId": "registerNode",
36612
+ "security": bearerSecurity,
36613
+ "parameters": [pathParameter],
36614
+ "requestBody": { required: true, content: jsonContent("NodeInput") },
36615
+ "responses": {
36616
+ 200: jsonResponse("Registered node", "RegistryNode"),
36617
+ ...errorResponses
36618
+ },
36619
+ "x-tool-bridge-greedy-path": true
36620
+ }
36621
+ },
36622
+ "/{path}/~feedback": {
36623
+ get: {
36624
+ "operationId": "listFeedback",
36625
+ "security": bearerSecurity,
36626
+ "parameters": [pathParameter, {
36627
+ in: "query",
36628
+ name: "hidden",
36629
+ schema: { type: "string", enum: ["1"] }
36630
+ }],
36631
+ "responses": { 200: jsonResponse("Feedback list", "FeedbackList"), ...errorResponses },
36632
+ "x-tool-bridge-greedy-path": true
36633
+ },
36634
+ post: {
36635
+ "operationId": "submitFeedback",
36636
+ "security": bearerSecurity,
36637
+ "parameters": [pathParameter],
36638
+ "requestBody": { required: true, content: jsonContent("FeedbackSubmitRequest") },
36639
+ "responses": {
36640
+ 200: jsonResponse("Submitted feedback", "FeedbackSubmitResponse"),
36641
+ ...errorResponses
36642
+ },
36643
+ "x-tool-bridge-greedy-path": true
36644
+ }
36645
+ },
36646
+ "/{path}/~feedback/{id}": {
36647
+ get: {
36648
+ "operationId": "getFeedback",
36649
+ "security": bearerSecurity,
36650
+ "parameters": [pathParameter, { in: "path", name: "id", required: true, schema: { type: "string" } }],
36651
+ "responses": { 200: jsonResponse("Feedback detail", "FeedbackDetail"), ...errorResponses },
36652
+ "x-tool-bridge-greedy-path": true
36653
+ },
36654
+ post: {
36655
+ "operationId": "voteFeedback",
36656
+ "security": bearerSecurity,
36657
+ "parameters": [pathParameter, { in: "path", name: "id", required: true, schema: { type: "string" } }],
36658
+ "requestBody": { required: true, content: jsonContent("FeedbackVoteRequest") },
36659
+ "responses": { 200: jsonResponse("Updated feedback", "FeedbackView"), ...errorResponses },
36660
+ "x-tool-bridge-greedy-path": true
36661
+ },
36662
+ delete: {
36663
+ "operationId": "removeFeedback",
36664
+ "security": bearerSecurity,
36665
+ "parameters": [pathParameter, { in: "path", name: "id", required: true, schema: { type: "string" } }],
36666
+ "responses": {
36667
+ 200: jsonResponse("Feedback removed", "FeedbackRemoveResponse"),
36668
+ ...errorResponses
36669
+ },
36670
+ "x-tool-bridge-greedy-path": true
36671
+ }
36672
+ },
36673
+ "/{commandPath}": {
36674
+ post: {
36675
+ "operationId": "invoke",
36676
+ "security": bearerSecurity,
36677
+ "parameters": [{
36678
+ in: "path",
36679
+ name: "commandPath",
36680
+ required: true,
36681
+ schema: { type: "string" },
36682
+ description: "Complete HTBP command path, including the command/tool leaf."
36683
+ }],
36684
+ "requestBody": {
36685
+ required: true,
36686
+ description: "Raw command arguments; there is no {tool, arguments} envelope.",
36687
+ content: { "application/json": { schema: {} } }
36688
+ },
36689
+ "responses": {
36690
+ 200: {
36691
+ description: "Raw JSON or Markdown according to Accept.",
36692
+ content: {
36693
+ "application/json": { schema: {} },
36694
+ "text/markdown": { schema: { type: "string" } }
36695
+ }
36696
+ },
36697
+ ...errorResponses
36698
+ },
36699
+ "x-tool-bridge-greedy-path": true
36700
+ }
36701
+ }
36702
+ }
36703
+ };
36704
+
35963
36705
  // ../app/src/paths.ts
35964
36706
  function scopeForCmd(mod, nodePath, cmd) {
35965
36707
  return mod.help(nodePath).cmds.find((c) => c.name === cmd);
@@ -36043,7 +36785,9 @@ function indexByParent(nodes) {
36043
36785
  }
36044
36786
 
36045
36787
  // ../app/src/routes/feedback.ts
36046
- var feedbackJson = (value) => new Response(JSON.stringify(value), { headers: { "content-type": contentTypeFor("json") } });
36788
+ var feedbackJson = (schema, value) => new Response(JSON.stringify(schema.parse(value)), {
36789
+ headers: { "content-type": contentTypeFor("json") }
36790
+ });
36047
36791
  async function handleFeedbackGet(c) {
36048
36792
  const target = splitFeedback(new URL(c.req.url).pathname);
36049
36793
  if (target === null || target.path === "") throw TBError.notFound("no such path");
@@ -36052,7 +36796,7 @@ async function handleFeedbackGet(c) {
36052
36796
  const fb = new FeedbackStore(c.get("store"));
36053
36797
  if (target.id !== void 0) {
36054
36798
  const e = await fb.get(target.path, target.id);
36055
- return feedbackJson({
36799
+ return feedbackJson(feedbackDetailSchema, {
36056
36800
  id: e.id,
36057
36801
  path: target.path,
36058
36802
  title: e.title,
@@ -36066,7 +36810,7 @@ async function handleFeedbackGet(c) {
36066
36810
  }
36067
36811
  const views = await fb.listViews(target.path);
36068
36812
  const items = c.req.query("hidden") === "1" ? views : views.filter((v) => v.score > FEEDBACK_HIDE_SCORE);
36069
- return feedbackJson({ items });
36813
+ return feedbackJson(feedbackListSchema, { items });
36070
36814
  }
36071
36815
  async function handleFeedbackPost(c, env) {
36072
36816
  const { searchSync } = env;
@@ -36088,23 +36832,32 @@ async function handleFeedbackPost(c, env) {
36088
36832
  await searchSync?.reconcileNodeQuietly(path, { feedback: entries, marker });
36089
36833
  });
36090
36834
  if (target.id !== void 0) {
36091
- const vote = body.vote;
36092
- if (vote !== "up" && vote !== "down" && vote !== "clear") {
36835
+ const input2 = feedbackVoteRequestSchema.safeParse(body);
36836
+ if (!input2.success) {
36093
36837
  throw new TBError("invalid_argument", `body.vote must be 'up' | 'down' | 'clear'`);
36094
36838
  }
36095
- return feedbackJson(await fb.vote(target.path, target.id, ctx.owner, vote));
36839
+ return feedbackJson(
36840
+ feedbackViewSchema,
36841
+ await fb.vote(target.path, target.id, ctx.owner, input2.data.vote)
36842
+ );
36096
36843
  }
36097
- if (typeof body.title !== "string" || typeof body.detail !== "string") {
36844
+ const input = feedbackSubmitRequestSchema.safeParse(body);
36845
+ if (!input.success) {
36098
36846
  throw new TBError("invalid_argument", "body must be { title: string, detail: string }");
36099
36847
  }
36100
36848
  await new NodeRegistryStore(store).resolve(target.path);
36101
36849
  const entry = await fb.submit(
36102
36850
  target.path,
36103
- { title: body.title, detail: body.detail },
36851
+ input.data,
36104
36852
  ctx.owner,
36105
36853
  (/* @__PURE__ */ new Date()).toISOString()
36106
36854
  );
36107
- return feedbackJson({ id: entry.id, path: target.path, title: entry.title, at: entry.at });
36855
+ return feedbackJson(feedbackSubmitResponseSchema, {
36856
+ id: entry.id,
36857
+ path: target.path,
36858
+ title: entry.title,
36859
+ at: entry.at
36860
+ });
36108
36861
  } catch (error) {
36109
36862
  await searchSync?.abort(marker);
36110
36863
  throw error;
@@ -36130,12 +36883,12 @@ async function handleFeedbackDelete(c, env) {
36130
36883
  await searchSync?.abort(marker);
36131
36884
  throw error;
36132
36885
  }
36133
- return feedbackJson({ ok: true });
36886
+ return feedbackJson(feedbackRemoveResponseSchema, { ok: true });
36134
36887
  }
36135
36888
 
36136
36889
  // ../app/src/responses.ts
36137
36890
  function tbErrorResponse(err) {
36138
- return new Response(JSON.stringify(err.toJSON()), {
36891
+ return new Response(JSON.stringify(tbErrorBodySchema.parse(err.toJSON())), {
36139
36892
  status: err.httpStatus,
36140
36893
  headers: { "content-type": "application/json; charset=utf-8" }
36141
36894
  });
@@ -36172,7 +36925,7 @@ async function runHandler(fn) {
36172
36925
  }
36173
36926
  function renderHelp(model, rep) {
36174
36927
  if (rep === "json") {
36175
- return new Response(JSON.stringify(renderHelpJson(model)), {
36928
+ return new Response(JSON.stringify(helpJsonSchema.parse(renderHelpJson(model))), {
36176
36929
  headers: { "content-type": contentTypeFor("json") }
36177
36930
  });
36178
36931
  }
@@ -36376,14 +37129,14 @@ async function authorizeToolNode(c, env, node, encryptionKey) {
36376
37129
  authRef: config.authRef,
36377
37130
  config: exported.oauth,
36378
37131
  encryptionKey,
36379
- fetcher: fetch,
37132
+ fetcher: deps.providerOAuthFetch,
36380
37133
  nodePath: node.path,
36381
37134
  now: /* @__PURE__ */ new Date(),
36382
37135
  origin: deps.canonicalOrigin ?? new URL(c.req.url).origin,
36383
37136
  secrets: deps.secrets,
36384
37137
  store: deps.state
36385
37138
  });
36386
- return new Response(JSON.stringify(result), {
37139
+ return new Response(JSON.stringify(oauthAuthorizeResponseSchema.parse(result)), {
36387
37140
  headers: { "content-type": contentTypeFor("json") }
36388
37141
  });
36389
37142
  }
@@ -36430,7 +37183,7 @@ async function handleRegister(c, env) {
36430
37183
  await invalidateProviderOAuth(store, body.path);
36431
37184
  await searchSync?.reconcileNodeQuietly(body.path, { marker });
36432
37185
  if (await refreshDynamicSearchNode(node, ctx, deps)) await searchSync?.abort(marker);
36433
- return new Response(JSON.stringify(node), {
37186
+ return new Response(JSON.stringify(registryNodeSchema.parse(node)), {
36434
37187
  headers: { "content-type": contentTypeFor("json") }
36435
37188
  });
36436
37189
  }
@@ -36457,6 +37210,11 @@ async function handleAuthorize(c, env) {
36457
37210
  } catch {
36458
37211
  throw TBError.notFound("not found");
36459
37212
  }
37213
+ const rawBody = await c.req.json().catch(() => ({}));
37214
+ const body = oauthAuthorizeRequestSchema.safeParse(rawBody);
37215
+ if (!body.success) {
37216
+ throw new TBError("invalid_argument", "body only accepts optional redirectUri string");
37217
+ }
36460
37218
  if (node.kind === "tool" && node.config?.kind === "tool") {
36461
37219
  return await authorizeToolNode(c, env, node, encKey);
36462
37220
  }
@@ -36466,8 +37224,7 @@ async function handleAuthorize(c, env) {
36466
37224
  `'${path}' \u65E2\u4E0D\u662F auth:'oauth' \u7684 mcp \u6302\u8F7D,\u4E5F\u4E0D\u662F\u58F0\u660E\u4E86 oauth \u7684 tool \u6302\u8F7D`
36467
37225
  );
36468
37226
  }
36469
- const body = await c.req.json().catch(() => null);
36470
- const redirectUri = body !== null && typeof body.redirectUri === "string" ? body.redirectUri : void 0;
37227
+ const redirectUri = body.data.redirectUri;
36471
37228
  const result = await startMcpAuthorization({
36472
37229
  store,
36473
37230
  encryptionKey: encKey,
@@ -36476,7 +37233,7 @@ async function handleAuthorize(c, env) {
36476
37233
  origin: deps.canonicalOrigin ?? new URL(c.req.url).origin,
36477
37234
  ...redirectUri !== void 0 ? { redirectUri } : {}
36478
37235
  });
36479
- return new Response(JSON.stringify(result), {
37236
+ return new Response(JSON.stringify(oauthAuthorizeResponseSchema.parse(result)), {
36480
37237
  headers: { "content-type": contentTypeFor("json") }
36481
37238
  });
36482
37239
  }
@@ -36538,6 +37295,137 @@ async function handleDescribe(c, env) {
36538
37295
  throw TBError.notFound(`no capabilities for kind '${node.kind}'`);
36539
37296
  }
36540
37297
 
37298
+ // ../app/src/routes/store.ts
37299
+ var SECRET_BODY_FIELDS = [
37300
+ "callCapability",
37301
+ "capability",
37302
+ "uploadToken",
37303
+ "shareToken"
37304
+ ];
37305
+ function hideCapabilityFailure(error) {
37306
+ if (isTBError(error) && error.code !== "permission_denied" && error.code !== "not_found") throw error;
37307
+ throw TBError.notFound("not found");
37308
+ }
37309
+ async function jsonObject(c) {
37310
+ const parsed = await c.req.json().catch(() => null);
37311
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
37312
+ throw new TBError("invalid_argument", "body must be a JSON object");
37313
+ }
37314
+ const body = parsed;
37315
+ for (const field of SECRET_BODY_FIELDS) {
37316
+ if (field in body) {
37317
+ throw new TBError(
37318
+ "invalid_argument",
37319
+ `${field} is not accepted in the request body; use the dedicated capability header`
37320
+ );
37321
+ }
37322
+ }
37323
+ return body;
37324
+ }
37325
+ function requiredHeader(c, name) {
37326
+ const value = c.req.header(name);
37327
+ if (value === void 0 || value.trim() === "") {
37328
+ throw new TBError("permission_denied", "Store capability is missing or invalid");
37329
+ }
37330
+ return value;
37331
+ }
37332
+ function uploadId(body) {
37333
+ if (typeof body.uploadId !== "string" || body.uploadId.trim() === "") {
37334
+ throw new TBError("invalid_argument", "uploadId must be a non-empty string");
37335
+ }
37336
+ return body.uploadId;
37337
+ }
37338
+ function registerStoreCapabilityRoutes(app, deps) {
37339
+ app.post("/system/store/create_upload", async (c, next) => {
37340
+ if (c.req.header(STORE_CALL_CAPABILITY_HEADER) === void 0) return await next();
37341
+ return await runHandler(async () => {
37342
+ await deps.ensureReady?.();
37343
+ const body = await jsonObject(c);
37344
+ const capability = requiredHeader(c, STORE_CALL_CAPABILITY_HEADER);
37345
+ const result = await beginCapabilityUpload(
37346
+ deps,
37347
+ body,
37348
+ capability,
37349
+ resolveStoreRequestOrigin(c.req.url, deps.canonicalOrigin)
37350
+ );
37351
+ return c.json(result);
37352
+ });
37353
+ });
37354
+ for (const command of ["complete_upload", "abort_upload"]) {
37355
+ app.post(`/system/store/${command}`, async (c, next) => {
37356
+ if (c.req.header(STORE_UPLOAD_HEADER) === void 0) return await next();
37357
+ return await runHandler(async () => {
37358
+ await deps.ensureReady?.();
37359
+ const body = await jsonObject(c);
37360
+ const token = requiredHeader(c, STORE_UPLOAD_HEADER);
37361
+ const result = command === "complete_upload" ? await completeCapabilityUpload(deps, uploadId(body), token) : await abortCapabilityUpload(deps, uploadId(body), token);
37362
+ return c.json(result);
37363
+ });
37364
+ });
37365
+ }
37366
+ app.put(
37367
+ "/~store/uploads/:uploadId",
37368
+ (c) => runHandler(async () => {
37369
+ await deps.ensureReady?.();
37370
+ const descriptor = await relayStoreUpload(
37371
+ deps,
37372
+ c.req.param("uploadId"),
37373
+ requiredHeader(c, STORE_UPLOAD_HEADER),
37374
+ c.req.raw.body
37375
+ );
37376
+ return c.json(descriptor);
37377
+ })
37378
+ );
37379
+ app.get(
37380
+ "/~store/refs/:token",
37381
+ (c) => runHandler(async () => {
37382
+ await deps.ensureReady?.();
37383
+ const store = await defaultStoreRuntime(deps);
37384
+ const payload = await verifyStoreRefToken(c.req.param("token"), store.tokenSecret);
37385
+ if (payload === null || payload.exp * 1e3 <= Date.now()) {
37386
+ throw TBError.notFound("not found");
37387
+ }
37388
+ let object2;
37389
+ try {
37390
+ object2 = await store.service.authorizeRead(storeUri(payload.objectId), {
37391
+ admin: true
37392
+ });
37393
+ } catch (error) {
37394
+ hideCapabilityFailure(error);
37395
+ }
37396
+ return await storeObjectResponse(store.objects, object2);
37397
+ })
37398
+ );
37399
+ app.get(
37400
+ "/~store/shares/:token",
37401
+ (c) => runHandler(async () => {
37402
+ await deps.ensureReady?.();
37403
+ const token = c.req.param("token");
37404
+ const store = await defaultStoreRuntime(deps);
37405
+ let object2;
37406
+ try {
37407
+ const grant = await store.service.verifyShareToken(token);
37408
+ const uri3 = storeUri(grant.objectId);
37409
+ object2 = await store.service.authorizeSharedRead(uri3, token);
37410
+ } catch (error) {
37411
+ hideCapabilityFailure(error);
37412
+ }
37413
+ return await storeObjectResponse(store.objects, object2);
37414
+ })
37415
+ );
37416
+ }
37417
+ function rejectStoreCapabilityBodyFields(module, args) {
37418
+ if (module !== "store") return;
37419
+ for (const field of SECRET_BODY_FIELDS) {
37420
+ if (field in args) {
37421
+ throw new TBError(
37422
+ "invalid_argument",
37423
+ `${field} is not accepted in the request body; use the dedicated capability header`
37424
+ );
37425
+ }
37426
+ }
37427
+ }
37428
+
36541
37429
  // ../app/src/routes/publicRoutes.ts
36542
37430
  async function finishToolAuthorization(opts) {
36543
37431
  const config = opts.node.config;
@@ -36558,7 +37446,7 @@ async function finishToolAuthorization(opts) {
36558
37446
  codeVerifier: opts.verifier,
36559
37447
  config: exported.oauth,
36560
37448
  encryptionKey: opts.encryptionKey,
36561
- fetcher: fetch,
37449
+ fetcher: opts.deps.providerOAuthFetch,
36562
37450
  nodePath: opts.node.path,
36563
37451
  now: /* @__PURE__ */ new Date(),
36564
37452
  origin: opts.origin,
@@ -36575,21 +37463,23 @@ function registerPublicRoutes(app, env) {
36575
37463
  app.get("/healthz", async (c) => {
36576
37464
  const catalog = deps.pluginCatalog;
36577
37465
  const digest = await env.pluginCatalogDigest();
36578
- return c.json({
37466
+ return c.json(healthResponseSchema.parse({
36579
37467
  healthy: true,
36580
37468
  version: deps.version,
36581
37469
  ...catalog !== void 0 && digest !== void 0 ? { catalog: { count: Object.keys(catalog).length, digest } } : {}
36582
- });
37470
+ }));
36583
37471
  });
36584
- app.get("/livez", (c) => c.json({ live: true }));
37472
+ app.get("/livez", (c) => c.json(livenessResponseSchema.parse({ live: true })));
36585
37473
  app.get("/readyz", async (c) => {
36586
37474
  const probe = deps.readiness;
36587
- if (probe === void 0) return c.json({ checks: {}, ready: true });
37475
+ if (probe === void 0) {
37476
+ return c.json(readinessResponseSchema.parse({ checks: {}, ready: true }));
37477
+ }
36588
37478
  try {
36589
- const report = await probe();
37479
+ const report = readinessResponseSchema.parse(await probe());
36590
37480
  return c.json(report, report.ready ? 200 : 503);
36591
37481
  } catch {
36592
- return c.json({ checks: {}, ready: false }, 503);
37482
+ return c.json(readinessResponseSchema.parse({ checks: {}, ready: false }), 503);
36593
37483
  }
36594
37484
  });
36595
37485
  app.get(
@@ -36749,7 +37639,11 @@ function registerSearchRoute(app, env) {
36749
37639
  if (opts.cursor !== void 0 && typeof opts.cursor !== "string") {
36750
37640
  throw new TBError("invalid_argument", "opts.cursor must be a string");
36751
37641
  }
36752
- const query = body.query.trim();
37642
+ const wireRequest = toolSearchRequestSchema.parse({
37643
+ query: body.query,
37644
+ ...rawOpts === void 0 ? {} : { opts }
37645
+ });
37646
+ const query = wireRequest.query;
36753
37647
  await searchSync?.ensureReady();
36754
37648
  const ctx = c.get("ctx");
36755
37649
  const registry = new NodeRegistryStore(c.get("store"));
@@ -36827,7 +37721,7 @@ function registerSearchRoute(app, env) {
36827
37721
  }
36828
37722
  if (items.length === 0) responseCursor = void 0;
36829
37723
  const result = responseCursor === void 0 ? { items } : { items, cursor: responseCursor };
36830
- return new Response(JSON.stringify(result), {
37724
+ return new Response(JSON.stringify(toolSearchPageSchema.parse(result)), {
36831
37725
  headers: { "content-type": contentTypeFor("json") }
36832
37726
  });
36833
37727
  })
@@ -37398,27 +38292,27 @@ async function handleInvoke(c, env) {
37398
38292
  throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
37399
38293
  }
37400
38294
  if (directUpload) {
37401
- const result3 = await createContextUploadGrant(
37402
- node,
37403
- cfg,
37404
- deps,
37405
- args2
38295
+ const result3 = await dispatchContextUploadCmd(
38296
+ args2,
38297
+ (input) => createContextUploadGrant(node, cfg, deps, input)
37406
38298
  );
37407
38299
  return renderResult(result3, negotiate(c.req.header("accept")));
37408
38300
  }
37409
38301
  if (cfg.provider === "device-fs") {
38302
+ const forwardedArgs = parseContextCmdArgs(command, args2);
37410
38303
  const result3 = await invokeDevice(deps, deviceIdForDeviceFs(cfg), {
37411
38304
  path: `fs/${command}`,
37412
- arguments: args2,
38305
+ arguments: forwardedArgs,
37413
38306
  context: deviceCallContextFrom(ctx)
37414
38307
  });
37415
38308
  return renderResult(result3, negotiate(c.req.header("accept")));
37416
38309
  }
37417
38310
  const contextMarker = deviceMarkerOf(cfg.providerConfig);
37418
38311
  if (cfg.provider !== "r2" && cfg.provider !== "s3" && contextMarker !== null) {
38312
+ const forwardedArgs = parseContextCmdArgs(command, args2);
37419
38313
  const result3 = await invokeDevice(deps, contextMarker.deviceId, {
37420
38314
  path: `${relativeDevicePath(node.path, contextMarker.mountPath)}/${command}`,
37421
- arguments: args2,
38315
+ arguments: forwardedArgs,
37422
38316
  context: deviceCallContextFrom(ctx)
37423
38317
  });
37424
38318
  return renderResult(result3, negotiate(c.req.header("accept")));
@@ -37426,7 +38320,7 @@ async function handleInvoke(c, env) {
37426
38320
  if (cfg.provider !== "r2" && cfg.provider !== "s3") {
37427
38321
  const local = localContext(deps, node);
37428
38322
  if (local !== null) {
37429
- const result4 = await dispatchContextCmd(local, command, args2);
38323
+ const result4 = await dispatchContextCmd2(local, command, args2);
37430
38324
  return renderResult(result4, negotiate(c.req.header("accept")));
37431
38325
  }
37432
38326
  const { manifest, export: exported } = await requirePluginExport(
@@ -37446,11 +38340,11 @@ async function handleInvoke(c, env) {
37446
38340
  ...cfg.authRef !== void 0 ? { upstreamAuthRef: cfg.authRef } : {},
37447
38341
  ...deps.pluginBindings !== void 0 ? { bindings: deps.pluginBindings } : {}
37448
38342
  });
37449
- const result3 = await dispatchContextCmd(provider2, command, args2);
38343
+ const result3 = await dispatchContextCmd2(provider2, command, args2);
37450
38344
  return renderResult(result3, negotiate(c.req.header("accept")));
37451
38345
  }
37452
38346
  const provider = await contextProviderFor(node, cfg, deps, c.req.url);
37453
- const result2 = await dispatchContextCmd(provider, command, args2);
38347
+ const result2 = await dispatchContextCmd2(provider, command, args2);
37454
38348
  return renderResult(result2, negotiate(c.req.header("accept")));
37455
38349
  }
37456
38350
  if (node.kind === "skillhub" && node.config?.kind === "skillhub") {
@@ -37468,7 +38362,7 @@ async function handleInvoke(c, env) {
37468
38362
  throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
37469
38363
  }
37470
38364
  const provider = await skillhubProviderFor(node, cfg, deps, c.req.url);
37471
- const result2 = await dispatchSkillhubCmd(provider, command, args2);
38365
+ const result2 = await dispatchSkillhubCmd2(provider, command, args2);
37472
38366
  return renderResult(result2, negotiate(c.req.header("accept")));
37473
38367
  }
37474
38368
  if (node.kind !== "builtin" || node.config?.kind !== "builtin") {
@@ -37479,6 +38373,7 @@ async function handleInvoke(c, env) {
37479
38373
  if (!mod) throw TBError.unimplemented(`builtin module '${node.config.module}' not available`);
37480
38374
  const args = await readInvokeBody();
37481
38375
  const cmd = command;
38376
+ rejectStoreCapabilityBodyFields(node.config.module, args);
37482
38377
  const spec = scopeForCmd(mod, node.path, cmd);
37483
38378
  if (!spec) throw new TBError("invalid_argument", `unknown cmd '${cmd}' on '${node.path}'`);
37484
38379
  if (!check(ctx, node.path, spec.scope).allow) {
@@ -37522,7 +38417,9 @@ async function handleInvoke(c, env) {
37522
38417
  const registryMarker = registryTarget === void 0 ? void 0 : await searchSync?.markNode(registryTarget);
37523
38418
  let result;
37524
38419
  try {
37525
- result = await mod.dispatch(cmd, args, ctx);
38420
+ result = await mod.dispatch(cmd, args, ctx, {
38421
+ requestOrigin: resolveStoreRequestOrigin(c.req.url, deps.canonicalOrigin)
38422
+ });
37526
38423
  } catch (error) {
37527
38424
  await searchSync?.abort(registryMarker);
37528
38425
  await Promise.all(pluginMounts.map(async (mount) => await searchSync?.abort(mount.marker)));
@@ -37573,17 +38470,21 @@ async function handleInvoke(c, env) {
37573
38470
  // ../app/src/routes/env.ts
37574
38471
  function createRouteEnv(deps) {
37575
38472
  const searchSync = isMutableSearchIndex(deps.search) ? new SearchSynchronizer(deps.state, deps.search) : void 0;
37576
- const builtinsOf = (store) => createBuiltins(
37577
- buildDeps({
37578
- store,
37579
- secrets: deps.secrets,
37580
- version: deps.version,
37581
- allowInsecureHttp: deps.allowInsecureHttp,
37582
- remoteAllowlistBase: deps.remote.allowlist,
37583
- ...deps.pluginBindings !== void 0 ? { pluginBindings: deps.pluginBindings } : {},
37584
- ...deps.pluginCatalog !== void 0 ? { pluginCatalog: deps.pluginCatalog } : {}
37585
- })
37586
- );
38473
+ const builtinsOf = (store) => {
38474
+ const builtins = createBuiltins(
38475
+ buildDeps({
38476
+ store,
38477
+ secrets: deps.secrets,
38478
+ version: deps.version,
38479
+ allowInsecureHttp: deps.allowInsecureHttp,
38480
+ remoteAllowlistBase: deps.remote.allowlist,
38481
+ ...deps.pluginBindings !== void 0 ? { pluginBindings: deps.pluginBindings } : {},
38482
+ ...deps.pluginCatalog !== void 0 ? { pluginCatalog: deps.pluginCatalog } : {}
38483
+ })
38484
+ );
38485
+ builtins.set("store", lazyDefaultStoreModule(deps));
38486
+ return builtins;
38487
+ };
37587
38488
  const globalSearchCapabilities = () => {
37588
38489
  const declared = new Set(deps.search?.capabilities ?? []);
37589
38490
  if (!declared.has("search")) return [];
@@ -37629,7 +38530,7 @@ async function handleHelp(c, env) {
37629
38530
  if (remote) return remote;
37630
38531
  const builtins = builtinsOf(store);
37631
38532
  const refresh = c.req.query("refresh") === "1";
37632
- const schemas = c.req.query("schemas") === "1";
38533
+ const schemas2 = c.req.query("schemas") === "1";
37633
38534
  let node;
37634
38535
  try {
37635
38536
  node = await registry.get(path);
@@ -37638,14 +38539,14 @@ async function handleHelp(c, env) {
37638
38539
  if (toolModel !== null) return renderHelp(await enrichHelp(toolModel, path, store), rep);
37639
38540
  const cmdModel = await commandHelpModelFor(registry, ctx, builtins, deps, path, {
37640
38541
  refresh,
37641
- schemas
38542
+ schemas: schemas2
37642
38543
  });
37643
38544
  if (cmdModel !== null) return renderHelp(await enrichHelp(cmdModel, path, store), rep);
37644
38545
  throw TBError.notFound("not found");
37645
38546
  }
37646
38547
  const model = await helpModelFor(node, registry, ctx, builtins, deps, {
37647
38548
  refresh,
37648
- schemas,
38549
+ schemas: schemas2,
37649
38550
  now: (/* @__PURE__ */ new Date()).toISOString()
37650
38551
  });
37651
38552
  return renderHelp(await enrichHelp(model, path, store), rep);
@@ -37697,7 +38598,7 @@ async function handleTree(c, env) {
37697
38598
  });
37698
38599
  const rep = negotiate(c.req.header("accept"));
37699
38600
  if (rep === "json") {
37700
- return new Response(JSON.stringify(tree), {
38601
+ return new Response(JSON.stringify(treeJsonSchema.parse(tree)), {
37701
38602
  headers: { "content-type": contentTypeFor("json") }
37702
38603
  });
37703
38604
  }
@@ -37724,6 +38625,7 @@ function createTbApp(deps) {
37724
38625
  if (secured !== response) c.res = secured;
37725
38626
  });
37726
38627
  registerPublicRoutes(app, env);
38628
+ registerStoreCapabilityRoutes(app, deps);
37727
38629
  app.use("*", async (c, next) => {
37728
38630
  const store = deps.state;
37729
38631
  try {
@@ -38250,6 +39152,19 @@ var DeviceRouter = class _DeviceRouter {
38250
39152
  }
38251
39153
  };
38252
39154
 
39155
+ // src/objects.ts
39156
+ import {
39157
+ link,
39158
+ mkdir as mkdir2,
39159
+ open,
39160
+ readdir as readdir2,
39161
+ realpath as realpath2,
39162
+ rename,
39163
+ rm as rm2,
39164
+ stat as stat2
39165
+ } from "fs/promises";
39166
+ import { constants, mkdirSync } from "fs";
39167
+
38253
39168
  // ../core/src/node/fsObjectStore.ts
38254
39169
  import { mkdir, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
38255
39170
  import { basename, dirname, join, resolve, sep } from "path";
@@ -38349,6 +39264,9 @@ var FsObjectStore = class {
38349
39264
  throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':\u672A\u77E5\u6839\u6216\u7F3A\u76F8\u5BF9\u8DEF\u5F84`);
38350
39265
  }
38351
39266
  await this.assertInRoot(r.root, r.full, key);
39267
+ if (opts?.ifMatchEtag !== void 0 && opts.ifNoneMatch !== void 0) {
39268
+ throw new TBError("invalid_argument", "ifMatchEtag \u4E0E ifNoneMatch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
39269
+ }
38352
39270
  if (opts?.ifMatchEtag !== void 0) {
38353
39271
  const existing = await this.head(key);
38354
39272
  if (opts.ifMatchEtag !== existing?.etag) {
@@ -38357,7 +39275,14 @@ var FsObjectStore = class {
38357
39275
  }
38358
39276
  const bytes = await objectBodyToBytes(body);
38359
39277
  await mkdir(dirname(r.full), { recursive: true });
38360
- await writeFile(r.full, bytes);
39278
+ try {
39279
+ await writeFile(r.full, bytes, opts?.ifNoneMatch === "*" ? { flag: "wx" } : void 0);
39280
+ } catch (e) {
39281
+ if (opts?.ifNoneMatch === "*" && isErrnoCode(e, "EEXIST")) {
39282
+ throw new TBError("conflict", `\u5BF9\u8C61\u5DF2\u5B58\u5728:'${key}'`);
39283
+ }
39284
+ throw e;
39285
+ }
38361
39286
  const st = await stat(r.full);
38362
39287
  return this.metaOf(r.key, st);
38363
39288
  }
@@ -38491,9 +39416,14 @@ import { spawn as nodeSpawn } from "child_process";
38491
39416
  var SHELL_OUTPUT_LIMIT_BYTES = 1024 * 1024;
38492
39417
 
38493
39418
  // src/objects.ts
38494
- import { mkdirSync } from "fs";
38495
- import { join as join2 } from "path";
39419
+ import { dirname as dirname2, join as join2, sep as sep2 } from "path";
39420
+ import { randomUUID } from "crypto";
39421
+ import { Readable } from "stream";
38496
39422
  var INTERNAL_ROOT = "objects";
39423
+ var TEMP_PREFIX = ".tb-object-upload-";
39424
+ var STORE_KEY_PREFIX = `${DEFAULT_STORE_DRIVER_KEY_ROOT}/`;
39425
+ var STORE_STAGING_DIR = ".tb-store-staging-v1";
39426
+ var UPLOAD_TEMP_NAME_RE = /^\.tb-object-upload-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.tmp$/i;
38497
39427
  function toInternal(key) {
38498
39428
  return `${INTERNAL_ROOT}/${key}`;
38499
39429
  }
@@ -38503,6 +39433,108 @@ function toExternal(key) {
38503
39433
  function externalMeta(meta) {
38504
39434
  return { ...meta, key: toExternal(meta.key) };
38505
39435
  }
39436
+ function isErrnoCode2(error, code) {
39437
+ return typeof error === "object" && error !== null && error.code === code;
39438
+ }
39439
+ function isUploadTempKey(key) {
39440
+ return key.split("/").some((segment) => segment.startsWith(TEMP_PREFIX));
39441
+ }
39442
+ async function writeChunk(handle, chunk) {
39443
+ let offset = 0;
39444
+ while (offset < chunk.byteLength) {
39445
+ const { bytesWritten } = await handle.write(chunk, offset, chunk.byteLength - offset, null);
39446
+ if (bytesWritten === 0) throw new Error("\u5BF9\u8C61\u4E34\u65F6\u6587\u4EF6\u5199\u5165\u672A\u53D6\u5F97\u8FDB\u5C55");
39447
+ offset += bytesWritten;
39448
+ }
39449
+ }
39450
+ async function writeStream(handle, stream) {
39451
+ const reader = stream.getReader();
39452
+ try {
39453
+ for (; ; ) {
39454
+ const { done, value } = await reader.read();
39455
+ if (done) break;
39456
+ if (value !== void 0 && value.byteLength > 0) await writeChunk(handle, value);
39457
+ }
39458
+ } catch (error) {
39459
+ try {
39460
+ await reader.cancel?.(error);
39461
+ } catch {
39462
+ }
39463
+ throw error;
39464
+ } finally {
39465
+ reader.releaseLock();
39466
+ }
39467
+ }
39468
+ async function writeBody(handle, body) {
39469
+ if (typeof body === "string") {
39470
+ await writeChunk(handle, Buffer.from(body));
39471
+ return;
39472
+ }
39473
+ if (body instanceof Uint8Array) {
39474
+ await writeChunk(handle, body);
39475
+ return;
39476
+ }
39477
+ if (body instanceof ArrayBuffer) {
39478
+ await writeChunk(handle, new Uint8Array(body));
39479
+ return;
39480
+ }
39481
+ await writeStream(handle, body);
39482
+ }
39483
+ async function assertParentInRoot(root, full, key) {
39484
+ const rootReal = await realpath2(root);
39485
+ const parentReal = await realpath2(dirname2(full));
39486
+ if (parentReal !== rootReal && !parentReal.startsWith(rootReal + sep2)) {
39487
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39488
+ }
39489
+ }
39490
+ async function assertAncestorInRoot(root, full, key) {
39491
+ const rootReal = await realpath2(root);
39492
+ let probe = dirname2(full);
39493
+ for (; ; ) {
39494
+ try {
39495
+ const ancestorReal = await realpath2(probe);
39496
+ if (ancestorReal !== rootReal && !ancestorReal.startsWith(rootReal + sep2)) {
39497
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39498
+ }
39499
+ return;
39500
+ } catch (error) {
39501
+ if (error instanceof TBError) throw error;
39502
+ if (!isErrnoCode2(error, "ENOENT")) throw error;
39503
+ const parent = dirname2(probe);
39504
+ if (parent === probe) {
39505
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39506
+ }
39507
+ probe = parent;
39508
+ }
39509
+ }
39510
+ }
39511
+ async function cleanupStagingTree(dir, cutoffMs) {
39512
+ let entries;
39513
+ try {
39514
+ entries = await readdir2(dir, { withFileTypes: true });
39515
+ } catch (error) {
39516
+ if (isErrnoCode2(error, "ENOENT")) return 0;
39517
+ throw error;
39518
+ }
39519
+ let deleted = 0;
39520
+ for (const entry of entries) {
39521
+ const path = join2(dir, entry.name);
39522
+ if (entry.isDirectory()) {
39523
+ deleted += await cleanupStagingTree(path, cutoffMs);
39524
+ continue;
39525
+ }
39526
+ if (!entry.isFile() || !UPLOAD_TEMP_NAME_RE.test(entry.name)) continue;
39527
+ try {
39528
+ const info = await stat2(path);
39529
+ if (info.mtimeMs > cutoffMs) continue;
39530
+ await rm2(path);
39531
+ deleted++;
39532
+ } catch (error) {
39533
+ if (!isErrnoCode2(error, "ENOENT")) throw error;
39534
+ }
39535
+ }
39536
+ return deleted;
39537
+ }
38506
39538
  function createDataObjectStore(dataDir) {
38507
39539
  const root = join2(dataDir, INTERNAL_ROOT);
38508
39540
  mkdirSync(root, { recursive: true });
@@ -38513,11 +39545,77 @@ function createDataObjectStore(dataDir) {
38513
39545
  return meta === null ? null : externalMeta(meta);
38514
39546
  },
38515
39547
  async get(key) {
38516
- const got = await fs.get(toInternal(key));
38517
- return got === null ? null : { meta: externalMeta(got.meta), body: got.body };
39548
+ const normalizedKey = normalizeEntryPath(key);
39549
+ const meta = await fs.head(toInternal(normalizedKey));
39550
+ if (meta === null) return null;
39551
+ const finalPath = join2(root, ...normalizedKey.split("/"));
39552
+ let handle;
39553
+ try {
39554
+ handle = await open(finalPath, constants.O_RDONLY | constants.O_NOFOLLOW);
39555
+ } catch (error) {
39556
+ if (isErrnoCode2(error, "ENOENT")) return null;
39557
+ if (isErrnoCode2(error, "ELOOP")) {
39558
+ throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
39559
+ }
39560
+ throw error;
39561
+ }
39562
+ const body = Readable.toWeb(handle.createReadStream());
39563
+ return { meta: externalMeta(meta), body };
38518
39564
  },
38519
39565
  async put(key, body, opts) {
38520
- return externalMeta(await fs.put(toInternal(key), body, opts));
39566
+ const normalizedKey = normalizeEntryPath(key);
39567
+ const internalKey = toInternal(normalizedKey);
39568
+ const finalPath = join2(root, ...normalizedKey.split("/"));
39569
+ const existing = await fs.head(internalKey);
39570
+ if (opts?.ifMatchEtag !== void 0 && opts.ifNoneMatch !== void 0) {
39571
+ throw new TBError("invalid_argument", "ifMatchEtag \u4E0E ifNoneMatch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
39572
+ }
39573
+ if (opts?.ifMatchEtag !== void 0) {
39574
+ if (opts.ifMatchEtag !== existing?.etag) {
39575
+ throw new TBError("conflict", `etag \u4E0D\u5339\u914D:'${key}'`);
39576
+ }
39577
+ }
39578
+ await assertAncestorInRoot(root, finalPath, key);
39579
+ await mkdir2(dirname2(finalPath), { recursive: true });
39580
+ await assertParentInRoot(root, finalPath, key);
39581
+ const tempDir = normalizedKey.startsWith(STORE_KEY_PREFIX) ? join2(root, STORE_STAGING_DIR) : dirname2(finalPath);
39582
+ await assertAncestorInRoot(root, join2(tempDir, "staging-probe"), key);
39583
+ await mkdir2(tempDir, { recursive: true });
39584
+ const tempPath = join2(tempDir, `${TEMP_PREFIX}${randomUUID()}.tmp`);
39585
+ await assertParentInRoot(root, tempPath, key);
39586
+ let handle;
39587
+ try {
39588
+ handle = await open(tempPath, "wx");
39589
+ await writeBody(handle, body);
39590
+ await handle.sync();
39591
+ await handle.close();
39592
+ handle = void 0;
39593
+ if (opts?.ifNoneMatch === "*") {
39594
+ try {
39595
+ await link(tempPath, finalPath);
39596
+ } catch (error) {
39597
+ if (isErrnoCode2(error, "EEXIST")) {
39598
+ throw new TBError("conflict", `\u5BF9\u8C61\u5DF2\u5B58\u5728:'${key}'`);
39599
+ }
39600
+ throw error;
39601
+ }
39602
+ await rm2(tempPath);
39603
+ } else {
39604
+ await rename(tempPath, finalPath);
39605
+ }
39606
+ } catch (error) {
39607
+ if (handle !== void 0) {
39608
+ try {
39609
+ await handle.close();
39610
+ } catch {
39611
+ }
39612
+ }
39613
+ await rm2(tempPath, { force: true });
39614
+ throw error;
39615
+ }
39616
+ const meta = await fs.head(internalKey);
39617
+ if (meta === null) throw new TBError("unavailable", `\u5BF9\u8C61\u63D0\u4EA4\u540E\u4E0D\u53EF\u89C1:'${key}'`);
39618
+ return externalMeta(meta);
38521
39619
  },
38522
39620
  async delete(key) {
38523
39621
  await fs.delete(toInternal(key));
@@ -38527,9 +39625,24 @@ function createDataObjectStore(dataDir) {
38527
39625
  return {
38528
39626
  items: result.items.map(
38529
39627
  (item) => "prefix" in item ? { prefix: toExternal(item.prefix) } : externalMeta(item)
38530
- ),
39628
+ ).filter((item) => !isUploadTempKey("prefix" in item ? item.prefix : item.key)),
38531
39629
  ...result.cursor !== void 0 ? { cursor: result.cursor } : {}
38532
39630
  };
39631
+ },
39632
+ async cleanupStaging(prefix, olderThan) {
39633
+ const cutoffMs = Date.parse(olderThan);
39634
+ if (!Number.isFinite(cutoffMs)) {
39635
+ throw new TBError("invalid_argument", "cleanupStaging olderThan \u5FC5\u987B\u662F ISO timestamp");
39636
+ }
39637
+ const normalizedPrefix = normalizeEntryPath(prefix.replace(/\/+$/, ""));
39638
+ const isDefaultStore = normalizedPrefix === STORE_KEY_PREFIX.slice(0, -1);
39639
+ const prefixDir = isDefaultStore ? join2(root, STORE_STAGING_DIR) : join2(root, ...normalizedPrefix.split("/"));
39640
+ await assertAncestorInRoot(root, join2(prefixDir, "staging-probe"), prefix);
39641
+ if (isDefaultStore) {
39642
+ await mkdir2(prefixDir, { recursive: true });
39643
+ await assertParentInRoot(root, join2(prefixDir, "staging-probe"), prefix);
39644
+ }
39645
+ return await cleanupStagingTree(prefixDir, cutoffMs);
38533
39646
  }
38534
39647
  };
38535
39648
  }
@@ -38652,6 +39765,29 @@ var PgStateStore = class {
38652
39765
  ON CONFLICT (key) DO UPDATE SET value = excluded.value
38653
39766
  `;
38654
39767
  }
39768
+ async compareAndSwap(key, expectedRevision, value) {
39769
+ if (expectedRevision === null) {
39770
+ if (value === null) return false;
39771
+ const result2 = await this.sql`
39772
+ INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
39773
+ ON CONFLICT (key) DO NOTHING
39774
+ `;
39775
+ return result2.count > 0;
39776
+ }
39777
+ if (!Number.isInteger(expectedRevision) || expectedRevision < 0) return false;
39778
+ const predicate = this.sql`
39779
+ key = ${key}
39780
+ AND jsonb_typeof(value) = 'object'
39781
+ AND jsonb_typeof(value -> 'revision') = 'number'
39782
+ AND (value ->> 'revision') ~ '^(0|[1-9][0-9]*)$'
39783
+ AND (value ->> 'revision')::numeric = ${expectedRevision}
39784
+ `;
39785
+ const result = value === null ? await this.sql`DELETE FROM tb_kv WHERE ${predicate}` : await this.sql`
39786
+ UPDATE tb_kv SET value = ${this.sql.json(value)}
39787
+ WHERE ${predicate}
39788
+ `;
39789
+ return result.count > 0;
39790
+ }
38655
39791
  async putIfAbsent(key, value) {
38656
39792
  const result = await this.sql`
38657
39793
  INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
@@ -38834,6 +39970,20 @@ var SqliteStateStore = class {
38834
39970
  async put(key, value) {
38835
39971
  this.stmtPut.run(key, JSON.stringify(value));
38836
39972
  }
39973
+ async compareAndSwap(key, expectedRevision, value) {
39974
+ if (expectedRevision === null) {
39975
+ if (value === null) return false;
39976
+ const info2 = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
39977
+ return info2.changes > 0;
39978
+ }
39979
+ if (!Number.isInteger(expectedRevision) || expectedRevision < 0) return false;
39980
+ const predicate = `key = ?
39981
+ AND json_type(value) = 'object'
39982
+ AND json_type(value, '$.revision') = 'integer'
39983
+ AND json_extract(value, '$.revision') = ?`;
39984
+ 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);
39985
+ return info.changes > 0;
39986
+ }
38837
39987
  async putIfAbsent(key, value) {
38838
39988
  const info = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
38839
39989
  return info.changes > 0;
@@ -38881,7 +40031,7 @@ import { join as join4 } from "path";
38881
40031
  // package.json
38882
40032
  var package_default = {
38883
40033
  name: "@tool-bridge/server",
38884
- version: "0.15.0",
40034
+ version: "0.17.0",
38885
40035
  description: "tool-bridge self-hosted server: run the HTBP gateway on Node with SQLite or PostgreSQL persistence, filesystem object storage and WebSocket device channel \u2014 single container via docker run",
38886
40036
  type: "module",
38887
40037
  license: "MIT",
@@ -38954,7 +40104,7 @@ var package_default = {
38954
40104
 
38955
40105
  // src/assets.ts
38956
40106
  import { brotliCompressSync, gzipSync, constants as zlibConstants } from "zlib";
38957
- import { dirname as dirname2, join as join3, resolve as resolve2, sep as sep2 } from "path";
40107
+ import { dirname as dirname3, join as join3, resolve as resolve2, sep as sep3 } from "path";
38958
40108
  import { readFile as readFile2 } from "fs/promises";
38959
40109
  import { createRequire } from "module";
38960
40110
  import { existsSync } from "fs";
@@ -38987,7 +40137,7 @@ function resolveUiDir(uiDirOverride) {
38987
40137
  try {
38988
40138
  const require2 = createRequire(import.meta.url);
38989
40139
  const pkgPath = require2.resolve("@tool-bridge/dashboard/package.json");
38990
- const dist = join3(dirname2(pkgPath), "dist");
40140
+ const dist = join3(dirname3(pkgPath), "dist");
38991
40141
  return existsSync(join3(dist, "index.html")) ? dist : void 0;
38992
40142
  } catch {
38993
40143
  return void 0;
@@ -39014,7 +40164,7 @@ function uiAssetsFetcher(uiDir) {
39014
40164
  let pathname = decodeURIComponent(url2.pathname);
39015
40165
  if (pathname.endsWith("/")) pathname += "index.html";
39016
40166
  const full = resolve2(join3(root, pathname));
39017
- if (full !== root && !full.startsWith(root + sep2)) {
40167
+ if (full !== root && !full.startsWith(root + sep3)) {
39018
40168
  return new Response("not found", { status: 404 });
39019
40169
  }
39020
40170
  let asset = cache.get(full);
@@ -39063,6 +40213,7 @@ function resolveUiAssets(uiDirOverride) {
39063
40213
  }
39064
40214
 
39065
40215
  // src/server.ts
40216
+ var providerOAuthFetch = createGuardedFetch({ crossOriginRedirect: "error" });
39066
40217
  function pgBackends(databaseUrl) {
39067
40218
  const sql = postgres(databaseUrl, { onnotice: () => {
39068
40219
  } });
@@ -39176,6 +40327,7 @@ function createTbServer(config) {
39176
40327
  const deps = {
39177
40328
  state,
39178
40329
  secrets,
40330
+ providerOAuthFetch,
39179
40331
  version: package_default.version,
39180
40332
  remote: config.remote,
39181
40333
  search,
@@ -39196,8 +40348,49 @@ function createTbServer(config) {
39196
40348
  if (config.uploadGrantTtlSec !== void 0) {
39197
40349
  deps.uploadGrantTtlSec = config.uploadGrantTtlSec;
39198
40350
  }
40351
+ if (config.storeTokenSecret !== void 0) deps.storeTokenSecret = config.storeTokenSecret;
40352
+ if (config.storeMaxObjectBytes !== void 0) {
40353
+ deps.storeMaxObjectBytes = config.storeMaxObjectBytes;
40354
+ }
40355
+ if (config.storeRelayMaxBytes !== void 0) {
40356
+ deps.storeRelayMaxBytes = config.storeRelayMaxBytes;
40357
+ }
40358
+ if (config.storeUploadTtlSec !== void 0) deps.storeUploadTtlSec = config.storeUploadTtlSec;
40359
+ if (config.storeShareTtlSec !== void 0) deps.storeShareTtlSec = config.storeShareTtlSec;
40360
+ if (config.storeReadTtlSec !== void 0) deps.storeReadTtlSec = config.storeReadTtlSec;
40361
+ if (config.storeCallMaxBytes !== void 0) deps.storeCallMaxBytes = config.storeCallMaxBytes;
40362
+ if (config.storeCallMaxObjectBytes !== void 0) {
40363
+ deps.storeCallMaxObjectBytes = config.storeCallMaxObjectBytes;
40364
+ }
40365
+ if (config.storeCallMaxObjects !== void 0) {
40366
+ deps.storeCallMaxObjects = config.storeCallMaxObjects;
40367
+ }
40368
+ if (config.storeCallAllowedContentTypes !== void 0) {
40369
+ deps.storeCallAllowedContentTypes = config.storeCallAllowedContentTypes;
40370
+ }
39199
40371
  const app = createTbApp(deps);
39200
40372
  let server;
40373
+ let storeCleanupTimer;
40374
+ let storeCleanupInFlight = false;
40375
+ const runStoreCleanup = async () => {
40376
+ if (storeCleanupInFlight) return;
40377
+ storeCleanupInFlight = true;
40378
+ try {
40379
+ await cleanupDefaultStore(deps);
40380
+ } finally {
40381
+ storeCleanupInFlight = false;
40382
+ }
40383
+ };
40384
+ const reportStoreCleanupFailure = () => {
40385
+ console.warn(JSON.stringify({ event: "tool_bridge_store_cleanup_failed" }));
40386
+ };
40387
+ const scheduleStoreCleanup = () => {
40388
+ void runStoreCleanup().catch(reportStoreCleanupFailure);
40389
+ storeCleanupTimer = setInterval(() => {
40390
+ void runStoreCleanup().catch(reportStoreCleanupFailure);
40391
+ }, config.storeCleanupIntervalSec * 1e3);
40392
+ storeCleanupTimer.unref?.();
40393
+ };
39201
40394
  return {
39202
40395
  app,
39203
40396
  search,
@@ -39214,6 +40407,7 @@ function createTbServer(config) {
39214
40407
  await hub.sweepOrphans();
39215
40408
  return await new Promise((resolve3) => {
39216
40409
  server = serve({ fetch: app.fetch, port: config.port, hostname: config.host }, (info) => {
40410
+ scheduleStoreCleanup();
39217
40411
  resolve3({ port: info.port });
39218
40412
  });
39219
40413
  hub.attach(server);
@@ -39224,6 +40418,10 @@ function createTbServer(config) {
39224
40418
  },
39225
40419
  async close() {
39226
40420
  draining = true;
40421
+ if (storeCleanupTimer !== void 0) {
40422
+ clearInterval(storeCleanupTimer);
40423
+ storeCleanupTimer = void 0;
40424
+ }
39227
40425
  let closed;
39228
40426
  if (server !== void 0) {
39229
40427
  const s = server;