@zackbart/connecta 0.34.1 → 0.35.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 (429) hide show
  1. package/CHANGELOG.md +363 -0
  2. package/README.md +62 -106
  3. package/SECURITY.md +3 -2
  4. package/bin/connecta.mjs +41 -16
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +2 -1
  7. package/dist/access-tokens.js +66 -30
  8. package/dist/activity.d.ts +1 -1
  9. package/dist/activity.js +2 -0
  10. package/dist/agent-config.d.ts +23 -0
  11. package/dist/agent-config.js +59 -0
  12. package/dist/agent-description.js +16 -0
  13. package/dist/agent-identity.js +4 -4
  14. package/dist/agent-observation.js +39 -0
  15. package/dist/auth/clerk-sdk/client.d.ts +2 -0
  16. package/dist/auth/clerk-sdk/generated.js +554 -563
  17. package/dist/auth/clerk-transport.js +70 -3
  18. package/dist/auth/clerk.d.ts +13 -0
  19. package/dist/auth/clerk.js +653 -246
  20. package/dist/auth/cloudflare-access.js +46 -42
  21. package/dist/auth/downstream-client-metadata.js +2 -2
  22. package/dist/auth/downstream-oauth.js +2051 -1160
  23. package/dist/auth/downstream-revocation.js +88 -88
  24. package/dist/auth/native-call.js +20 -0
  25. package/dist/auth/native.js +184 -0
  26. package/dist/auth/oauth-start-reset.js +11 -5
  27. package/dist/auth/oauth-token-response.js +121 -102
  28. package/dist/auth/oauth-v2-migration.js +97 -78
  29. package/dist/auth/renewable-grant.js +10 -0
  30. package/dist/auth/static-oauth.js +663 -271
  31. package/dist/auth/vercel-agent.js +266 -0
  32. package/dist/auth-admission.js +204 -0
  33. package/dist/auth-elicitation.js +350 -241
  34. package/dist/authorization-handoff.js +164 -90
  35. package/dist/call-admission-context.js +3 -10
  36. package/dist/call-admission.js +2 -0
  37. package/dist/call-classification.js +9 -0
  38. package/dist/call-recovery.js +32 -13
  39. package/dist/canonical-json.js +8 -0
  40. package/dist/catalog-cache.js +369 -213
  41. package/dist/catalog-service.js +659 -530
  42. package/dist/catalog.js +5 -1
  43. package/dist/concurrency.js +0 -25
  44. package/dist/config-defaults.d.ts +1 -0
  45. package/dist/config-defaults.js +1 -0
  46. package/dist/config-schema.d.ts +3 -1
  47. package/dist/config-schema.js +13 -3
  48. package/dist/config.d.ts +40 -6
  49. package/dist/config.js +40 -26
  50. package/dist/connector-caller.d.ts +4 -0
  51. package/dist/connector-caller.js +17 -4
  52. package/dist/connectors/api-connector.d.ts +12 -7
  53. package/dist/connectors/api-connector.js +142 -56
  54. package/dist/connectors/guarded-fetch.d.ts +51 -17
  55. package/dist/connectors/guarded-fetch.js +215 -121
  56. package/dist/connectors/negotiation-cache.js +20 -10
  57. package/dist/connectors/remote-mcp.d.ts +10 -30
  58. package/dist/connectors/remote-mcp.js +955 -563
  59. package/dist/d1.js +10 -243
  60. package/dist/derived-context.js +5 -0
  61. package/dist/describe-config.d.ts +3 -1
  62. package/dist/describe-config.js +2 -0
  63. package/dist/downstream-input.js +347 -260
  64. package/dist/errors.d.ts +87 -6
  65. package/dist/errors.js +114 -4
  66. package/dist/execute.js +87 -50
  67. package/dist/executor-admission.js +3 -0
  68. package/dist/executors/quickjs.js +163 -112
  69. package/dist/guarded-transport.js +50 -0
  70. package/dist/guest-runtime.js +1 -1
  71. package/dist/http/client.js +439 -0
  72. package/dist/http/errors.js +558 -0
  73. package/dist/http/fields.js +49 -0
  74. package/dist/http/hatch-paging.js +318 -0
  75. package/dist/http/hatch.js +1279 -0
  76. package/dist/http/json-schema.js +267 -0
  77. package/dist/http/node-transport.js +241 -0
  78. package/dist/http/oauth.js +162 -0
  79. package/dist/http/paging.js +228 -0
  80. package/dist/http/provider.js +221 -0
  81. package/dist/http/safety.js +82 -0
  82. package/dist/http/services.js +79 -0
  83. package/dist/http/tools.js +383 -0
  84. package/dist/http/vendor-index.js +138 -0
  85. package/dist/http/vendor.js +448 -0
  86. package/dist/http/write-request.js +66 -0
  87. package/dist/http-policy.d.ts +11 -0
  88. package/dist/http-policy.js +45 -0
  89. package/dist/inbound-credential.js +1 -1
  90. package/dist/index.d.ts +2 -2
  91. package/dist/index.js +4 -0
  92. package/dist/invocation-auth.js +30 -19
  93. package/dist/invocation.js +218 -85
  94. package/dist/mcp-result.js +0 -4
  95. package/dist/membership.d.ts +4 -0
  96. package/dist/meta-output.js +1 -1
  97. package/dist/meta-tools.js +531 -406
  98. package/dist/native-recovery.js +52 -0
  99. package/dist/native-tools.js +382 -0
  100. package/dist/node.d.ts +4 -3
  101. package/dist/node.js +45 -8
  102. package/dist/oauth-handoff.js +177 -104
  103. package/dist/oauth-partition.js +6 -20
  104. package/dist/oauth-sealing.js +6 -7
  105. package/dist/operation-vocabulary.js +101 -0
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +17 -4
  108. package/dist/operator-ui/agents.d.ts +5 -4
  109. package/dist/operator-ui/agents.js +17 -7
  110. package/dist/operator-ui/config-summary.js +5 -4
  111. package/dist/operator-ui/connection-map.js +7 -5
  112. package/dist/operator-ui/contract.d.ts +7 -3
  113. package/dist/operator-ui/endpoints.js +4 -13
  114. package/dist/operator-ui/fix-prompts.d.ts +1 -1
  115. package/dist/operator-ui/fix-prompts.js +0 -8
  116. package/dist/operator-ui/generated.js +3 -3
  117. package/dist/operator-ui/model.d.ts +2 -3
  118. package/dist/operator-ui/overview.js +5 -2
  119. package/dist/operator-ui/setup-commands.js +0 -4
  120. package/dist/operator-ui/view.d.ts +2 -25
  121. package/dist/operator-ui/view.js +2 -76
  122. package/dist/program-writes.js +47 -26
  123. package/dist/provider-call-facts.d.ts +115 -0
  124. package/dist/provider-call-facts.js +182 -0
  125. package/dist/provider-call.d.ts +112 -0
  126. package/dist/provider-call.js +271 -0
  127. package/dist/provider.d.ts +65 -31
  128. package/dist/provider.js +116 -39
  129. package/dist/providers/_shared/google/hatch.js +91 -0
  130. package/dist/providers/_shared/google/kit.js +537 -0
  131. package/dist/providers/_shared/google/result-size.js +15 -2
  132. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  133. package/dist/providers/_shared/google/service-account.js +8 -4
  134. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  135. package/dist/providers/_shared/google/workspace.js +6 -425
  136. package/dist/providers/_shared/rest/compact-index.js +86 -0
  137. package/dist/providers/_shared/rest/dispatch.js +3 -23
  138. package/dist/providers/_shared/rest/kit.js +546 -0
  139. package/dist/providers/_shared/rest/operation-index.js +230 -14
  140. package/dist/providers/_shared/rest/response-media.js +11 -0
  141. package/dist/providers/_shared/rest/tools.js +101 -78
  142. package/dist/providers/_shared/rest/value-safety.js +353 -195
  143. package/dist/providers/_shared/sensitive-fields.js +27 -0
  144. package/dist/providers/breeze/api.js +584 -0
  145. package/dist/providers/breeze/index.d.ts +1 -3
  146. package/dist/providers/breeze/index.js +67 -1420
  147. package/dist/providers/breeze/openapi.generated.js +60 -0
  148. package/dist/providers/breeze/skill.generated.js +2 -6
  149. package/dist/providers/breeze/tools.js +696 -0
  150. package/dist/providers/ccb/api.js +496 -0
  151. package/dist/providers/ccb/index.d.ts +15 -37
  152. package/dist/providers/ccb/index.js +77 -1702
  153. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  154. package/dist/providers/ccb/lib/constants.js +74 -0
  155. package/dist/providers/ccb/lib/guide.js +9 -0
  156. package/dist/providers/ccb/lib/verdicts.js +387 -0
  157. package/dist/providers/ccb/openapi.generated.js +397 -0
  158. package/dist/providers/ccb/safety.js +295 -0
  159. package/dist/providers/ccb/skill.generated.js +2 -6
  160. package/dist/providers/ccb/tools.js +853 -0
  161. package/dist/providers/cloudflare/api.js +1066 -0
  162. package/dist/providers/cloudflare/index.d.ts +5 -22
  163. package/dist/providers/cloudflare/index.js +79 -197
  164. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  165. package/dist/providers/cloudflare/lib/config.js +2 -0
  166. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  167. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  168. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  169. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  170. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  171. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  172. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  173. package/dist/providers/cloudflare/skill.generated.js +2 -10
  174. package/dist/providers/cloudflare/tools.js +360 -0
  175. package/dist/providers/docs/api.js +51 -0
  176. package/dist/providers/docs/index.d.ts +1 -12
  177. package/dist/providers/docs/index.js +53 -1165
  178. package/dist/providers/docs/lib/constants.d.ts +18 -0
  179. package/dist/providers/docs/lib/constants.js +18 -0
  180. package/dist/providers/docs/lib/discovery.js +26 -0
  181. package/dist/providers/docs/lib/requests.js +48 -0
  182. package/dist/providers/docs/lib/schemas.js +168 -0
  183. package/dist/providers/docs/openapi.generated.js +16 -0
  184. package/dist/providers/docs/safety.js +54 -0
  185. package/dist/providers/docs/skill.generated.js +2 -6
  186. package/dist/providers/docs/tools.js +802 -0
  187. package/dist/providers/drive/api.js +177 -0
  188. package/dist/providers/drive/index.d.ts +1 -12
  189. package/dist/providers/drive/index.js +38 -1448
  190. package/dist/providers/drive/lib/constants.d.ts +6 -0
  191. package/dist/providers/drive/lib/constants.js +6 -0
  192. package/dist/providers/drive/lib/contracts.js +532 -0
  193. package/dist/providers/drive/lib/hatch.js +197 -0
  194. package/dist/providers/drive/openapi.generated.js +77 -0
  195. package/dist/providers/drive/safety.js +90 -0
  196. package/dist/providers/drive/skill.generated.js +2 -6
  197. package/dist/providers/drive/tools.js +1290 -0
  198. package/dist/providers/forms/api.js +71 -0
  199. package/dist/providers/forms/index.d.ts +1 -12
  200. package/dist/providers/forms/index.js +49 -1145
  201. package/dist/providers/forms/lib/constants.d.ts +14 -0
  202. package/dist/providers/forms/lib/constants.js +19 -0
  203. package/dist/providers/forms/lib/hatch.js +72 -0
  204. package/dist/providers/forms/openapi.generated.js +23 -0
  205. package/dist/providers/forms/safety.js +34 -0
  206. package/dist/providers/forms/skill.generated.js +2 -8
  207. package/dist/providers/forms/tools.js +1024 -0
  208. package/dist/providers/github/api.js +150 -0
  209. package/dist/providers/github/index.d.ts +4 -5
  210. package/dist/providers/github/index.js +30 -87
  211. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  212. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  213. package/dist/providers/github/lib/discovery.js +132 -0
  214. package/dist/providers/github/lib/operations.js +58 -0
  215. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  216. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  217. package/dist/providers/github/openapi.generated.js +1216 -0
  218. package/dist/providers/github/safety.js +216 -0
  219. package/dist/providers/github/skill.generated.js +2 -5
  220. package/dist/providers/github/tools.js +506 -0
  221. package/dist/providers/gmail/api.js +114 -0
  222. package/dist/providers/gmail/index.d.ts +1 -12
  223. package/dist/providers/gmail/index.js +73 -1621
  224. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  225. package/dist/providers/gmail/lib/constants.js +16 -0
  226. package/dist/providers/gmail/lib/index.js +173 -0
  227. package/dist/providers/gmail/lib/schemas.js +176 -0
  228. package/dist/providers/gmail/lib/threading.js +120 -0
  229. package/dist/providers/gmail/openapi.generated.js +92 -0
  230. package/dist/providers/gmail/safety.js +21 -0
  231. package/dist/providers/gmail/skill.generated.js +2 -6
  232. package/dist/providers/gmail/tools.js +1435 -0
  233. package/dist/providers/overflow/api.js +1008 -0
  234. package/dist/providers/overflow/index.d.ts +1 -17
  235. package/dist/providers/overflow/index.js +60 -1488
  236. package/dist/providers/overflow/skill.generated.js +2 -5
  237. package/dist/providers/overflow/tools.js +873 -0
  238. package/dist/providers/planning-center/api.js +467 -0
  239. package/dist/providers/planning-center/index.d.ts +4 -35
  240. package/dist/providers/planning-center/index.js +69 -2445
  241. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  242. package/dist/providers/planning-center/lib/constants.js +83 -0
  243. package/dist/providers/planning-center/lib/discovery.js +40 -0
  244. package/dist/providers/planning-center/lib/guide.js +39 -0
  245. package/dist/providers/planning-center/lib/json-api.js +83 -0
  246. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  247. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  248. package/dist/providers/planning-center/safety.js +188 -0
  249. package/dist/providers/planning-center/skill.generated.js +2 -27
  250. package/dist/providers/planning-center/tools.js +1010 -0
  251. package/dist/providers/sheets/api.js +133 -0
  252. package/dist/providers/sheets/index.d.ts +4 -16
  253. package/dist/providers/sheets/index.js +61 -1172
  254. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  255. package/dist/providers/sheets/lib/constants.js +14 -0
  256. package/dist/providers/sheets/lib/hatch.js +163 -0
  257. package/dist/providers/sheets/lib/requests.js +105 -0
  258. package/dist/providers/sheets/lib/schemas.js +138 -0
  259. package/dist/providers/sheets/openapi.generated.js +30 -0
  260. package/dist/providers/sheets/safety.js +27 -0
  261. package/dist/providers/sheets/skill.generated.js +2 -7
  262. package/dist/providers/sheets/tools.js +734 -0
  263. package/dist/providers/slides/api.js +147 -0
  264. package/dist/providers/slides/index.d.ts +2 -10
  265. package/dist/providers/slides/index.js +48 -2555
  266. package/dist/providers/slides/lib/constants.d.ts +11 -0
  267. package/dist/providers/slides/lib/constants.js +11 -0
  268. package/dist/providers/slides/lib/requests.js +60 -0
  269. package/dist/providers/slides/lib/schemas.js +565 -0
  270. package/dist/providers/slides/openapi.generated.js +18 -0
  271. package/dist/providers/slides/skill.generated.js +2 -6
  272. package/dist/providers/slides/tools.js +1968 -0
  273. package/dist/providers/stripe/api.js +401 -0
  274. package/dist/providers/stripe/index.d.ts +5 -27
  275. package/dist/providers/stripe/index.js +74 -210
  276. package/dist/providers/stripe/lib/config.d.ts +4 -0
  277. package/dist/providers/stripe/lib/config.js +2 -0
  278. package/dist/providers/stripe/openapi.generated.js +4 -3
  279. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  280. package/dist/providers/stripe/skill.generated.js +2 -11
  281. package/dist/providers/stripe/tools.js +77 -0
  282. package/dist/providers/tithely/api.js +531 -0
  283. package/dist/providers/tithely/index.d.ts +17 -0
  284. package/dist/providers/tithely/index.js +86 -965
  285. package/dist/providers/tithely/skill.generated.js +2 -5
  286. package/dist/providers/tithely/tools.js +426 -0
  287. package/dist/providers/vercel/api.js +551 -0
  288. package/dist/providers/vercel/index.d.ts +9 -25
  289. package/dist/providers/vercel/index.js +66 -260
  290. package/dist/providers/vercel/lib/config.d.ts +2 -0
  291. package/dist/providers/vercel/lib/config.js +2 -0
  292. package/dist/providers/vercel/lib/index.js +117 -0
  293. package/dist/providers/vercel/lib/logs.js +100 -0
  294. package/dist/providers/vercel/lib/pin.js +270 -0
  295. package/dist/providers/vercel/openapi.generated.js +10 -5
  296. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  297. package/dist/providers/vercel/skill.generated.js +2 -7
  298. package/dist/providers/vercel/tools.js +642 -0
  299. package/dist/redaction-identity.js +47 -0
  300. package/dist/redaction-marker.js +125 -0
  301. package/dist/registry.d.ts +21 -15
  302. package/dist/registry.js +409 -376
  303. package/dist/routes/access-tokens.js +1 -1
  304. package/dist/routes/actor-labels.js +1 -1
  305. package/dist/routes/connect.js +1 -1
  306. package/dist/routes/dispatch.js +103 -0
  307. package/dist/routes/mcp.js +170 -79
  308. package/dist/routes/oauth.js +3 -1
  309. package/dist/routes/operator.js +2 -2
  310. package/dist/routes/shared.d.ts +5 -4
  311. package/dist/routes/shared.js +244 -177
  312. package/dist/routes/skills.js +10 -8
  313. package/dist/routes/ui-config.js +27 -3
  314. package/dist/routes/ui-data.js +21 -20
  315. package/dist/routes/ui.js +1 -1
  316. package/dist/runtime/call-admission.js +0 -26
  317. package/dist/runtime/call-entry.js +30 -0
  318. package/dist/runtime/call.js +556 -0
  319. package/dist/runtime/connector-scope.js +7 -0
  320. package/dist/runtime/credential-probe.js +34 -0
  321. package/dist/runtime/executor.js +40 -0
  322. package/dist/runtime/native-storage.js +63 -0
  323. package/dist/runtime/registry.js +27 -0
  324. package/dist/runtime/request-scope.js +72 -0
  325. package/dist/runtime/run.js +15 -7
  326. package/dist/runtime/shared-read.js +34 -5
  327. package/dist/runtime/storage.js +34 -21
  328. package/dist/sent-secrets.d.ts +194 -37
  329. package/dist/sent-secrets.js +1743 -340
  330. package/dist/server.js +5 -56
  331. package/dist/skills.js +322 -222
  332. package/dist/sqlite.js +15 -14
  333. package/dist/storage/d1-copy.js +255 -0
  334. package/dist/storage/d1-driver.js +15 -0
  335. package/dist/storage/keys.js +28 -0
  336. package/dist/storage/sql-driver.js +88 -0
  337. package/dist/storage/sql-native.js +544 -0
  338. package/dist/storage/sql-schema.d.ts +6 -0
  339. package/dist/storage/sql-schema.js +15 -0
  340. package/dist/storage/sql.d.ts +14 -20
  341. package/dist/storage/sql.js +29 -535
  342. package/dist/timeout.js +0 -26
  343. package/dist/tool-safety.js +20 -10
  344. package/dist/types.d.ts +7 -109
  345. package/dist/upward.js +3 -0
  346. package/dist/usage-guide.js +3 -1
  347. package/dist/validate.d.ts +4 -2
  348. package/dist/validate.js +86 -9
  349. package/dist/version.d.ts +1 -1
  350. package/dist/version.js +1 -1
  351. package/dist/worker.js +312 -292
  352. package/dist/workspace-subject.js +5 -1
  353. package/dist/write-outcome.js +454 -34
  354. package/documentation/README.md +27 -7
  355. package/documentation/architecture.md +2547 -226
  356. package/documentation/auth.md +821 -98
  357. package/documentation/code-mode.md +49 -16
  358. package/documentation/deploying.md +164 -20
  359. package/documentation/hosted-mcp-migration.md +460 -0
  360. package/documentation/integrating.md +129 -144
  361. package/documentation/meta-tools.md +409 -32
  362. package/documentation/operating.md +27 -3
  363. package/documentation/operator-tests.md +3 -0
  364. package/documentation/operator-ui.md +68 -45
  365. package/documentation/provider-auth-migration.md +16 -63
  366. package/documentation/provider-migration-0.29.md +10 -5
  367. package/documentation/provider-migration-0.35.md +975 -0
  368. package/documentation/providers.md +965 -0
  369. package/examples/worker/AGENTS.md +23 -9
  370. package/examples/worker/README.md +254 -135
  371. package/examples/worker/alchemy.run.ts +116 -0
  372. package/examples/worker/gitignore +10 -0
  373. package/examples/worker/src/connecta.config.ts +23 -21
  374. package/examples/worker/wrangler.jsonc +14 -5
  375. package/guides/connecting-agents.md +157 -0
  376. package/guides/how-it-works.md +156 -0
  377. package/guides/safety-and-trust.md +153 -0
  378. package/package.json +27 -46
  379. package/templates/node/.env.example +3 -0
  380. package/templates/node/README.md +22 -2
  381. package/templates/node/docker-compose.yml +1 -0
  382. package/templates/node/package.json +3 -2
  383. package/templates/node/src/connecta.config.ts +8 -0
  384. package/dist/catalog-drift.d.ts +0 -138
  385. package/dist/catalog-drift.js +0 -578
  386. package/dist/providers/_shared/request-token.js +0 -83
  387. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  388. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  389. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  390. package/dist/providers/basecamp/index.d.ts +0 -45
  391. package/dist/providers/basecamp/index.js +0 -997
  392. package/dist/providers/basecamp/skill.generated.js +0 -9
  393. package/dist/providers/cloudflare/rest.d.ts +0 -37
  394. package/dist/providers/cloudflare/rest.js +0 -1097
  395. package/dist/providers/github/hosted.js +0 -218
  396. package/dist/providers/github/rest.js +0 -321
  397. package/dist/providers/infisical/index.d.ts +0 -7
  398. package/dist/providers/infisical/index.js +0 -713
  399. package/dist/providers/infisical/skill.generated.js +0 -8
  400. package/dist/providers/intercom/index.d.ts +0 -24
  401. package/dist/providers/intercom/index.js +0 -121
  402. package/dist/providers/intercom/skill.generated.js +0 -8
  403. package/dist/providers/linear/index.d.ts +0 -55
  404. package/dist/providers/linear/index.js +0 -537
  405. package/dist/providers/linear/skill.generated.js +0 -8
  406. package/dist/providers/mixpanel/index.d.ts +0 -59
  407. package/dist/providers/mixpanel/index.js +0 -472
  408. package/dist/providers/mixpanel/operations.js +0 -20
  409. package/dist/providers/mixpanel/rest.js +0 -603
  410. package/dist/providers/mixpanel/skill.generated.js +0 -10
  411. package/dist/providers/mixpanel/value-safety.js +0 -16
  412. package/dist/providers/notion/index.d.ts +0 -67
  413. package/dist/providers/notion/index.js +0 -368
  414. package/dist/providers/notion/openapi.generated.js +0 -75
  415. package/dist/providers/notion/rest.d.ts +0 -24
  416. package/dist/providers/notion/rest.js +0 -1226
  417. package/dist/providers/notion/skill.generated.js +0 -11
  418. package/dist/providers/notion/value-safety.js +0 -56
  419. package/dist/providers/revenuecat/index.d.ts +0 -51
  420. package/dist/providers/revenuecat/index.js +0 -697
  421. package/dist/providers/revenuecat/skill.generated.js +0 -11
  422. package/dist/providers/stripe/rest.d.ts +0 -14
  423. package/dist/providers/stripe/rest.js +0 -402
  424. package/dist/providers/vercel/rest.d.ts +0 -20
  425. package/dist/providers/vercel/rest.js +0 -1020
  426. package/dist/providers/wispr-flow/index.d.ts +0 -14
  427. package/dist/providers/wispr-flow/index.js +0 -117
  428. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  429. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
