@zackbart/connecta 0.28.1 → 0.30.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 (332) hide show
  1. package/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
package/bin/connecta.mjs CHANGED
@@ -1,18 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import {
4
- copyFile,
5
- cp,
6
- lstat,
7
- mkdtemp,
8
- readFile,
9
- rename,
10
- rm,
11
- symlink,
12
- writeFile,
13
- } from "node:fs/promises";
3
+ import { copyFile, cp, lstat, mkdtemp, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
4
+ import { CONNECTA_VERSION } from "./version.mjs";
5
+ import { META_TOOL_NAMES } from "./meta-tool-names.mjs";
14
6
  import { basename, dirname, join, resolve } from "node:path";
15
- import { fileURLToPath } from "node:url";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
8
 
17
9
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
18
10
  const [command, ...args] = process.argv.slice(2);
@@ -27,7 +19,8 @@ function shellCd(path) {
27
19
  function usage() {
28
20
  console.log(`Usage:
29
21
  connecta init [directory]
30
- CONNECTA_TOKEN=<bearer> connecta doctor [--url http://localhost:8787]
22
+ connecta migrate-state <state.json> <connecta.sqlite>
23
+ CONNECTA_TOKEN=<bearer> connecta doctor [--config] [--url http://localhost:8787]
31
24
  CF_ACCESS_CLIENT_ID=<id> CF_ACCESS_CLIENT_SECRET=<secret> connecta doctor --url https://worker.example`);
32
25
  }
33
26
 
@@ -44,18 +37,13 @@ async function init() {
44
37
  await lstat(target);
45
38
  throw new Error(`Refusing to overwrite existing path: ${target}`);
46
39
  } catch (error) {
47
- const code =
48
- error && typeof error === "object" && "code" in error
49
- ? String(error.code)
50
- : "";
40
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
51
41
  if (code !== "ENOENT") throw error;
52
42
  }
53
43
 
54
44
  // Build beside the destination, then rename once complete. A failed copy or
55
45
  // rewrite leaves no partial destination that blocks a clean retry.
56
- let stage = await mkdtemp(
57
- join(parent, `.${basename(target)}.connecta-init-`),
58
- );
46
+ let stage = await mkdtemp(join(parent, `.${basename(target)}.connecta-init-`));
59
47
  try {
60
48
  await cp(join(packageRoot, "templates", "node"), stage, {
61
49
  recursive: true,
@@ -63,10 +51,7 @@ async function init() {
63
51
 
64
52
  // npm excludes .gitignore files and symlinks from packed dependencies.
65
53
  // Restore both conventions explicitly in the generated project.
66
- await writeFile(
67
- join(stage, ".gitignore"),
68
- ".connecta-state.json\n.connecta-activity.jsonl\n.env\nnode_modules/\n",
69
- );
54
+ await writeFile(join(stage, ".gitignore"), ".connecta.sqlite*\n.env\nnode_modules/\n");
70
55
  await rm(join(stage, "CLAUDE.md"), { force: true });
71
56
  try {
72
57
  await symlink("AGENTS.md", join(stage, "CLAUDE.md"));
@@ -76,18 +61,11 @@ async function init() {
76
61
  await copyFile(join(stage, "AGENTS.md"), join(stage, "CLAUDE.md"));
77
62
  }
78
63
 
79
- const rootPackage = JSON.parse(
80
- await readFile(join(packageRoot, "package.json"), "utf8"),
81
- );
64
+ const rootPackage = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
82
65
  const deploymentPath = join(stage, "package.json");
83
- const deploymentPackage = JSON.parse(
84
- await readFile(deploymentPath, "utf8"),
85
- );
66
+ const deploymentPackage = JSON.parse(await readFile(deploymentPath, "utf8"));
86
67
  deploymentPackage.dependencies["@zackbart/connecta"] = rootPackage.version;
87
- await writeFile(
88
- deploymentPath,
89
- JSON.stringify(deploymentPackage, null, 2) + "\n",
90
- );
68
+ await writeFile(deploymentPath, JSON.stringify(deploymentPackage, null, 2) + "\n");
91
69
 
92
70
  await rename(stage, target);
93
71
  stage = "";
@@ -99,7 +77,10 @@ async function init() {
99
77
  console.log("Next:");
100
78
  console.log(` ${shellCd(target)}`);
101
79
  console.log(" npm install");
102
- console.log(" CONNECTA_TOKEN=dev-token npm start");
80
+ console.log(' npm run --silent provision-token -- "local-machine"');
81
+ console.log(" # Save the returned cta_ token privately for your MCP client and doctor.");
82
+ console.log(" npm start");
83
+ console.log(" # README.md covers database paths and Docker provisioning.");
103
84
  }
104
85
 
105
86
  function option(name, fallback) {
@@ -135,7 +116,7 @@ async function doctorFetch(url, init = {}) {
135
116
  const response = await fetch(url, {
136
117
  ...init,
137
118
  redirect: "manual",
138
- signal: AbortSignal.timeout(DOCTOR_TIMEOUT_MS),
119
+ signal: AbortSignal.any([AbortSignal.timeout(DOCTOR_TIMEOUT_MS), ...(init.signal ? [init.signal] : [])]),
139
120
  });
140
121
  if ([301, 302, 303, 307, 308].includes(response.status)) {
141
122
  await response.body?.cancel().catch(() => {});
@@ -145,10 +126,7 @@ async function doctorFetch(url, init = {}) {
145
126
  }
146
127
  return response;
147
128
  } catch (error) {
148
- if (
149
- error instanceof Error &&
150
- (error.name === "TimeoutError" || error.name === "AbortError")
151
- ) {
129
+ if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
152
130
  throw new Error(`Timed out after ${DOCTOR_TIMEOUT_MS}ms contacting ${url}`);
153
131
  }
154
132
  throw error;
@@ -156,9 +134,9 @@ async function doctorFetch(url, init = {}) {
156
134
  }
157
135
 
158
136
  async function doctor() {
159
- const known = new Set(["--url"]);
160
- for (let index = 0; index < args.length; index += 2) {
161
- if (!known.has(args[index]) || !args[index + 1]) {
137
+ for (let index = 0; index < args.length; index++) {
138
+ if (args[index] === "--config") continue;
139
+ if (args[index] !== "--url" || !args[++index] || args[index].startsWith("--")) {
162
140
  usage();
163
141
  process.exitCode = 1;
164
142
  return;
@@ -173,22 +151,15 @@ async function doctor() {
173
151
  throw new Error("Doctor URL must use http or https.");
174
152
  }
175
153
  const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
176
- if (
177
- parsedUrl.protocol === "http:" &&
178
- !loopbackHosts.has(parsedUrl.hostname)
179
- ) {
180
- throw new Error(
181
- "Refusing to send authentication credentials over remote plaintext HTTP. Use HTTPS.",
182
- );
154
+ if (parsedUrl.protocol === "http:" && !loopbackHosts.has(parsedUrl.hostname)) {
155
+ throw new Error("Refusing to send authentication credentials over remote plaintext HTTP. Use HTTPS.");
183
156
  }
184
157
  const baseUrl = requestedUrl.replace(/\/+$/, "");
185
158
  const token = process.env.CONNECTA_TOKEN;
186
159
  const accessClientId = process.env.CF_ACCESS_CLIENT_ID;
187
160
  const accessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET;
188
161
  if (Boolean(accessClientId) !== Boolean(accessClientSecret)) {
189
- throw new Error(
190
- "Set both CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET.",
191
- );
162
+ throw new Error("Set both CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET.");
192
163
  }
193
164
  if (!token && !accessClientId) {
194
165
  throw new Error(
@@ -205,9 +176,28 @@ async function doctor() {
205
176
  : {}),
206
177
  };
207
178
 
208
- const health = await jsonResponse(
209
- await doctorFetch(`${baseUrl}/health`, { headers: authHeaders }),
210
- );
179
+ if (args.includes("--config")) {
180
+ const response = await doctorFetch(`${baseUrl}/ui/api/config`, { headers: authHeaders });
181
+ if (!response.ok) {
182
+ await response.body?.cancel().catch(() => {});
183
+ throw new Error(`HTTP ${response.status}: config snapshot unavailable.`);
184
+ }
185
+ let contract;
186
+ try {
187
+ contract = await response.json();
188
+ } catch {
189
+ throw new Error("Deployment returned an invalid config contract.");
190
+ }
191
+ if (contract?.schemaVersion !== 1 || contract.config?.schemaVersion !== 1) {
192
+ throw new Error("Deployment returned an unsupported config contract.");
193
+ }
194
+ // The server builds this with describeConfig's allowlist; do not print
195
+ // the overlay, identity, health deploymentInfo, or a raw error body.
196
+ console.log(JSON.stringify(contract.config, null, 2));
197
+ return;
198
+ }
199
+
200
+ const health = await jsonResponse(await doctorFetch(`${baseUrl}/health`, { headers: authHeaders }));
211
201
  if (health.status !== "ok") {
212
202
  throw new Error(`Unexpected health status: ${String(health.status)}`);
213
203
  }
@@ -225,107 +215,99 @@ async function doctor() {
225
215
  .slice(0, 40)
226
216
  .trim()
227
217
  : "";
228
- let requestId = 0;
229
- const mcp = async (method, params) =>
230
- jsonResponse(
231
- await doctorFetch(`${baseUrl}/mcp`, {
232
- method: "POST",
233
- headers: {
234
- ...authHeaders,
235
- "Content-Type": "application/json",
236
- Accept: "application/json, text/event-stream",
237
- },
238
- body: JSON.stringify({
239
- jsonrpc: "2.0",
240
- id: ++requestId,
241
- method,
242
- params,
243
- }),
244
- }),
245
- );
246
-
247
- const listed = await mcp("tools/list", {});
248
- if (listed.error) {
249
- throw new Error(`tools/list failed: ${JSON.stringify(listed.error)}`);
250
- }
251
- const actual = listed.result?.tools
252
- ?.map((tool) => tool.name)
253
- .sort();
254
- const expected = [
255
- "authorize_connector",
256
- "call_destructive_tool",
257
- "call_tool",
258
- "execute_code",
259
- "get_result",
260
- "search_tools",
261
- "skills",
262
- ];
263
- if (JSON.stringify(actual) !== JSON.stringify(expected)) {
264
- throw new Error(
265
- `Unexpected MCP surface. Expected ${expected.join(", ")}; received ` +
266
- `${Array.isArray(actual) ? actual.join(", ") : "no tool list"}.`,
267
- );
268
- }
269
-
270
- const executed = await mcp("tools/call", {
271
- name: "execute_code",
272
- arguments: { code: "async () => 42" },
218
+ const { Client, StreamableHTTPClientTransport } = await import("@modelcontextprotocol/client");
219
+ const client = new Client(
220
+ { name: "connecta-doctor", version: CONNECTA_VERSION },
221
+ { versionNegotiation: { mode: "auto", probe: { timeoutMs: DOCTOR_TIMEOUT_MS } } },
222
+ );
223
+ const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {
224
+ requestInit: { headers: authHeaders },
225
+ fetch: doctorFetch,
273
226
  });
274
- if (executed.error) {
275
- throw new Error(`execute_code failed: ${JSON.stringify(executed.error)}`);
276
- }
277
- const executionResult =
278
- executed.result?.structuredContent ??
279
- JSON.parse(executed.result?.content?.[0]?.text ?? "null");
280
- if (executed.result?.isError || executionResult?.result !== 42) {
281
- throw new Error(
282
- `${executorName ? `${executorName} execution` : "execute_code"} check ` +
283
- `failed: ${JSON.stringify(executed.result)}`,
284
- );
285
- }
227
+ let negotiatedVersion;
228
+ try {
229
+ await client.connect(transport);
230
+ negotiatedVersion = client.getNegotiatedProtocolVersion();
231
+ const listed = await client.listTools({}, { timeout: DOCTOR_TIMEOUT_MS });
232
+ const actual = listed.tools.map((tool) => tool.name).sort();
233
+ const expected = [...META_TOOL_NAMES].sort();
234
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
235
+ throw new Error(
236
+ `Unexpected MCP surface. Expected ${expected.join(", ")}; received ` +
237
+ `${Array.isArray(actual) ? actual.join(", ") : "no tool list"}.`,
238
+ );
239
+ }
286
240
 
287
- // Drift is reported, never failed on. A downstream that grew a tool nobody
288
- // has classified is a maintainer's next task, not a broken deployment: the
289
- // unclassified tool already fails closed onto call_destructive_tool. These
290
- // counts come from refreshes the deployment already served, so a deployment
291
- // that has answered no catalog request yet reports nothing here (#343).
292
- const drifted = Object.entries(health.catalogDrift ?? {}).filter(
293
- ([, report]) =>
294
- report.unclassifiedTools ||
295
- report.unservedTools ||
296
- report.annotationConflicts ||
297
- report.schemaChanges,
298
- );
299
- for (const [connectorId, report] of drifted) {
300
- console.warn(
301
- `[connecta] catalog drift on "${connectorId}" (observed ` +
302
- `${report.observedAt}): ${report.unclassifiedTools} unclassified, ` +
303
- `${report.unservedTools} no longer served, ` +
304
- `${report.annotationConflicts} annotation conflict(s), ` +
305
- `${report.schemaChanges} schema change(s).`,
241
+ const executed = await client.callTool(
242
+ {
243
+ name: "execute_code",
244
+ arguments: { code: "async () => 42" },
245
+ },
246
+ undefined,
247
+ { timeout: DOCTOR_TIMEOUT_MS },
306
248
  );
249
+ const executionResult = executed.structuredContent ?? JSON.parse(executed.content?.[0]?.text ?? "null");
250
+ if (executed.isError || executionResult?.result !== 42) {
251
+ throw new Error(
252
+ `${executorName ? `${executorName} execution` : "execute_code"} check ` + `failed: ${JSON.stringify(executed)}`,
253
+ );
254
+ }
255
+ } finally {
256
+ await client.close();
307
257
  }
308
258
 
309
259
  console.log(
310
260
  `Connecta doctor passed: ${health.connectors} connector(s), ` +
311
261
  `${executorName ? `${executorName} executed` : "code executed"}, ` +
312
- "prescribed seven-tool surface" +
313
- (drifted.length > 0
314
- ? `, catalog drift on ${drifted.length} connector(s).`
315
- : "."),
262
+ `prescribed ${META_TOOL_NAMES.length}-tool surface, MCP ${negotiatedVersion}, package ${CONNECTA_VERSION}.`,
316
263
  );
317
264
  }
318
265
 
266
+ /**
267
+ * One-shot copy of a 0.28 `fileStorage` JSON state file into the SQLite
268
+ * database 0.29 stores everything in. Stop the old deployment first; the file
269
+ * is read, never changed.
270
+ */
271
+ async function migrateState() {
272
+ if (args.length !== 2 || args.some((arg) => arg.startsWith("--"))) {
273
+ usage();
274
+ process.exitCode = 1;
275
+ return;
276
+ }
277
+ const [statePath, databasePath] = args.map((arg) => resolve(process.cwd(), arg));
278
+ const { importStateFile, openSqlite } = await import(pathToFileURL(join(packageRoot, "dist", "sqlite.js")).href);
279
+ let database;
280
+ try {
281
+ database = openSqlite(databasePath);
282
+ } catch {
283
+ throw new Error("State migration failed: could not open the SQLite database");
284
+ }
285
+ let closeFailed = false;
286
+ try {
287
+ const result = importStateFile(database, statePath);
288
+ console.log(
289
+ `Imported ${result.imported} entries from ${statePath} into ${databasePath}` +
290
+ ` (${result.kept} already present, ${result.expired} expired).`,
291
+ );
292
+ } finally {
293
+ try {
294
+ database.close();
295
+ } catch {
296
+ closeFailed = true;
297
+ }
298
+ }
299
+ if (closeFailed) throw new Error("State migration failed: could not close the SQLite database");
300
+ }
301
+
319
302
  try {
320
303
  if (command === "init") await init();
321
304
  else if (command === "doctor") await doctor();
305
+ else if (command === "migrate-state") await migrateState();
322
306
  else {
323
307
  usage();
324
308
  process.exitCode = command === "--help" || command === "-h" ? 0 : 1;
325
309
  }
326
310
  } catch (error) {
327
- console.error(
328
- `[connecta] ${error instanceof Error ? error.message : String(error)}`,
329
- );
311
+ console.error(`[connecta] ${error instanceof Error ? error.message : String(error)}`);
330
312
  process.exitCode = 1;
331
313
  }
@@ -0,0 +1,2 @@
1
+ /** The complete upward meta-tool set, shared by discovery checks and doctor. */
2
+ export declare const META_TOOL_NAMES: readonly string[];
@@ -0,0 +1,9 @@
1
+ /** The complete upward meta-tool set, shared by discovery checks and doctor. */
2
+ export const META_TOOL_NAMES = Object.freeze([
3
+ "authorize_connector",
4
+ "call_destructive_tool",
5
+ "call_tool",
6
+ "execute_code",
7
+ "search_tools",
8
+ "skills",
9
+ ]);
@@ -0,0 +1,3 @@
1
+ // Generated from package.json by scripts/generate-version.mjs.
2
+ // Workers-safe: no runtime filesystem read or deployment override.
3
+ export const CONNECTA_VERSION = "0.30.0";
@@ -1,35 +1,27 @@
1
+ import { authorizationCredential, isMachineCredential } from "./inbound-credential.js";
2
+ import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
1
3
  import { routeAccessTokens } from "./routes/access-tokens.js";
2
4
  import { validIdentityReference } from "./identity.js";
5
+ import { accessTokenKeys } from "./storage/keys.js";
3
6
  const TOKEN_PREFIX = "cta_";
4
7
  const TOKEN_BYTES = 32;
5
8
  const TOKEN_VALUE_RE = /^cta_[A-Za-z0-9_-]{43}$/;
6
- const RECORD_PREFIX = "access-token:v1:record:";
7
- const LOOKUP_PREFIX = "access-token:v1:lookup:";
8
9
  const MAX_NAME_CHARACTERS = 80;
9
10
  const DEFAULT_MAX_ACTIVE = 100;
10
11
  const MAX_CONFIGURED_ACTIVE = 1_000;
11
- const ACTIVE_KEY = "access-token:v1:active";
12
12
  const ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
13
13
  const encoder = new TextEncoder();
14
- function recordKey(id) {
15
- return `${RECORD_PREFIX}${id}`;
16
- }
17
- function lookupKey(hash) {
18
- return `${LOOKUP_PREFIX}${hash}`;
19
- }
14
+ const recordKey = accessTokenKeys.record;
15
+ const lookupKey = accessTokenKeys.lookup;
16
+ const ACTIVE_KEY = accessTokenKeys.active;
20
17
  function bytesToBase64Url(bytes) {
21
18
  let binary = "";
22
19
  for (const byte of bytes)
23
20
  binary += String.fromCharCode(byte);
24
- return btoa(binary)
25
- .replaceAll("+", "-")
26
- .replaceAll("/", "_")
27
- .replace(/=+$/u, "");
21
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
28
22
  }
29
23
  function bytesToHex(bytes) {
30
- return [...bytes]
31
- .map((byte) => byte.toString(16).padStart(2, "0"))
32
- .join("");
24
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
33
25
  }
34
26
  async function hashToken(token) {
35
27
  return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token))));
@@ -62,12 +54,10 @@ function parseRecord(raw) {
62
54
  typeof value.createdAt !== "string" ||
63
55
  !Number.isFinite(Date.parse(value.createdAt)) ||
64
56
  typeof value.createdBy !== "string" ||
65
- (value.principal !== undefined &&
66
- !validIdentityReference(value.principal)) ||
57
+ (value.principal !== undefined && !validIdentityReference(value.principal)) ||
67
58
  (value.revokedAt !== undefined &&
68
59
  (typeof value.revokedAt !== "string" || !value.revokedAt || !Number.isFinite(Date.parse(value.revokedAt)))) ||
69
- (value.revokedBy !== undefined &&
70
- typeof value.revokedBy !== "string")) {
60
+ (value.revokedBy !== undefined && typeof value.revokedBy !== "string")) {
71
61
  throw new Error("invalid token record");
72
62
  }
73
63
  return value;
@@ -119,18 +109,20 @@ export class AccessTokenManager {
119
109
  maxActive;
120
110
  constructor(storage, options = {}) {
121
111
  this.storage = storage;
122
- if (!storage.list) {
123
- throw new Error("accessTokens requires a storage adapter that implements list(prefix)");
112
+ // Issuance claims capacity by compare-and-set and listing enumerates
113
+ // records; refuse a store without either here, not at the first create.
114
+ if (typeof storage?.list !== "function" || typeof storage.compareAndSet !== "function") {
115
+ throw new Error("accessTokens requires storage that implements list and compareAndSet " +
116
+ "(d1Storage, sqliteStorage, or memoryStorage)");
124
117
  }
125
118
  const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
126
- if (!Number.isInteger(maxActive) ||
127
- maxActive < 1 ||
128
- maxActive > MAX_CONFIGURED_ACTIVE) {
119
+ if (!Number.isInteger(maxActive) || maxActive < 1 || maxActive > MAX_CONFIGURED_ACTIVE) {
129
120
  throw new Error(`accessTokens.maxActive must be a whole number from 1 to ${MAX_CONFIGURED_ACTIVE}`);
130
121
  }
131
122
  this.maxActive = maxActive;
132
123
  this.auth = {
133
124
  kind: "access_token",
125
+ recognizesCredential: isMachineCredential,
134
126
  activityActorNamespace: "connecta:access-tokens:v1",
135
127
  activityActorLabel: async (id) => {
136
128
  try {
@@ -153,7 +145,7 @@ export class AccessTokenManager {
153
145
  return record;
154
146
  }
155
147
  async list() {
156
- const keys = await this.storage.list(RECORD_PREFIX);
148
+ const keys = await this.storage.list(accessTokenKeys.recordPrefix);
157
149
  const records = await Promise.all(keys.map(async (key) => {
158
150
  const raw = await this.storage.get(key);
159
151
  return raw ? parseRecord(raw) : null;
@@ -165,9 +157,6 @@ export class AccessTokenManager {
165
157
  }
166
158
  async create(name, createdBy) {
167
159
  const normalizedName = normalizeName(name);
168
- if (!this.storage.compareAndSet) {
169
- throw new Error("Creating access tokens requires atomic compareAndSet storage");
170
- }
171
160
  if (typeof createdBy !== "string" && !validIdentityReference(createdBy)) {
172
161
  throw new Error("Invalid token owner");
173
162
  }
@@ -184,12 +173,8 @@ export class AccessTokenManager {
184
173
  tokenHash: hash,
185
174
  tokenPrefix: token.slice(0, 12),
186
175
  createdAt: new Date().toISOString(),
187
- createdBy: typeof createdBy === "string"
188
- ? createdBy
189
- : `${createdBy.namespace}:${createdBy.id}`,
190
- ...(typeof createdBy === "string"
191
- ? {}
192
- : { principal: { ...createdBy } }),
176
+ createdBy: typeof createdBy === "string" ? createdBy : `${createdBy.namespace}:${createdBy.id}`,
177
+ ...(typeof createdBy === "string" ? {} : { principal: { ...createdBy } }),
193
178
  };
194
179
  try {
195
180
  await this.updateActive(record.id, true);
@@ -211,7 +196,7 @@ export class AccessTokenManager {
211
196
  }
212
197
  async rename(id, name) {
213
198
  const normalized = normalizeName(name);
214
- const record = await this.updateRecord(id, current => ({ ...current, name: normalized }));
199
+ const record = await this.updateRecord(id, (current) => ({ ...current, name: normalized }));
215
200
  return record ? metadata(record) : null;
216
201
  }
217
202
  async revoke(id, revokedBy) {
@@ -221,11 +206,14 @@ export class AccessTokenManager {
221
206
  // Remove admission first. A metadata failure cannot leave a successful
222
207
  // revocation's lookup alive. Retry deletion even if already marked revoked.
223
208
  await this.storage.delete(lookupKey(record.tokenHash));
224
- const updated = await this.updateRecord(id, current => current.revokedAt ? current : {
225
- ...current, revokedAt: new Date().toISOString(), revokedBy,
226
- });
227
- if (this.storage.compareAndSet)
228
- await this.updateActive(id, false);
209
+ const updated = await this.updateRecord(id, (current) => current.revokedAt
210
+ ? current
211
+ : {
212
+ ...current,
213
+ revokedAt: new Date().toISOString(),
214
+ revokedBy,
215
+ });
216
+ await this.updateActive(id, false);
229
217
  return updated ? metadata(updated) : null;
230
218
  }
231
219
  async updateRecord(id, update) {
@@ -239,45 +227,36 @@ export class AccessTokenManager {
239
227
  if (record.id !== id)
240
228
  throw new Error("Stored access token id mismatch");
241
229
  const next = update(record);
242
- if (this.storage.compareAndSet) {
243
- // A rename racing revocation must retain revokedAt, including when
244
- // an in-flight create has not yet published its lookup.
245
- if (!await this.storage.compareAndSet(recordKey(id), raw, JSON.stringify(next)))
246
- continue;
247
- }
248
- else {
249
- // Legacy adapters cannot create, so no late lookup writer exists.
250
- // Revocation's deleted lookup still refuses a stale metadata write.
251
- await this.storage.set(recordKey(id), JSON.stringify(next));
252
- }
230
+ // A rename racing revocation must retain revokedAt, including when
231
+ // an in-flight create has not yet published its lookup.
232
+ if (!(await this.storage.compareAndSet(recordKey(id), raw, JSON.stringify(next))))
233
+ continue;
253
234
  return next;
254
235
  }
255
236
  throw new Error("Access token metadata is busy; retry the operation");
256
237
  }
257
238
  async updateActive(id, adding) {
258
- const cas = this.storage.compareAndSet;
259
- if (!cas)
260
- throw new Error("Creating access tokens requires atomic compareAndSet storage");
261
239
  for (let attempt = 0; attempt < 32; attempt++) {
262
240
  const raw = await this.storage.get(ACTIVE_KEY);
263
241
  const ids = raw === null
264
- ? (await this.list()).filter(token => !token.revokedAt).map(token => token.id)
242
+ ? (await this.list()).filter((token) => !token.revokedAt).map((token) => token.id)
265
243
  : JSON.parse(raw);
266
- if (!Array.isArray(ids) || !ids.every(value => typeof value === "string" && ID_RE.test(value)) || new Set(ids).size !== ids.length) {
244
+ if (!Array.isArray(ids) ||
245
+ !ids.every((value) => typeof value === "string" && ID_RE.test(value)) ||
246
+ new Set(ids).size !== ids.length) {
267
247
  throw new Error("Stored access token capacity is invalid");
268
248
  }
269
249
  if (adding && ids.length >= this.maxActive)
270
250
  throw new Error(`This deployment already has the maximum of ${this.maxActive} active access tokens`);
271
- const next = adding ? [...ids, id] : ids.filter(value => value !== id);
272
- if (await cas.call(this.storage, ACTIVE_KEY, raw, JSON.stringify(next)))
251
+ const next = adding ? [...ids, id] : ids.filter((value) => value !== id);
252
+ if (await this.storage.compareAndSet(ACTIVE_KEY, raw, JSON.stringify(next)))
273
253
  return;
274
254
  }
275
255
  throw new Error("Access token capacity is busy; retry the operation");
276
256
  }
277
257
  async authorize(request) {
278
- const header = request.headers.get("authorization") ?? "";
279
- const match = /^Bearer\s+(.+)$/iu.exec(header);
280
- const token = match?.[1];
258
+ const credential = authorizationCredential(request);
259
+ const token = credential.kind === "bearer" ? credential.token : undefined;
281
260
  if (!token || !TOKEN_VALUE_RE.test(token))
282
261
  return unauthorized();
283
262
  const hash = await hashToken(token);
@@ -298,8 +277,19 @@ export class AccessTokenManager {
298
277
  };
299
278
  }
300
279
  }
280
+ /** The closed options accessTokens() accepts; see `assertKnownOptions`. */
281
+ const ACCESS_TOKENS_OPTIONS = optionsOf()(keys("maxActive"));
301
282
  /** Opt in using the same storage namespace that held the v0.23 records. */
302
283
  export function accessTokens(storage, options = {}) {
284
+ options = assertKnownOptions(options, "accessTokens()", ACCESS_TOKENS_OPTIONS);
303
285
  const manager = new AccessTokenManager(storage, options);
304
- return { auth: manager.auth, handle: context => routeAccessTokens(context, manager) };
286
+ const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
287
+ const optionSources = Object.freeze({
288
+ maxActive: options.maxActive === undefined ? "default" : "config",
289
+ });
290
+ return {
291
+ auth: manager.auth,
292
+ handle: (context) => routeAccessTokens(context, manager),
293
+ describe: () => ({ maxActive, optionSources }),
294
+ };
305
295
  }
@@ -0,0 +1,8 @@
1
+ // Weak ownership keeps the grouping context within the request's lifetime.
2
+ const requests = new WeakMap();
3
+ export function bindActivityRequest(scope, context) {
4
+ requests.set(scope, context);
5
+ }
6
+ export function activityRequest(scope) {
7
+ return scope ? requests.get(scope) : undefined;
8
+ }