package/dist/registry.js CHANGED
@@ -1,28 +1,33 @@
1
- import { markCatalogFreshness, carryCatalogFreshness } from "./catalog-freshness.js";
1
+ import { authCall, authInvoke } from "./auth/native.js";
2
+ import { Clock, Duration, Effect, Random } from "effect";
2
3
  import { activityRequest } from "./activity-request.js";
3
- import { attachCatalogCache, catalogExpiry, customCatalogFallback, storeCustomCatalogFallback, catalogFetchedAt, invalidateCatalogCache, observeUncachedCatalogRefresh, } from "./catalog-cache.js";
4
+ import { aggregateCallAdmissionSnapshots, ConnectorCallAdmissionController, } from "./call-admission.js";
5
+ import { attachCatalogCache, catalogExpiry, catalogFetchedAt, customCatalogFallback, invalidateCatalogCache, observeUncachedCatalogRefresh, storeCustomCatalogFallback, } from "./catalog-cache.js";
6
+ import { carryCatalogFreshness, markCatalogFreshness } from "./catalog-freshness.js";
7
+ import { MAX_CATALOG_TOOLS, MAX_SERIALIZED_CATALOG_BYTES } from "./catalog-limits.js";
4
8
  import { CONFIG_DEFAULTS } from "./config-defaults.js";
5
- import { assertStaticToolNames, hasControlCharacters } from "./tool-name.js";
6
- import { Clock, Duration, Effect, Random } from "effect";
9
+ import { usesCallerIdentity } from "./connector-caller.js";
10
+ import { canonicalJson } from "./canonical-json.js";
7
11
  import { storedCredentialShape } from "./credential-rules.js";
8
12
  import { ConnectorCallError } from "./errors.js";
13
+ import { attachOAuthPartition, oauthPartitionIdle } from "./oauth-partition.js";
14
+ import { attachOAuthSealer, vaultOAuthSealer } from "./oauth-sealing.js";
9
15
  import { boundedStatus, failureRecord, failureStatus, logFailure, ownStatus } from "./operator-record.js";
10
- import { ConnectorCallAdmissionController, aggregateCallAdmissionSnapshots, } from "./call-admission.js";
11
- import { boundedCatalogDrift, catalogReviewOf, classifyCatalog, observeReviewedDrift, observedCatalogDrift, } from "./catalog-drift.js";
12
- import { MAX_CATALOG_TOOLS, MAX_SERIALIZED_CATALOG_BYTES } from "./catalog-limits.js";
13
16
  import { ObservedOutputSchemas } from "./result-shapes.js";
17
+ import { ContinuationBinding, OperationCredentials, ContinuationStorage, operationView, setViewService, duringViewEffect, } from "./runtime/call.js";
18
+ import { hasNativeStorage, registerNativeStorage } from "./runtime/native-storage.js";
19
+ import { provideRegistryPrograms, registryEdgeProgram, registryProgram } from "./runtime/registry.js";
20
+ import { causeError, runEdge } from "./runtime/run.js";
21
+ import { Storage } from "./runtime/services.js";
22
+ import { SharedRead } from "./runtime/shared-read.js";
23
+ import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageSet, storageList, } from "./runtime/storage.js";
14
24
  import { redactCatalog, sentSecretsFor, sentSecretsForRequest, trackCredentialReads } from "./sent-secrets.js";
15
25
  import { GUIDE_SUMMARY_LENGTH, normalizeGuideSummary } from "./skills.js";
16
- import { attachOAuthSealer, vaultOAuthSealer } from "./oauth-sealing.js";
17
- import { attachOAuthPartition, oauthPartitionIdle } from "./oauth-partition.js";
18
- import { attachCaller } from "./connector-caller.js";
26
+ import { jsonCodec, OAUTH_HANDOFF_TTL_SECONDS, oauthHandoffKeys, resultKeys, scopes, stashExpiryKeys, stashLedgerKeys, } from "./storage/keys.js";
27
+ import { assertStaticToolNames, hasControlCharacters } from "./tool-name.js";
28
+ import { carryToolClassification, classifyTool } from "./tool-safety.js";
19
29
  import { isWorkspaceAddress } from "./workspace-address.js";
20
30
  import { workspaceSubjectOf } from "./workspace-subject.js";
21
- import { runEdge } from "./runtime/run.js";
22
- import { SharedRead } from "./runtime/shared-read.js";
23
- import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageSet } from "./runtime/storage.js";
24
- import { jsonCodec, OAUTH_HANDOFF_TTL_SECONDS, oauthHandoffKeys, resultKeys, scopes, stashLedgerKeys, stashExpiryKeys, } from "./storage/keys.js";
25
- import { classifyTool } from "./tool-safety.js";
26
31
  const ID_RE = /^[a-z0-9_-]+$/;
27
32
  const DEFAULT_MAX_RESULT_BYTES = CONFIG_DEFAULTS.calls.maxResultBytes;
28
33
  const encoder = new TextEncoder();
@@ -74,7 +79,7 @@ export function isValidMaxResultBytes(value) {
74
79
  export function resolveMaxResultBytes(value, inherited) {
75
80
  return value !== undefined && isValidMaxResultBytes(value) ? value : inherited;
76
81
  }
77
- /** Freeze registry-owned facts so review digest memoization remains valid. */
82
+ /** Freeze registry-owned catalog facts before caching. */
78
83
  function frozenFacts(tools) {
79
84
  const pending = [tools];
80
85
  while (pending.length > 0) {
@@ -88,7 +93,7 @@ function frozenFacts(tools) {
88
93
  return tools;
89
94
  }
90
95
  function namespaced(storage, prefix) {
91
- return {
96
+ const edge = {
92
97
  capabilities: storage.capabilities,
93
98
  get: (k) => storage.get(prefix + k),
94
99
  set: (k, v, o) => storage.set(prefix + k, v, o),
@@ -96,6 +101,18 @@ function namespaced(storage, prefix) {
96
101
  list: async (keyPrefix) => (await storage.list(prefix + keyPrefix)).map((key) => key.slice(prefix.length)),
97
102
  compareAndSet: (k, expected, next, o) => storage.compareAndSet(prefix + k, expected, next, o),
98
103
  };
104
+ return hasNativeStorage(storage)
105
+ ? registerNativeStorage(edge, {
106
+ // Select against the underlying current methods at dispatch. A
107
+ // namespace created during a decoration regains the native path when
108
+ // that decoration is restored, without capturing stale capabilities.
109
+ get: (key) => storageGet(prefix + key).pipe(Effect.provideService(Storage, storage)),
110
+ set: (key, value, options) => storageSet(prefix + key, value, options).pipe(Effect.provideService(Storage, storage)),
111
+ delete: (key) => storageDelete(prefix + key).pipe(Effect.provideService(Storage, storage)),
112
+ list: (keyPrefix) => storageList(prefix + keyPrefix).pipe(Effect.provideService(Storage, storage), Effect.map((keys) => keys.map((key) => key.slice(prefix.length)))),
113
+ compareAndSet: (key, expected, next, options) => storageCompareAndSet(prefix + key, expected, next, options).pipe(Effect.provideService(Storage, storage)),
114
+ })
115
+ : edge;
99
116
  }
100
117
  const MAX_PERSONAL_REGISTRIES = 1_024;
101
118
  const MAX_ABSENT_GRANT_WARNINGS = 1_024;
@@ -231,6 +248,7 @@ export class Registry {
231
248
  catalogAccess = new Map();
232
249
  /** Bounded tokens isolating count-only observations by catalog identity. */
233
250
  catalogObservationPartitions = new Map();
251
+ overrideWarningCatalogs = new WeakMap();
234
252
  observedOutputSchemas;
235
253
  /** Result-size guard cap threaded to the meta-tools. */
236
254
  maxResultBytes;
@@ -271,11 +289,6 @@ export class Registry {
271
289
  `normalization; the discovery bound is ${GUIDE_SUMMARY_LENGTH}. ` +
272
290
  "Shorten it or omit it to derive one.");
273
291
  }
274
- // Validated here, before any request (INV-11), and read once per
275
- // connector object.
276
- if (catalogReviewOf(c) && c.staticTools) {
277
- throw new Error(`Connector "${c.id}" declares both staticTools and a classification; ` + "annotate static tools directly.");
278
- }
279
292
  this.connectors.set(c.id, c);
280
293
  if (c.callAdmission !== undefined) {
281
294
  this.callAdmission.set(c.id, new ConnectorCallAdmissionController(c.id, c.callAdmission));
@@ -288,8 +301,8 @@ export class Registry {
288
301
  Object.freeze(this.classification);
289
302
  for (const [id, overrides] of Object.entries(this.classification)) {
290
303
  for (const [name, verdict] of Object.entries(overrides)) {
291
- if (!name || (verdict !== "read" && verdict !== "write")) {
292
- throw new Error(`ConnectaConfig.classification.${id}.${name}: expected "read" or "write"`);
304
+ if (!name || (verdict !== "read" && verdict !== "write" && verdict !== "hidden")) {
305
+ throw new Error(`ConnectaConfig.classification.${id}.${name}: expected "read", "write", or "hidden"`);
293
306
  }
294
307
  }
295
308
  const connector = this.connectors.get(id);
@@ -326,6 +339,7 @@ export class Registry {
326
339
  ...this.opts,
327
340
  classification: Object.fromEntries(Object.entries(this.classification).filter(([id]) => this.connectors.get(id)?.authScope === "personal")),
328
341
  storage: namespaced(this.opts.storage, scopes.principal(principalKey)),
342
+ rootStorage: this.opts.rootStorage ?? this.opts.storage,
329
343
  credentialOwner: principalKey,
330
344
  catalogStorage: this.opts.catalogStorage ?? this.opts.storage,
331
345
  constructionChecks: false,
@@ -373,45 +387,14 @@ export class Registry {
373
387
  const quoted = JSON.stringify(key).replace(/[\u2028\u2029]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16)}`);
374
388
  this.opts.logger.warn(`a pool grants ${quoted} but connector "${connectorId}" lists no such tool; the grant is unreachable`);
375
389
  }
376
- async storeOAuthHandoff(connectorId, state, principalKey) {
377
- const key = oauthHandoffKeys.handoff(connectorId, await sha256Hex(state));
378
- for (let attempt = 0; attempt < 32; attempt++) {
379
- const existing = await this.opts.storage.get(key);
380
- if (existing && existing !== principalKey) {
381
- throw new Error(`Connector "${connectorId}" reused one OAuth state across principals`);
382
- }
383
- // Bind ownership atomically, including renewal of the same owner's
384
- // handoff. Two owners reading a miss must not overwrite each other.
385
- if (await this.opts.storage.compareAndSet(key, existing, principalKey, {
386
- ttlSeconds: OAUTH_HANDOFF_TTL_SECONDS,
387
- }))
388
- return;
389
- }
390
- throw new Error(`OAuth handoff for "${connectorId}" is busy; retry authorization`);
390
+ storeOAuthHandoff(connectorId, state, principalKey) {
391
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "storeOAuthHandoff", connectorId, state, principalKey));
391
392
  }
392
- async oauthCallbackView(connectorId, state) {
393
- if (!state)
394
- return null;
395
- const principalKey = await this.opts.storage.get(oauthHandoffKeys.handoff(connectorId, await sha256Hex(state)));
396
- const connector = this.connectors.get(connectorId);
397
- if (!connector || !principalKey)
398
- return null;
399
- return {
400
- registry: connector.authScope === "personal"
401
- ? this.scoped({
402
- connectorIds: [connectorId],
403
- subjectKey: principalKey,
404
- principalKey,
405
- })
406
- : this,
407
- principalKey,
408
- };
393
+ oauthCallbackView(connectorId, state) {
394
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "oauthCallbackView", connectorId, state));
409
395
  }
410
- async consumeOAuthHandoff(connectorId, state, principalKey) {
411
- if (!state)
412
- return false;
413
- const key = oauthHandoffKeys.handoff(connectorId, await sha256Hex(state));
414
- return this.opts.storage.compareAndSet(key, principalKey, null);
396
+ consumeOAuthHandoff(connectorId, state, principalKey) {
397
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "consumeOAuthHandoff", connectorId, state, principalKey));
415
398
  }
416
399
  /**
417
400
  * Refuse an unusable result cap at construction, like every other
@@ -465,18 +448,22 @@ export class Registry {
465
448
  return this.connectors.has(id);
466
449
  }
467
450
  contextFor(id, baseUrl, requestScope = {}, callOptions = {}, scope) {
468
- const credentialConfig = this.connectors.get(id)?.credential;
451
+ const connector = this.connectors.get(id);
452
+ const credentialConfig = connector?.credential;
469
453
  let credentialAccess;
454
+ let credentialProgram;
470
455
  if (this.opts.credentialVault && credentialConfig) {
471
456
  const vault = this.opts.credentialVault;
472
- const readValues = async () => {
473
- const values = await vault.getAll(id, this.opts.credentialOwner);
457
+ credentialProgram = Effect.tryPromise({
458
+ try: () => vault.getAll(id, this.opts.credentialOwner),
459
+ catch: (error) => error,
460
+ }).pipe(Effect.flatMap((values) => {
474
461
  const shape = storedCredentialShape(credentialConfig, values);
475
- if (shape.state === "mismatch") {
476
- throw new ConnectorCallError("auth_required", shape.message);
477
- }
478
- return values;
479
- };
462
+ return shape.state === "mismatch"
463
+ ? Effect.fail(new ConnectorCallError("auth_required", shape.message))
464
+ : Effect.succeed(values);
465
+ }));
466
+ const readValues = () => runEdge(credentialProgram);
480
467
  credentialAccess = {
481
468
  get: async (field = "value") => {
482
469
  const values = await readValues();
@@ -485,7 +472,7 @@ export class Registry {
485
472
  getAll: readValues,
486
473
  };
487
474
  }
488
- const context = {
475
+ const context = operationView({
489
476
  storage: namespaced(this.opts.storage, scopes.connector(id)),
490
477
  logger: this.opts.logger,
491
478
  baseUrl,
@@ -494,8 +481,16 @@ export class Registry {
494
481
  ...(credentialAccess ? { credential: credentialAccess } : {}),
495
482
  requestScope,
496
483
  ...callOptions,
497
- };
498
- attachCaller(context, scope?.caller);
484
+ }, scope?.caller);
485
+ setViewService(context, OperationCredentials, credentialProgram);
486
+ setViewService(context, ContinuationStorage, this.opts.rootStorage ?? this.opts.storage);
487
+ const owner = this.opts.credentialOwner ?? null;
488
+ setViewService(context, ContinuationBinding, {
489
+ connection: id,
490
+ // Include every admitted input a subject mapping can inspect, including
491
+ // the pool owner for an agent. This value enters only the HMAC claims.
492
+ principal: connector && usesCallerIdentity(connector) ? canonicalJson([owner, scope?.caller ?? null]) : owner,
493
+ });
499
494
  attachCatalogCache(context, {
500
495
  storage: this.opts.catalogStorage ?? this.opts.storage,
501
496
  partition: this.catalogPartition(scope),
@@ -564,17 +559,6 @@ export class Registry {
564
559
  }
565
560
  return partition;
566
561
  }
567
- catalogDriftOf(connector, baseUrl, scope) {
568
- const partition = this.catalogObservationPartition(connector.id, baseUrl, scope);
569
- const observed = partition ? observedCatalogDrift(connector, partition) : undefined;
570
- // Plugin reports cannot establish a reviewed baseline or schema coverage
571
- // that this registry has not checked. Personal plugin seams have no subject
572
- // parameter and must never bypass the partition above.
573
- const fallback = !observed && !catalogReviewOf(connector) && connector.authScope !== "personal"
574
- ? connector.catalogDrift?.()
575
- : undefined;
576
- return boundedCatalogDrift(observed ?? (fallback ? { ...fallback, reviewedBaseline: false } : undefined));
577
- }
578
562
  /** Reject queued/future downstream admission; active permits release safely. */
579
563
  closeCallAdmission() {
580
564
  this.callAdmissionClosed = true;
@@ -602,88 +586,7 @@ export class Registry {
602
586
  * stop waiting after 15 seconds. Storage TTL reclaims the rows.
603
587
  */
604
588
  stashResult(id, chunks, ttlSeconds, partition = scopes.results) {
605
- return runOnPartition(Effect.gen({ self: this }, function* () {
606
- const maxBytes = this.opts.results?.maxStashBytes ?? CONFIG_DEFAULTS.results.maxStashBytes;
607
- const maxEntries = this.opts.results?.maxStashEntries ?? CONFIG_DEFAULTS.results.maxStashEntries;
608
- // The paging envelope is ASCII, so its string length is its stored byte count.
609
- const bytes = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
610
- if (bytes > maxBytes || maxEntries === 0)
611
- return false;
612
- const keys = chunks.map((_, index) => partition + resultKeys.chunk(id, index));
613
- // IDs belong to reservations, not reusable result IDs or subjects.
614
- const charge = crypto.randomUUID();
615
- const ttlMs = ttlSeconds * 1000;
616
- if (!Number.isSafeInteger(ttlMs) || ttlMs <= 0)
617
- return false;
618
- const booking = yield* swapStashLedger((live, now) => {
619
- // A committed CAS with a lost response is already our booking, even
620
- // at full capacity. Reuse its deadline instead of adding a second slot.
621
- const own = live.find((entry) => entry[0] === charge);
622
- if (own)
623
- return { result: own[2] };
624
- const used = live.reduce((sum, entry) => sum + entry[1], 0);
625
- if (live.length >= maxEntries || used + bytes > maxBytes)
626
- return { result: undefined };
627
- const deadline = now + STASH_WRITE_TIMEOUT_MS + ttlMs;
628
- if (!Number.isSafeInteger(deadline))
629
- return { result: undefined };
630
- return { entries: [...live, [charge, bytes, deadline]], result: deadline };
631
- });
632
- if (booking.status === "exhausted" || booking.result === undefined)
633
- return false;
634
- const deadline = booking.result;
635
- const writeDeadline = deadline - ttlMs;
636
- const chunkExpiry = deadline - STASH_WRITE_TIMEOUT_MS;
637
- const owns = (entry) => entry[0] === charge;
638
- const settle = swapStashLedger((live) => live.some((entry) => owns(entry) && entry[2] > chunkExpiry)
639
- ? {
640
- entries: live.map((entry) => (owns(entry) ? [charge, bytes, Math.min(entry[2], chunkExpiry)] : entry)),
641
- result: undefined,
642
- }
643
- : { result: undefined });
644
- let pendingWrite = false;
645
- // A rejected or timed-out driver call may still commit after deletion.
646
- // Only a confirmed write response clears pendingWrite.
647
- const release = completeStash(Effect.gen(function* () {
648
- const deleted = yield* Effect.gen(function* () {
649
- for (const key of keys)
650
- yield* storageDelete(key);
651
- return true;
652
- }).pipe(Effect.catch(() => Effect.succeed(false)));
653
- if (pendingWrite)
654
- return;
655
- if (deleted) {
656
- const released = yield* swapStashLedger((live) => live.some(owns)
657
- ? { entries: live.filter((entry) => !owns(entry)), result: undefined }
658
- : { result: undefined });
659
- if (released.status === "applied")
660
- return;
661
- }
662
- yield* settle;
663
- }));
664
- // Trailing chunks first, then the header. The caller receives no result
665
- // ID after failure or timeout, including a late header commit.
666
- const remaining = Math.max(0, writeDeadline - (yield* Clock.currentTimeMillis));
667
- const written = yield* Effect.gen(function* () {
668
- for (let index = keys.length - 1; index >= 0; index--) {
669
- const before = yield* Clock.currentTimeMillis;
670
- if (before >= writeDeadline || before >= chunkExpiry)
671
- return false;
672
- pendingWrite = true;
673
- yield* storageSet(keys[index], chunks[index], { expiresAtMs: chunkExpiry }).pipe(Effect.tap(() => Effect.sync(() => {
674
- pendingWrite = false;
675
- })));
676
- if ((yield* Clock.currentTimeMillis) >= writeDeadline)
677
- return false;
678
- }
679
- return true;
680
- }).pipe(Effect.timeoutOrElse({ duration: Duration.millis(remaining), orElse: () => Effect.succeed(false) }), Effect.onError(() => release.pipe(Effect.ignore)));
681
- if (!written)
682
- yield* release;
683
- else
684
- yield* completeStash(settle);
685
- return written;
686
- }), this.opts);
589
+ return runOnPartition(registryEdgeProgram(Registry.prototype, this, "stashResult", id, chunks, ttlSeconds, partition), this.opts);
687
590
  }
688
591
  /**
689
592
  * Storage namespaced to the root result partition, kept separate from any
@@ -692,7 +595,7 @@ export class Registry {
692
595
  resultIdentity() {
693
596
  return { subject: null, principal: null, endpoint: "/mcp", origin: null };
694
597
  }
695
- recheckResultAccess(_address, _classification, _signal, _access) {
598
+ recheckResultAccess(_dependencies, _classification, _signal) {
696
599
  return Promise.resolve(true);
697
600
  }
698
601
  resultsStorage() {
@@ -701,11 +604,8 @@ export class Registry {
701
604
  credentialUiAvailable() {
702
605
  return Boolean(this.opts.credentialUi);
703
606
  }
704
- async bindOAuthHandoff(id, authorizationUrl, principalKey) {
705
- const state = new URL(authorizationUrl).searchParams.get("state");
706
- if (!state || !principalKey)
707
- throw new Error("OAuth requires state and an initiating user");
708
- await this.storeOAuthHandoff(id, state, principalKey);
607
+ bindOAuthHandoff(id, authorizationUrl, principalKey) {
608
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "bindOAuthHandoff", id, authorizationUrl, principalKey));
709
609
  }
710
610
  observedOutputSchema(connectorId, definition) {
711
611
  return this.observedOutputSchemas.get(connectorId, definition);
@@ -741,56 +641,43 @@ export class Registry {
741
641
  ...(connector.authScope === "personal" ? { personal: true } : {}),
742
642
  }, activityRequest(ctx.requestScope));
743
643
  }
744
- async loadDownstreamTools(id, baseUrl, requestScope, callOptions = {}, scope) {
745
- const connector = this.connectors.get(id);
746
- if (!connector)
747
- throw new Error(`Unknown connector "${id}"`);
748
- if (connector.staticTools)
749
- return { tools: markCatalogFreshness(connector.staticTools, Infinity), verification: undefined };
750
- const ctx = this.contextFor(id, baseUrl, requestScope, callOptions, scope);
751
- let listed;
752
- try {
753
- listed = await connector.listTools(ctx);
754
- }
755
- catch (error) {
756
- const fallback = error instanceof ConnectorCallError && error.code === "unavailable"
757
- ? await customCatalogFallback(ctx, id).catch(() => undefined)
758
- : undefined;
759
- if (!fallback)
760
- throw sentSecretsForRequest(ctx.requestScope ?? ctx).redact(error);
761
- listed = fallback;
762
- }
763
- const tools = redactCatalog(ctx, listed).map(({ classification: _ignored, ...fact }) => fact);
764
- if (tools.length > MAX_CATALOG_TOOLS ||
765
- encoder.encode(JSON.stringify(tools)).byteLength > MAX_SERIALIZED_CATALOG_BYTES) {
766
- throw new ConnectorCallError("connector_call_failed", "Downstream catalog exceeds the complete-catalog ceiling.", { retryable: false });
767
- }
768
- const facts = frozenFacts(structuredClone(tools));
769
- const accepted = this.acceptToolNames(facts);
770
- const review = catalogReviewOf(connector);
771
- const verification = await observeReviewedDrift(connector, review, accepted, this.opts.logger, this.catalogObservationPartition(id, baseUrl, scope, true), facts.length - accepted.length);
772
- if (catalogFetchedAt(ctx) === undefined) {
773
- try {
774
- await storeCustomCatalogFallback(ctx, id, facts);
775
- }
776
- catch (error) {
777
- logFailure(this.opts.logger, "catalog refresh observation failed", failureRecord({ connector: id }, error));
778
- }
779
- try {
780
- await observeUncachedCatalogRefresh(ctx, id, facts);
644
+ loadDownstreamTools(id, baseUrl, requestScope, callOptions = {}, scope) {
645
+ return Effect.gen({ self: this }, function* () {
646
+ const connector = this.connectors.get(id);
647
+ if (!connector)
648
+ throw new Error(`Unknown connector "${id}"`);
649
+ if (connector.staticTools)
650
+ return markCatalogFreshness(connector.staticTools, Infinity);
651
+ const ctx = this.contextFor(id, baseUrl, requestScope, callOptions, scope);
652
+ const listed = yield* authInvoke(connector, "listTools", [ctx]).pipe(Effect.catch((error) => Effect.gen(function* () {
653
+ const fallback = error instanceof ConnectorCallError && error.code === "unavailable"
654
+ ? yield* authCall(customCatalogFallback, [ctx, id]).pipe(Effect.catch(() => Effect.succeed(undefined)))
655
+ : undefined;
656
+ return fallback
657
+ ? fallback
658
+ : yield* Effect.fail(sentSecretsForRequest(ctx.requestScope ?? ctx).redact(error));
659
+ })));
660
+ const tools = redactCatalog(ctx, listed).map(({ classification: _ignored, ...fact }) => fact);
661
+ if (tools.length > MAX_CATALOG_TOOLS ||
662
+ encoder.encode(JSON.stringify(tools)).byteLength > MAX_SERIALIZED_CATALOG_BYTES) {
663
+ throw new ConnectorCallError("connector_call_failed", "Downstream catalog exceeds the complete-catalog ceiling.", { retryable: false });
781
664
  }
782
- catch (error) {
783
- logFailure(this.opts.logger, "catalog refresh observation failed", failureRecord({ connector: id }, error));
665
+ const facts = frozenFacts(structuredClone(tools));
666
+ const accepted = this.acceptToolNames(facts);
667
+ if (catalogFetchedAt(ctx) === undefined) {
668
+ for (const observe of [storeCustomCatalogFallback, observeUncachedCatalogRefresh]) {
669
+ yield* authCall(observe, [ctx, id, facts]).pipe(Effect.catch((error) => Effect.sync(() => logFailure(this.opts.logger, "catalog refresh observation failed", failureRecord({ connector: id }, error)))));
670
+ }
784
671
  }
785
- }
786
- this.catalogObservedAt.set(id, catalogFetchedAt(ctx) ?? Date.now());
787
- const provenance = catalogExpiry(ctx);
788
- const freshUntil = provenance && !provenance.staleFallback ? provenance.expiresAt : 0;
789
- this.catalogAccess.set(id, {
790
- state: freshUntil > Date.now() ? "fresh" : "stale",
791
- observedAt: new Date().toISOString(),
672
+ this.catalogObservedAt.set(id, catalogFetchedAt(ctx) ?? Date.now());
673
+ const provenance = catalogExpiry(ctx);
674
+ const freshUntil = provenance && !provenance.staleFallback ? provenance.expiresAt : 0;
675
+ this.catalogAccess.set(id, {
676
+ state: freshUntil > Date.now() ? "fresh" : "stale",
677
+ observedAt: new Date().toISOString(),
678
+ });
679
+ return markCatalogFreshness(accepted, freshUntil);
792
680
  });
793
- return { tools: markCatalogFreshness(accepted, freshUntil), verification };
794
681
  }
795
682
  /** The served catalog excludes control-character names; raw caches stay complete. */
796
683
  acceptToolNames(tools) {
@@ -799,30 +686,28 @@ export class Registry {
799
686
  }
800
687
  /**
801
688
  * One connector's catalog as served: the cached downstream facts, classified
802
- * on every read by the connector's `classification`, into fresh objects.
803
- * Safety is derived here and nowhere else, so no cache layer can carry a
804
- * stale verdict — from an older review, an older release, or a stale
805
- * fallback — and nothing a caller or decorator does to a served tool reaches
806
- * the next read.
689
+ * on every read by annotations and deployment overrides, into fresh objects.
690
+ * No cache or caller mutation can carry a stale verdict into the next read.
807
691
  */
808
- async loadTools(id, baseUrl, requestScope, callOptions = {}, scope) {
809
- const { tools, verification } = await this.loadDownstreamTools(id, baseUrl, requestScope, callOptions, scope);
810
- const connector = this.connectors.get(id);
811
- if (!connector)
812
- throw new Error(`Unknown connector "${id}"`);
813
- const accepted = this.acceptToolNames(tools);
814
- const overrides = this.classification[id];
815
- this.validateClassification(id, accepted, overrides);
816
- const review = catalogReviewOf(connector);
817
- return carryCatalogFreshness(tools, review
818
- ? await classifyCatalog(review, id, accepted, this.opts.logger, overrides, verification)
819
- : accepted.map((tool) => this.publishUnreviewedTool(id, tool)));
820
- }
821
- publishUnreviewedTool(id, tool) {
692
+ loadTools(id, baseUrl, requestScope, callOptions = {}, scope) {
693
+ return Effect.gen({ self: this }, function* () {
694
+ const tools = yield* this.loadDownstreamTools(id, baseUrl, requestScope, callOptions, scope);
695
+ const connector = this.connectors.get(id);
696
+ if (!connector)
697
+ throw new Error(`Unknown connector "${id}"`);
698
+ const accepted = this.acceptToolNames(tools);
699
+ const overrides = this.classification[id];
700
+ this.validateClassification(id, accepted, overrides, this.catalogObservationPartition(id, baseUrl, scope, true));
701
+ const visible = accepted.filter((tool) => overrides?.[tool.name] !== "hidden");
702
+ const served = visible.map((tool) => this.publishTool(id, tool));
703
+ return carryCatalogFreshness(tools, served);
704
+ });
705
+ }
706
+ publishTool(id, tool) {
822
707
  const override = this.classification[id]?.[tool.name];
823
- return {
708
+ return carryToolClassification(tool, {
824
709
  ...structuredClone(tool),
825
- classification: classifyTool(tool, override),
710
+ classification: classifyTool(tool, override === "hidden" ? undefined : override),
826
711
  ...(override !== undefined
827
712
  ? {
828
713
  annotations: {
@@ -832,65 +717,42 @@ export class Registry {
832
717
  },
833
718
  }
834
719
  : {}),
835
- };
720
+ });
836
721
  }
837
722
  /** Construction-only view for the secret-free static config description. */
838
723
  describeStaticTools(id) {
839
724
  const tools = this.connectors.get(id)?.staticTools;
840
- return tools?.map((tool) => this.publishUnreviewedTool(id, tool));
725
+ return tools
726
+ ?.filter((tool) => this.classification[id]?.[tool.name] !== "hidden")
727
+ .map((tool) => this.publishTool(id, tool));
841
728
  }
842
- validateClassification(id, tools, overrides) {
729
+ validateClassification(id, tools, overrides, partition) {
730
+ if (!overrides)
731
+ return;
732
+ if (partition) {
733
+ const catalog = JSON.stringify(tools);
734
+ if (this.overrideWarningCatalogs.get(partition) === catalog)
735
+ return;
736
+ this.overrideWarningCatalogs.set(partition, catalog);
737
+ }
843
738
  const known = new Set(tools.map((tool) => tool.name));
844
739
  for (const name of Object.keys(overrides ?? {})) {
845
- if (!known.has(name))
740
+ if (known.has(name))
741
+ continue;
742
+ if (this.connectors.get(id)?.staticTools) {
846
743
  throw new Error(`ConnectaConfig.classification: connector "${id}" has no tool "${name}"`);
744
+ }
745
+ // Only deployment-authored names, never downstream text. JSON quoting
746
+ // prevents control characters from forging operator log lines.
747
+ const quoted = JSON.stringify(`${id}.${name}`).replace(/[\u2028\u2029]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16)}`);
748
+ this.opts.logger.warn(`classification override ${quoted} is dormant because the runtime catalog lists no such tool`);
847
749
  }
848
750
  }
849
- async getTools(id, baseUrl, requestScope, callOptions = {}, scope) {
850
- const connector = this.connectors.get(id);
851
- if (!connector)
852
- throw new Error(`Unknown connector "${id}"`);
853
- if (!requestScope) {
854
- return this.loadTools(id, baseUrl, requestScope, callOptions, scope);
855
- }
856
- let loads = this.requestCatalogLoads.get(requestScope);
857
- if (!loads) {
858
- loads = new Map();
859
- this.requestCatalogLoads.set(requestScope, loads);
860
- }
861
- const requestLoads = loads;
862
- const key = JSON.stringify([id, scope?.principalKey, scope?.subjectKey, scope?.caller]);
863
- let load = requestLoads.get(key);
864
- if (!load) {
865
- const started = new SharedRead((signal) => this.loadTools(id, baseUrl, requestScope, { ...callOptions, signal }, scope),
866
- // Settled or cancelled, the read leaves the map before any caller
867
- // resumes, so the next one asks the caches afresh.
868
- () => {
869
- if (requestLoads.get(key) === started)
870
- requestLoads.delete(key);
871
- if (requestLoads.size === 0) {
872
- this.requestCatalogLoads.delete(requestScope);
873
- }
874
- });
875
- requestLoads.set(key, started);
876
- load = started;
877
- }
878
- return runEdge(load.join(callOptions.signal));
751
+ getTools(id, baseUrl, requestScope, callOptions = {}, scope) {
752
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "getTools", id, baseUrl, requestScope, callOptions, scope));
879
753
  }
880
- async credentialDriftFor(id) {
881
- const credential = this.connectors.get(id)?.credential;
882
- const vault = this.opts.credentialVault;
883
- if (!credential || !vault)
884
- return undefined;
885
- try {
886
- const values = await vault.getAll(id, this.opts.credentialOwner);
887
- const shape = storedCredentialShape(credential, values);
888
- return shape.state === "mismatch" ? shape.message : undefined;
889
- }
890
- catch (error) {
891
- logFailure(this.opts.logger, "credential shape read failed", failureRecord({ connector: id }, error));
892
- return undefined;
893
- }
754
+ credentialDriftFor(id) {
755
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "credentialDriftFor", id));
894
756
  }
895
757
  /** Age of a complete cache entry; reads never refresh it. */
896
758
  catalogAgeMs(id) {
@@ -898,76 +760,225 @@ export class Registry {
898
760
  return observedAt === undefined ? null : Math.max(0, Date.now() - observedAt);
899
761
  }
900
762
  /** Pure lookup shared by status probes and operator catalog refreshes. */
901
- withCatalogObservations(id, baseUrl, status, scope) {
763
+ withCatalogObservations(id, _baseUrl, status, _scope) {
902
764
  const connector = this.connectors.get(id);
903
765
  if (!connector)
904
766
  return ownStatus({ state: "error", message: "Unknown connector" });
905
- const report = this.catalogDriftOf(connector, baseUrl, scope);
906
767
  const access = this.catalogAccess.get(id);
907
768
  // Connector.status is an open plugin seam. Rebuild its public fields so
908
769
  // it cannot smuggle payload or another partition's observations (INV-6).
909
- return Object.assign(boundedStatus(status), {
910
- ...(report ? { catalogDrift: report } : {}),
911
- ...(access ? { catalogAccess: { ...access } } : {}),
912
- });
770
+ return Object.assign(boundedStatus(status), access ? { catalogAccess: { ...access } } : {});
913
771
  }
914
772
  /** Best-effort connector status for the operator UI. */
915
- async statusFor(id, baseUrl, requestScope = {}, callOptions = {}, scope) {
916
- const connector = this.connectors.get(id);
917
- if (!connector)
918
- return ownStatus({ state: "error", message: "Unknown connector" });
919
- const ctx = this.contextFor(id, baseUrl, requestScope, callOptions, scope);
920
- // Whatever the state turns out to be, it carries the drift the last
921
- // refresh saw. Reading it is a lookup, not a probe: a connector that has
922
- // listed nothing yet reports nothing, and status never lists on its own to
923
- // make the field appear.
924
- // The report is rebuilt rather than spread through: what the seam returned
925
- // is third-party output, and status is read by the operator UI and copied
926
- // into responses.
927
- const withObservations = (status) => this.withCatalogObservations(id, baseUrl, status, scope);
928
- // Credential declarations own their readiness even when a static catalog
929
- // or a plugin status would otherwise report success. Never probe an empty slot.
930
- if (connector.credential) {
931
- try {
932
- const values = (await this.opts.credentialVault?.getAll(id, this.opts.credentialOwner)) ?? null;
933
- const shape = storedCredentialShape(connector.credential, values);
934
- if (shape.state === "missing")
935
- return withObservations(ownStatus({ state: "credential_required" }));
936
- if (shape.state === "mismatch")
937
- return withObservations(ownStatus({ state: "auth_required" }));
938
- }
939
- catch (err) {
940
- return withObservations(failureStatus(id, err));
941
- }
942
- }
943
- if (connector.status) {
944
- try {
945
- return withObservations(await connector.status(ctx));
946
- }
947
- catch (err) {
948
- return withObservations(failureStatus(id, err));
949
- }
950
- }
951
- try {
952
- await this.getTools(id, baseUrl, requestScope, callOptions, scope);
953
- return withObservations({ state: "ok" });
954
- }
955
- catch (err) {
956
- return withObservations(failureStatus(id, err));
957
- }
773
+ statusFor(id, baseUrl, requestScope = {}, callOptions = {}, scope) {
774
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "statusFor", id, baseUrl, requestScope, callOptions, scope));
958
775
  }
959
776
  /** Rotate the storage generation; in-flight old readers cannot republish it. */
960
777
  invalidate(id) {
961
778
  void this.invalidateStored(id);
962
779
  }
963
- async invalidateStored(id) {
964
- this.catalogObservedAt.delete(id);
965
- try {
966
- await invalidateCatalogCache(this.opts.catalogStorage ?? this.opts.storage, id);
967
- }
968
- catch (error) {
969
- logFailure(this.opts.logger, "catalog invalidation failed", failureRecord({ connector: id }, error));
970
- }
780
+ invalidateStored(id) {
781
+ return runEdge(registryEdgeProgram(Registry.prototype, this, "invalidateStored", id));
782
+ }
783
+ static {
784
+ provideRegistryPrograms(Registry.prototype, {
785
+ stashResult: (self, id, chunks, ttlSeconds, partition = scopes.results) => Effect.gen({ self }, function* () {
786
+ const maxBytes = this.opts.results?.maxStashBytes ?? CONFIG_DEFAULTS.results.maxStashBytes;
787
+ const maxEntries = this.opts.results?.maxStashEntries ?? CONFIG_DEFAULTS.results.maxStashEntries;
788
+ // The paging envelope is ASCII, so its string length is its stored byte count.
789
+ const bytes = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
790
+ if (bytes > maxBytes || maxEntries === 0)
791
+ return false;
792
+ const keys = chunks.map((_, index) => partition + resultKeys.chunk(id, index));
793
+ // IDs belong to reservations, not reusable result IDs or subjects.
794
+ const charge = crypto.randomUUID();
795
+ const ttlMs = ttlSeconds * 1000;
796
+ if (!Number.isSafeInteger(ttlMs) || ttlMs <= 0)
797
+ return false;
798
+ const booking = yield* swapStashLedger((live, now) => {
799
+ // A committed CAS with a lost response is already our booking, even
800
+ // at full capacity. Reuse its deadline instead of adding a second slot.
801
+ const own = live.find((entry) => entry[0] === charge);
802
+ if (own)
803
+ return { result: own[2] };
804
+ const used = live.reduce((sum, entry) => sum + entry[1], 0);
805
+ if (live.length >= maxEntries || used + bytes > maxBytes)
806
+ return { result: undefined };
807
+ const deadline = now + STASH_WRITE_TIMEOUT_MS + ttlMs;
808
+ if (!Number.isSafeInteger(deadline))
809
+ return { result: undefined };
810
+ return { entries: [...live, [charge, bytes, deadline]], result: deadline };
811
+ });
812
+ if (booking.status === "exhausted" || booking.result === undefined)
813
+ return false;
814
+ const deadline = booking.result;
815
+ const writeDeadline = deadline - ttlMs;
816
+ const chunkExpiry = deadline - STASH_WRITE_TIMEOUT_MS;
817
+ const owns = (entry) => entry[0] === charge;
818
+ const settle = swapStashLedger((live) => live.some((entry) => owns(entry) && entry[2] > chunkExpiry)
819
+ ? {
820
+ entries: live.map((entry) => owns(entry) ? [charge, bytes, Math.min(entry[2], chunkExpiry)] : entry),
821
+ result: undefined,
822
+ }
823
+ : { result: undefined });
824
+ let pendingWrite = false;
825
+ // A rejected or timed-out driver call may still commit after deletion.
826
+ // Only a confirmed write response clears pendingWrite.
827
+ const release = completeStash(Effect.gen(function* () {
828
+ const deleted = yield* Effect.gen(function* () {
829
+ for (const key of keys)
830
+ yield* storageDelete(key);
831
+ return true;
832
+ }).pipe(Effect.catch(() => Effect.succeed(false)));
833
+ if (pendingWrite)
834
+ return;
835
+ if (deleted) {
836
+ const released = yield* swapStashLedger((live) => live.some(owns)
837
+ ? { entries: live.filter((entry) => !owns(entry)), result: undefined }
838
+ : { result: undefined });
839
+ if (released.status === "applied")
840
+ return;
841
+ }
842
+ yield* settle;
843
+ }));
844
+ // Trailing chunks first, then the header. The caller receives no result
845
+ // ID after failure or timeout, including a late header commit.
846
+ const remaining = Math.max(0, writeDeadline - (yield* Clock.currentTimeMillis));
847
+ const written = yield* Effect.gen(function* () {
848
+ for (let index = keys.length - 1; index >= 0; index--) {
849
+ const before = yield* Clock.currentTimeMillis;
850
+ if (before >= writeDeadline || before >= chunkExpiry)
851
+ return false;
852
+ pendingWrite = true;
853
+ yield* storageSet(keys[index], chunks[index], { expiresAtMs: chunkExpiry }).pipe(Effect.tap(() => Effect.sync(() => {
854
+ pendingWrite = false;
855
+ })));
856
+ if ((yield* Clock.currentTimeMillis) >= writeDeadline)
857
+ return false;
858
+ }
859
+ return true;
860
+ }).pipe(Effect.timeoutOrElse({ duration: Duration.millis(remaining), orElse: () => Effect.succeed(false) }), Effect.onError(() => release.pipe(Effect.ignore)));
861
+ if (!written)
862
+ yield* release;
863
+ else
864
+ yield* completeStash(settle);
865
+ return written;
866
+ }).pipe(Effect.provideService(Storage, self.opts.storage)),
867
+ recheckResultAccess: () => Effect.succeed(true),
868
+ storeOAuthHandoff: (self, connectorId, state, principalKey) => Effect.gen(function* () {
869
+ const key = oauthHandoffKeys.handoff(connectorId, yield* Effect.tryPromise({ try: () => sha256Hex(state), catch: (error) => error }));
870
+ for (let attempt = 0; attempt < 32; attempt++) {
871
+ const existing = yield* storageGet(key);
872
+ if (existing && existing !== principalKey)
873
+ throw new Error(`Connector "${connectorId}" reused one OAuth state across principals`);
874
+ if (yield* storageCompareAndSet(key, existing, principalKey, { ttlSeconds: OAUTH_HANDOFF_TTL_SECONDS }))
875
+ return;
876
+ }
877
+ throw new Error(`OAuth handoff for "${connectorId}" is busy; retry authorization`);
878
+ }).pipe(Effect.provideService(Storage, self.opts.storage)),
879
+ oauthCallbackView: (self, connectorId, state) => Effect.gen(function* () {
880
+ if (!state)
881
+ return null;
882
+ const digest = yield* Effect.tryPromise({ try: () => sha256Hex(state), catch: (error) => error });
883
+ const principalKey = yield* storageGet(oauthHandoffKeys.handoff(connectorId, digest));
884
+ const connector = self.connectors.get(connectorId);
885
+ if (!connector || !principalKey)
886
+ return null;
887
+ return {
888
+ registry: connector.authScope === "personal"
889
+ ? self.scoped({ connectorIds: [connectorId], subjectKey: principalKey, principalKey })
890
+ : self,
891
+ principalKey,
892
+ };
893
+ }).pipe(Effect.provideService(Storage, self.opts.storage)),
894
+ consumeOAuthHandoff: (self, connectorId, state, principalKey) => Effect.gen(function* () {
895
+ if (!state)
896
+ return false;
897
+ const digest = yield* Effect.tryPromise({ try: () => sha256Hex(state), catch: (error) => error });
898
+ return yield* storageCompareAndSet(oauthHandoffKeys.handoff(connectorId, digest), principalKey, null);
899
+ }).pipe(Effect.provideService(Storage, self.opts.storage)),
900
+ bindOAuthHandoff: (self, id, authorizationUrl, principalKey) => Effect.gen(function* () {
901
+ const state = new URL(authorizationUrl).searchParams.get("state");
902
+ if (!state || !principalKey)
903
+ throw new Error("OAuth requires state and an initiating user");
904
+ yield* registryProgram(self, "storeOAuthHandoff", id, state, principalKey);
905
+ }),
906
+ getTools: (self, id, baseUrl, requestScope, callOptions = {}, scope) => Effect.suspend(() => {
907
+ if (!self.connectors.has(id))
908
+ return Effect.fail(new Error(`Unknown connector "${id}"`));
909
+ const owner = self.contextFor(id, baseUrl, requestScope, callOptions, scope);
910
+ return duringViewEffect(owner, (signal) => {
911
+ if (!requestScope)
912
+ return self.loadTools(id, baseUrl, owner.requestScope, { ...callOptions, signal }, scope);
913
+ let loads = self.requestCatalogLoads.get(requestScope);
914
+ if (!loads) {
915
+ loads = new Map();
916
+ self.requestCatalogLoads.set(requestScope, loads);
917
+ }
918
+ const requestLoads = loads;
919
+ const key = JSON.stringify([id, scope?.principalKey, scope?.subjectKey, scope?.caller]);
920
+ let load = requestLoads.get(key);
921
+ if (!load) {
922
+ const started = new SharedRead((signal) => self.loadTools(id, baseUrl, requestScope, { ...callOptions, signal }, scope), () => {
923
+ if (requestLoads.get(key) === started)
924
+ requestLoads.delete(key);
925
+ if (requestLoads.size === 0)
926
+ self.requestCatalogLoads.delete(requestScope);
927
+ }, requestScope);
928
+ requestLoads.set(key, started);
929
+ load = started;
930
+ }
931
+ return load.join(signal);
932
+ });
933
+ }),
934
+ credentialDriftFor: (self, id) => Effect.gen(function* () {
935
+ const credential = self.connectors.get(id)?.credential;
936
+ const vault = self.opts.credentialVault;
937
+ if (!credential || !vault)
938
+ return undefined;
939
+ return yield* Effect.tryPromise({
940
+ try: () => vault.getAll(id, self.opts.credentialOwner),
941
+ catch: (error) => error,
942
+ }).pipe(Effect.map((values) => {
943
+ const shape = storedCredentialShape(credential, values);
944
+ return shape.state === "mismatch" ? shape.message : undefined;
945
+ }), Effect.catchCause((cause) => Effect.sync(() => {
946
+ logFailure(self.opts.logger, "credential shape read failed", failureRecord({ connector: id }, causeError(cause)));
947
+ return undefined;
948
+ })));
949
+ }),
950
+ statusFor: (self, id, baseUrl, requestScope = {}, callOptions = {}, scope) => Effect.suspend(() => {
951
+ const connector = self.connectors.get(id);
952
+ if (!connector)
953
+ return Effect.succeed(ownStatus({ state: "error", message: "Unknown connector" }));
954
+ const ctx = self.contextFor(id, baseUrl, requestScope, callOptions, scope);
955
+ const withObservations = (status) => self.withCatalogObservations(id, baseUrl, status, scope);
956
+ return duringViewEffect(ctx, () => Effect.gen(function* () {
957
+ if (connector.credential) {
958
+ const values = yield* Effect.tryPromise({
959
+ try: () => self.opts.credentialVault?.getAll(id, self.opts.credentialOwner) ?? Promise.resolve(null),
960
+ catch: (error) => error,
961
+ });
962
+ const shape = storedCredentialShape(connector.credential, values);
963
+ if (shape.state === "missing")
964
+ return withObservations(ownStatus({ state: "credential_required" }));
965
+ if (shape.state === "mismatch")
966
+ return withObservations(ownStatus({ state: "auth_required" }));
967
+ }
968
+ if (connector.status)
969
+ return withObservations(yield* authInvoke(connector, "status", [ctx]));
970
+ yield* registryProgram(self, "getTools", id, baseUrl, requestScope, callOptions, scope);
971
+ return withObservations({ state: "ok" });
972
+ })).pipe(Effect.catchCause((cause) => Effect.succeed(withObservations(failureStatus(id, causeError(cause, ctx.signal))))));
973
+ }),
974
+ invalidateStored: (self, id) => Effect.suspend(() => {
975
+ self.catalogObservedAt.delete(id);
976
+ return Effect.tryPromise({
977
+ try: () => invalidateCatalogCache(self.opts.catalogStorage ?? self.opts.storage, id),
978
+ catch: (error) => error,
979
+ }).pipe(Effect.catch((error) => Effect.sync(() => logFailure(self.opts.logger, "catalog invalidation failed", failureRecord({ connector: id }, error)))));
980
+ }),
981
+ });
971
982
  }
972
983
  }
973
984
  class ScopedRegistryView {
@@ -1033,27 +1044,8 @@ class ScopedRegistryView {
1033
1044
  const connector = this.getConnector(parsed.connectorId);
1034
1045
  return connector ? { connector, toolName: parsed.toolName } : null;
1035
1046
  }
1036
- async getTools(...args) {
1037
- const registry = this.registryFor(args[0]);
1038
- if (!registry) {
1039
- throw new Error(`Unknown connector "${args[0]}"`);
1040
- }
1041
- const tools = await registry.getTools(args[0], args[1], args[2], args[3], this.scope);
1042
- const granted = this.scope.toolAccess?.get(args[0]);
1043
- if (!granted)
1044
- return tools;
1045
- // Every consumer — search, describe, call_tool, and a program's
1046
- // connecta.call — resolves through this list, so an ungranted tool is
1047
- // indistinguishable from one the connector never had.
1048
- const visible = tools.filter((tool) => granted.has(tool.name));
1049
- if (visible.length < granted.size) {
1050
- const present = new Set(tools.map((tool) => tool.name));
1051
- for (const name of granted) {
1052
- if (!present.has(name))
1053
- this.root.noteAbsentGrant(args[0], name);
1054
- }
1055
- }
1056
- return carryCatalogFreshness(tools, visible);
1047
+ getTools(...args) {
1048
+ return runEdge(registryEdgeProgram(ScopedRegistryView.prototype, this, "getTools", ...args));
1057
1049
  }
1058
1050
  contextFor(...args) {
1059
1051
  const registry = this.registryFor(args[0]);
@@ -1079,15 +1071,14 @@ class ScopedRegistryView {
1079
1071
  origin: this.scope.origin ?? null,
1080
1072
  };
1081
1073
  }
1082
- recheckResultAccess(address, classification, signal, access) {
1083
- return this.scope.currentResultAccess?.(address, classification, signal, access) ?? Promise.resolve(true);
1074
+ recheckResultAccess(dependencies, classification, signal) {
1075
+ return this.scope.currentResultAccess?.(dependencies, classification, signal) ?? Promise.resolve(true);
1084
1076
  }
1085
1077
  resultsStorage() {
1086
1078
  return this.scope.subjectKey ? this.root.scopedStorage(this.scope.subjectKey) : this.root.resultsStorage();
1087
1079
  }
1088
1080
  credentialDriftFor(id) {
1089
- const registry = this.registryFor(id);
1090
- return registry ? registry.credentialDriftFor(id) : Promise.resolve(undefined);
1081
+ return runEdge(registryEdgeProgram(ScopedRegistryView.prototype, this, "credentialDriftFor", id));
1091
1082
  }
1092
1083
  observedOutputSchema(connectorId, definition) {
1093
1084
  return this.registryFor(connectorId)?.observedOutputSchema(connectorId, definition);
@@ -1099,23 +1090,65 @@ class ScopedRegistryView {
1099
1090
  return this.registryFor(id)?.catalogAgeMs(id) ?? null;
1100
1091
  }
1101
1092
  statusFor(...args) {
1102
- const registry = this.registryFor(args[0]);
1103
- return registry
1104
- ? registry.statusFor(args[0], args[1], args[2], args[3], this.scope)
1105
- : Promise.resolve({ state: "error", message: "Unknown connector" });
1093
+ return runEdge(registryEdgeProgram(ScopedRegistryView.prototype, this, "statusFor", ...args));
1106
1094
  }
1107
1095
  withCatalogObservations(id, baseUrl, status) {
1108
1096
  return (this.registryFor(id)?.withCatalogObservations(id, baseUrl, status, this.scope) ??
1109
1097
  ownStatus({ state: "error", message: "Unknown connector" }));
1110
1098
  }
1111
1099
  invalidateStored(id) {
1112
- const registry = this.registryFor(id);
1113
- return registry ? registry.invalidateStored(id) : Promise.resolve();
1100
+ return runEdge(registryEdgeProgram(ScopedRegistryView.prototype, this, "invalidateStored", id));
1114
1101
  }
1115
1102
  credentialUiAvailable() {
1116
1103
  return this.root.credentialUiAvailable();
1117
1104
  }
1118
1105
  async bindOAuthHandoff(id, authorizationUrl, principalKey) {
1119
- await this.root.bindOAuthHandoff(id, authorizationUrl, principalKey ?? this.scope.principalKey);
1106
+ return runEdge(registryEdgeProgram(ScopedRegistryView.prototype, this, "bindOAuthHandoff", id, authorizationUrl, principalKey));
1107
+ }
1108
+ static {
1109
+ provideRegistryPrograms(ScopedRegistryView.prototype, {
1110
+ stashResult: (self, id, chunks, ttlSeconds) => registryProgram(self.root, "stashResult", id, chunks, ttlSeconds, self.scope.subjectKey ? scopes.subject(self.scope.subjectKey) : scopes.results),
1111
+ recheckResultAccess: (self, dependencies, classification, signal) => Effect.tryPromise({
1112
+ try: () => self.scope.currentResultAccess?.(dependencies, classification, signal) ?? Promise.resolve(true),
1113
+ catch: (error) => error,
1114
+ }),
1115
+ getTools: (self, ...args) => Effect.gen(function* () {
1116
+ const registry = self.registryFor(args[0]);
1117
+ if (!registry) {
1118
+ throw new Error(`Unknown connector "${args[0]}"`);
1119
+ }
1120
+ const tools = yield* registryProgram(registry, "getTools", args[0], args[1], args[2], args[3], self.scope);
1121
+ const granted = self.scope.toolAccess?.get(args[0]);
1122
+ if (!granted)
1123
+ return tools;
1124
+ // Every consumer — search, describe, call_tool, and a program's
1125
+ // connecta.call — resolves through this list, so an ungranted tool is
1126
+ // indistinguishable from one the connector never had.
1127
+ const visible = tools.filter((tool) => granted.has(tool.name));
1128
+ if (visible.length < granted.size) {
1129
+ const present = new Set(tools.map((tool) => tool.name));
1130
+ for (const name of granted) {
1131
+ if (!present.has(name))
1132
+ self.root.noteAbsentGrant(args[0], name);
1133
+ }
1134
+ }
1135
+ return carryCatalogFreshness(tools, visible);
1136
+ }),
1137
+ credentialDriftFor: (self, id) => Effect.suspend(() => {
1138
+ const registry = self.registryFor(id);
1139
+ return registry ? registryProgram(registry, "credentialDriftFor", id) : Effect.succeed(undefined);
1140
+ }),
1141
+ statusFor: (self, ...args) => Effect.suspend(() => {
1142
+ const registry = self.registryFor(args[0]);
1143
+ return registry
1144
+ ? registryProgram(registry, "statusFor", args[0], args[1], args[2], args[3], self.scope)
1145
+ : Effect.succeed({ state: "error", message: "Unknown connector" });
1146
+ }),
1147
+ invalidateStored: (self, id) => Effect.suspend(() => {
1148
+ const registry = self.registryFor(id);
1149
+ return registry ? registryProgram(registry, "invalidateStored", id) : Effect.succeed(undefined);
1150
+ }),
1151
+ bindOAuthHandoff: (self, id, authorizationUrl, principalKey) => registryProgram(self.root, "bindOAuthHandoff", id, authorizationUrl, principalKey ?? self.scope.principalKey),
1152
+ });
1120
1153
  }
1121
1154
  }