@zackbart/connecta 0.15.1 → 0.16.1

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 (335) hide show
  1. package/AGENTS.md +40 -20
  2. package/CHANGELOG.md +653 -0
  3. package/README.md +25 -5
  4. package/bin/connecta.mjs +44 -3
  5. package/dist/access-tokens.d.ts +0 -1
  6. package/dist/access-tokens.js +0 -1
  7. package/dist/activity.d.ts +50 -2
  8. package/dist/activity.js +34 -1
  9. package/dist/apps-shell.d.ts +0 -1
  10. package/dist/apps-shell.js +0 -1
  11. package/dist/auth/bearer.d.ts +0 -1
  12. package/dist/auth/bearer.js +0 -1
  13. package/dist/auth/clerk.d.ts +0 -1
  14. package/dist/auth/clerk.js +45 -9
  15. package/dist/auth/downstream-oauth.d.ts +0 -1
  16. package/dist/auth/downstream-oauth.js +0 -1
  17. package/dist/call-admission.d.ts +0 -1
  18. package/dist/call-admission.js +0 -1
  19. package/dist/catalog-drift.d.ts +99 -0
  20. package/dist/catalog-drift.js +251 -0
  21. package/dist/catalog-fingerprint.d.ts +0 -1
  22. package/dist/catalog-fingerprint.js +0 -1
  23. package/dist/catalog-limits.d.ts +0 -1
  24. package/dist/catalog-limits.js +0 -1
  25. package/dist/catalog-service.d.ts +0 -1
  26. package/dist/catalog-service.js +41 -5
  27. package/dist/catalog.d.ts +11 -1
  28. package/dist/catalog.js +20 -1
  29. package/dist/concurrency.d.ts +0 -1
  30. package/dist/concurrency.js +0 -1
  31. package/dist/connector-scope.d.ts +0 -1
  32. package/dist/connector-scope.js +0 -1
  33. package/dist/connectors/api.d.ts +29 -21
  34. package/dist/connectors/api.js +45 -19
  35. package/dist/connectors/guarded-fetch.d.ts +106 -0
  36. package/dist/connectors/guarded-fetch.js +289 -0
  37. package/dist/connectors/remote-mcp.d.ts +0 -1
  38. package/dist/connectors/remote-mcp.js +0 -1
  39. package/dist/credentials.d.ts +0 -1
  40. package/dist/credentials.js +0 -1
  41. package/dist/errors.d.ts +30 -3
  42. package/dist/errors.js +1 -1
  43. package/dist/execute.d.ts +0 -1
  44. package/dist/execute.js +0 -1
  45. package/dist/executor-admission.d.ts +11 -1
  46. package/dist/executor-admission.js +28 -1
  47. package/dist/executor-result.d.ts +0 -1
  48. package/dist/executor-result.js +0 -1
  49. package/dist/executors/quickjs-child.d.ts +0 -1
  50. package/dist/executors/quickjs-child.js +0 -1
  51. package/dist/executors/quickjs-protocol.d.ts +0 -1
  52. package/dist/executors/quickjs-protocol.js +0 -1
  53. package/dist/executors/quickjs-runtime.d.ts +0 -1
  54. package/dist/executors/quickjs-runtime.js +0 -1
  55. package/dist/executors/quickjs.d.ts +0 -1
  56. package/dist/executors/quickjs.js +2 -1
  57. package/dist/favicon.d.ts +0 -1
  58. package/dist/favicon.js +0 -1
  59. package/dist/index.d.ts +2 -3
  60. package/dist/index.js +24 -7
  61. package/dist/invocation.d.ts +0 -1
  62. package/dist/invocation.js +0 -1
  63. package/dist/json-schema.d.ts +0 -1
  64. package/dist/json-schema.js +0 -1
  65. package/dist/mcp-result.d.ts +0 -1
  66. package/dist/mcp-result.js +0 -1
  67. package/dist/meta-tools.d.ts +0 -1
  68. package/dist/meta-tools.js +0 -1
  69. package/dist/node.d.ts +0 -1
  70. package/dist/node.js +0 -1
  71. package/dist/operator-ui/generated.d.ts +2 -3
  72. package/dist/operator-ui/generated.js +3 -4
  73. package/dist/operator-ui/model.d.ts +10 -1
  74. package/dist/operator-ui/model.js +0 -1
  75. package/dist/operator-ui/view.d.ts +167 -0
  76. package/dist/operator-ui/view.js +270 -0
  77. package/dist/providers/cloudflare.d.ts +0 -1
  78. package/dist/providers/cloudflare.js +261 -299
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -61
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -79
  83. package/dist/providers/notion.d.ts +0 -1
  84. package/dist/providers/notion.js +87 -42
  85. package/dist/providers/stripe.d.ts +13 -1
  86. package/dist/providers/stripe.js +31 -60
  87. package/dist/registry.d.ts +34 -2
  88. package/dist/registry.js +85 -5
  89. package/dist/retired-toolkits.d.ts +0 -1
  90. package/dist/retired-toolkits.js +0 -1
  91. package/dist/routes/access-tokens.d.ts +0 -1
  92. package/dist/routes/access-tokens.js +0 -1
  93. package/dist/routes/activity.d.ts +0 -1
  94. package/dist/routes/activity.js +0 -1
  95. package/dist/routes/credentials.d.ts +0 -1
  96. package/dist/routes/credentials.js +0 -1
  97. package/dist/routes/mcp.d.ts +0 -1
  98. package/dist/routes/mcp.js +0 -1
  99. package/dist/routes/oauth.d.ts +0 -1
  100. package/dist/routes/oauth.js +0 -1
  101. package/dist/routes/shared.d.ts +2 -1
  102. package/dist/routes/shared.js +0 -1
  103. package/dist/routes/ui.d.ts +0 -1
  104. package/dist/routes/ui.js +0 -1
  105. package/dist/server.d.ts +0 -1
  106. package/dist/server.js +11 -1
  107. package/dist/skills.d.ts +0 -1
  108. package/dist/skills.js +0 -1
  109. package/dist/storage/file.d.ts +0 -1
  110. package/dist/storage/file.js +0 -1
  111. package/dist/storage/memory.d.ts +0 -1
  112. package/dist/storage/memory.js +0 -1
  113. package/dist/timeout.d.ts +0 -1
  114. package/dist/timeout.js +0 -1
  115. package/dist/tool-safety.d.ts +0 -1
  116. package/dist/tool-safety.js +0 -1
  117. package/dist/types.d.ts +45 -1
  118. package/dist/types.js +0 -1
  119. package/dist/ui.d.ts +0 -1
  120. package/dist/ui.js +13 -137
  121. package/dist/validate.d.ts +13 -15
  122. package/dist/validate.js +24 -11
  123. package/dist/version.d.ts +1 -2
  124. package/dist/version.js +1 -2
  125. package/documentation/architecture.md +186 -5
  126. package/documentation/auth.md +11 -0
  127. package/documentation/call-admission.md +142 -6
  128. package/documentation/cloudflare.md +92 -10
  129. package/documentation/code-first-exploration.md +2 -2
  130. package/documentation/code-mode.md +14 -14
  131. package/documentation/connector-guides.md +172 -5
  132. package/documentation/connectors.md +89 -0
  133. package/documentation/linear.md +20 -2
  134. package/documentation/mcp-ui-design.md +2 -2
  135. package/documentation/meta-tools.md +7 -1
  136. package/documentation/mixpanel.md +42 -9
  137. package/documentation/notion.md +23 -2
  138. package/documentation/operations.md +325 -5
  139. package/documentation/operator-ui.md +120 -5
  140. package/documentation/provider-audit.md +160 -0
  141. package/documentation/provider-conventions.md +667 -0
  142. package/documentation/request-admission.md +167 -5
  143. package/documentation/rich-output-design.md +1 -2
  144. package/documentation/stripe.md +15 -0
  145. package/documentation/upgrading.md +454 -0
  146. package/ethos.md +14 -9
  147. package/examples/worker/README.md +102 -12
  148. package/examples/worker/src/index.ts +46 -1
  149. package/examples/worker/wrangler.jsonc +14 -1
  150. package/package.json +10 -6
  151. package/templates/node/.dockerignore +10 -0
  152. package/templates/node/.env.example +40 -3
  153. package/templates/node/AGENTS.md +16 -1
  154. package/templates/node/Dockerfile +49 -0
  155. package/templates/node/README.md +117 -2
  156. package/templates/node/docker-compose.yml +39 -0
  157. package/templates/node/package.json +1 -1
  158. package/templates/node/src/file-activity.ts +182 -0
  159. package/templates/node/src/index.ts +80 -4
  160. package/assets/connecta-clay-hero.png +0 -0
  161. package/dist/access-tokens.d.ts.map +0 -1
  162. package/dist/access-tokens.js.map +0 -1
  163. package/dist/activity.d.ts.map +0 -1
  164. package/dist/activity.js.map +0 -1
  165. package/dist/apps-shell.d.ts.map +0 -1
  166. package/dist/apps-shell.js.map +0 -1
  167. package/dist/auth/bearer.d.ts.map +0 -1
  168. package/dist/auth/bearer.js.map +0 -1
  169. package/dist/auth/clerk.d.ts.map +0 -1
  170. package/dist/auth/clerk.js.map +0 -1
  171. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  172. package/dist/auth/downstream-oauth.js.map +0 -1
  173. package/dist/call-admission.d.ts.map +0 -1
  174. package/dist/call-admission.js.map +0 -1
  175. package/dist/catalog-fingerprint.d.ts.map +0 -1
  176. package/dist/catalog-fingerprint.js.map +0 -1
  177. package/dist/catalog-limits.d.ts.map +0 -1
  178. package/dist/catalog-limits.js.map +0 -1
  179. package/dist/catalog-service.d.ts.map +0 -1
  180. package/dist/catalog-service.js.map +0 -1
  181. package/dist/catalog.d.ts.map +0 -1
  182. package/dist/catalog.js.map +0 -1
  183. package/dist/concurrency.d.ts.map +0 -1
  184. package/dist/concurrency.js.map +0 -1
  185. package/dist/connector-scope.d.ts.map +0 -1
  186. package/dist/connector-scope.js.map +0 -1
  187. package/dist/connectors/api.d.ts.map +0 -1
  188. package/dist/connectors/api.js.map +0 -1
  189. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  190. package/dist/connectors/remote-mcp.js.map +0 -1
  191. package/dist/credentials.d.ts.map +0 -1
  192. package/dist/credentials.js.map +0 -1
  193. package/dist/errors.d.ts.map +0 -1
  194. package/dist/errors.js.map +0 -1
  195. package/dist/execute.d.ts.map +0 -1
  196. package/dist/execute.js.map +0 -1
  197. package/dist/executor-admission.d.ts.map +0 -1
  198. package/dist/executor-admission.js.map +0 -1
  199. package/dist/executor-result.d.ts.map +0 -1
  200. package/dist/executor-result.js.map +0 -1
  201. package/dist/executors/quickjs-child.d.ts.map +0 -1
  202. package/dist/executors/quickjs-child.js.map +0 -1
  203. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  204. package/dist/executors/quickjs-protocol.js.map +0 -1
  205. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  206. package/dist/executors/quickjs-runtime.js.map +0 -1
  207. package/dist/executors/quickjs.d.ts.map +0 -1
  208. package/dist/executors/quickjs.js.map +0 -1
  209. package/dist/favicon.d.ts.map +0 -1
  210. package/dist/favicon.js.map +0 -1
  211. package/dist/index.d.ts.map +0 -1
  212. package/dist/index.js.map +0 -1
  213. package/dist/invocation.d.ts.map +0 -1
  214. package/dist/invocation.js.map +0 -1
  215. package/dist/json-schema.d.ts.map +0 -1
  216. package/dist/json-schema.js.map +0 -1
  217. package/dist/mcp-result.d.ts.map +0 -1
  218. package/dist/mcp-result.js.map +0 -1
  219. package/dist/meta-tools.d.ts.map +0 -1
  220. package/dist/meta-tools.js.map +0 -1
  221. package/dist/node.d.ts.map +0 -1
  222. package/dist/node.js.map +0 -1
  223. package/dist/operator-ui/generated.d.ts.map +0 -1
  224. package/dist/operator-ui/generated.js.map +0 -1
  225. package/dist/operator-ui/model.d.ts.map +0 -1
  226. package/dist/operator-ui/model.js.map +0 -1
  227. package/dist/providers/cloudflare.d.ts.map +0 -1
  228. package/dist/providers/cloudflare.js.map +0 -1
  229. package/dist/providers/linear.d.ts.map +0 -1
  230. package/dist/providers/linear.js.map +0 -1
  231. package/dist/providers/mixpanel.d.ts.map +0 -1
  232. package/dist/providers/mixpanel.js.map +0 -1
  233. package/dist/providers/notion.d.ts.map +0 -1
  234. package/dist/providers/notion.js.map +0 -1
  235. package/dist/providers/stripe.d.ts.map +0 -1
  236. package/dist/providers/stripe.js.map +0 -1
  237. package/dist/registry.d.ts.map +0 -1
  238. package/dist/registry.js.map +0 -1
  239. package/dist/retired-toolkits.d.ts.map +0 -1
  240. package/dist/retired-toolkits.js.map +0 -1
  241. package/dist/routes/access-tokens.d.ts.map +0 -1
  242. package/dist/routes/access-tokens.js.map +0 -1
  243. package/dist/routes/activity.d.ts.map +0 -1
  244. package/dist/routes/activity.js.map +0 -1
  245. package/dist/routes/credentials.d.ts.map +0 -1
  246. package/dist/routes/credentials.js.map +0 -1
  247. package/dist/routes/mcp.d.ts.map +0 -1
  248. package/dist/routes/mcp.js.map +0 -1
  249. package/dist/routes/oauth.d.ts.map +0 -1
  250. package/dist/routes/oauth.js.map +0 -1
  251. package/dist/routes/shared.d.ts.map +0 -1
  252. package/dist/routes/shared.js.map +0 -1
  253. package/dist/routes/ui.d.ts.map +0 -1
  254. package/dist/routes/ui.js.map +0 -1
  255. package/dist/server.d.ts.map +0 -1
  256. package/dist/server.js.map +0 -1
  257. package/dist/skills.d.ts.map +0 -1
  258. package/dist/skills.js.map +0 -1
  259. package/dist/storage/file.d.ts.map +0 -1
  260. package/dist/storage/file.js.map +0 -1
  261. package/dist/storage/memory.d.ts.map +0 -1
  262. package/dist/storage/memory.js.map +0 -1
  263. package/dist/timeout.d.ts.map +0 -1
  264. package/dist/timeout.js.map +0 -1
  265. package/dist/tool-safety.d.ts.map +0 -1
  266. package/dist/tool-safety.js.map +0 -1
  267. package/dist/types.d.ts.map +0 -1
  268. package/dist/types.js.map +0 -1
  269. package/dist/ui.d.ts.map +0 -1
  270. package/dist/ui.js.map +0 -1
  271. package/dist/validate.d.ts.map +0 -1
  272. package/dist/validate.js.map +0 -1
  273. package/dist/version.d.ts.map +0 -1
  274. package/dist/version.js.map +0 -1
  275. package/examples/node/README.md +0 -52
  276. package/examples/node/src/index.ts +0 -71
  277. package/src/access-tokens.ts +0 -289
  278. package/src/activity.ts +0 -234
  279. package/src/apps-shell.ts +0 -370
  280. package/src/auth/bearer.ts +0 -61
  281. package/src/auth/clerk.ts +0 -553
  282. package/src/auth/downstream-oauth.ts +0 -575
  283. package/src/call-admission.ts +0 -521
  284. package/src/catalog-fingerprint.ts +0 -52
  285. package/src/catalog-limits.ts +0 -11
  286. package/src/catalog-service.ts +0 -1153
  287. package/src/catalog.ts +0 -790
  288. package/src/concurrency.ts +0 -39
  289. package/src/connector-scope.ts +0 -66
  290. package/src/connectors/api.ts +0 -159
  291. package/src/connectors/remote-mcp.ts +0 -1090
  292. package/src/credentials.ts +0 -428
  293. package/src/errors.ts +0 -364
  294. package/src/execute.ts +0 -1406
  295. package/src/executor-admission.ts +0 -328
  296. package/src/executor-result.ts +0 -107
  297. package/src/executors/quickjs-child.ts +0 -172
  298. package/src/executors/quickjs-protocol.ts +0 -99
  299. package/src/executors/quickjs-runtime.ts +0 -470
  300. package/src/executors/quickjs.ts +0 -776
  301. package/src/favicon.ts +0 -53
  302. package/src/index.ts +0 -666
  303. package/src/invocation.ts +0 -655
  304. package/src/json-schema.ts +0 -11
  305. package/src/mcp-result.ts +0 -43
  306. package/src/meta-tools.ts +0 -1607
  307. package/src/node.ts +0 -206
  308. package/src/operator-ui/browser.css +0 -423
  309. package/src/operator-ui/browser.ts +0 -1281
  310. package/src/operator-ui/generated.ts +0 -4
  311. package/src/operator-ui/model.ts +0 -111
  312. package/src/providers/cloudflare.ts +0 -3999
  313. package/src/providers/linear.ts +0 -302
  314. package/src/providers/mixpanel.ts +0 -229
  315. package/src/providers/notion.ts +0 -1879
  316. package/src/providers/stripe.ts +0 -307
  317. package/src/registry.ts +0 -1085
  318. package/src/retired-toolkits.ts +0 -14
  319. package/src/routes/access-tokens.ts +0 -115
  320. package/src/routes/activity.ts +0 -215
  321. package/src/routes/credentials.ts +0 -254
  322. package/src/routes/mcp.ts +0 -429
  323. package/src/routes/oauth.ts +0 -322
  324. package/src/routes/shared.ts +0 -247
  325. package/src/routes/ui.ts +0 -152
  326. package/src/server.ts +0 -195
  327. package/src/skills.ts +0 -271
  328. package/src/storage/file.ts +0 -123
  329. package/src/storage/memory.ts +0 -44
  330. package/src/timeout.ts +0 -84
  331. package/src/tool-safety.ts +0 -15
  332. package/src/types.ts +0 -523
  333. package/src/ui.ts +0 -744
  334. package/src/validate.ts +0 -387
  335. package/src/version.ts +0 -7
@@ -1,52 +0,0 @@
1
- # connecta — Node repository example
2
-
3
- This example runs against the current package source. For an independently
4
- installable deployment, use the root initializer instead:
5
-
6
- ```sh
7
- npx @zackbart/connecta init my-connecta
8
- ```
9
-
10
- From the package repository, run this example with:
11
-
12
- ```sh
13
- npm install
14
- CONNECTA_TOKEN=dev-token npx tsx examples/node/src/index.ts
15
- ```
16
-
17
- It serves the prescribed seven-tool code-first surface: `execute_code` in a
18
- bounded QuickJS child plus the six explicit boundary tools.
19
-
20
- - MCP endpoint: `http://localhost:8787/mcp`, with
21
- `Authorization: Bearer dev-token`
22
- - Operator pages: Connections at `http://localhost:8787/`, Credentials at
23
- `/credentials`, and Activity at `/activity` (paste the same token; this
24
- bearer-only example cannot manage credentials)
25
- - Health: `http://localhost:8787/health`
26
-
27
- `PORT` defaults to `8787`; `CONNECTA_TOKEN` is required. Use a long random
28
- value outside this local example.
29
-
30
- ## The deployment contract
31
-
32
- Keep this deployment small:
33
-
34
- - Edit `src/index.ts` to change connectors, auth, storage, and the public URL.
35
- - Keep the required `executor: quickJsExecutor()` configuration.
36
- - Keep secrets in environment variables or an external secret store. Never put
37
- tokens in `src/index.ts`, connector guides, or committed JSON.
38
- - Add application code only when implementing a deliberate `api()` connector.
39
- Do not copy Connecta internals into the deployment.
40
- - Run the repository's `npm run check` after a package change.
41
-
42
- `fileStorage("./.connecta-state.json")` persists downstream OAuth tokens and
43
- tool catalogs across restarts. `memoryStorage()` is sufficient only when the
44
- deployment needs neither. Add `remoteMcp(...)` entries for downstream MCP
45
- servers or `api(...)` connectors for HTTP APIs you deliberately expose.
46
- Downstream OAuth also requires `publicUrl` to be an origin the browser can
47
- reach.
48
-
49
- Docker packaging of the same code-first shape is
50
- [repository-only](https://github.com/zackbart/connecta/tree/main/examples/docker).
51
- The standalone template used by the initializer is in
52
- [`../../templates/node/`](../../templates/node/).
@@ -1,71 +0,0 @@
1
- /**
2
- * connecta on Node.
3
- *
4
- * One MCP endpoint aggregating two in-code HTTP API connectors behind the
5
- * seven-tool code-first surface (execute_code in a QuickJS/WASM sandbox plus the
6
- * six explicit tools), guarded by a static bearer token, with OAuth/cache state
7
- * on disk.
8
- *
9
- * Run:
10
- * CONNECTA_TOKEN=dev-token npx tsx examples/node/src/index.ts
11
- * # then point an MCP client at http://localhost:8787/mcp with
12
- * # Authorization: Bearer dev-token
13
- */
14
- import { api, bearerToken, createConnecta } from "@zackbart/connecta";
15
- import { fileStorage, listen } from "@zackbart/connecta/node";
16
- import { quickJsExecutor } from "@zackbart/connecta/quickjs";
17
-
18
- const token = process.env.CONNECTA_TOKEN;
19
- if (!token) {
20
- throw new Error(
21
- "CONNECTA_TOKEN is required. Refusing to start without inbound auth.",
22
- );
23
- }
24
- const port = Number(process.env.PORT ?? 8787);
25
-
26
- const connecta = createConnecta({
27
- // fileStorage persists downstream-OAuth/cache state across restarts.
28
- // Swap for memoryStorage() if you don't need persistence.
29
- storage: fileStorage("./.connecta-state.json"),
30
- auth: bearerToken(token, { subjectId: "operator" }),
31
- // Downstream OAuth callbacks use this deployment origin.
32
- publicUrl: `http://localhost:${port}`,
33
- // Required: QuickJS runs model-written JS in a bounded disposable child.
34
- executor: quickJsExecutor(),
35
- connectors: [
36
- api("time", {
37
- description: "Time — current timestamp",
38
- tools: [
39
- {
40
- name: "get_now",
41
- description: "Return the current time as an ISO 8601 timestamp.",
42
- inputSchema: { type: "object", properties: {} },
43
- annotations: { readOnlyHint: true },
44
- handler: async () => ({ now: new Date().toISOString() }),
45
- },
46
- ],
47
- }),
48
- api("text", {
49
- description: "Text — string utilities",
50
- tools: [
51
- {
52
- name: "upper",
53
- description: "Uppercase the given text.",
54
- inputSchema: {
55
- type: "object",
56
- properties: { text: { type: "string" } },
57
- required: ["text"],
58
- },
59
- annotations: { readOnlyHint: true },
60
- handler: async ({ text }: { text: string }) => ({
61
- text: text.toUpperCase(),
62
- }),
63
- },
64
- ],
65
- }),
66
- ],
67
- });
68
-
69
- listen(connecta, port);
70
-
71
- console.log(`connecta listening on http://localhost:${port}/mcp`);
@@ -1,289 +0,0 @@
1
- import type { AuthResult, InboundAuth, KVStorage } from "./types.js";
2
-
3
- const TOKEN_PREFIX = "cta_";
4
- const TOKEN_BYTES = 32;
5
- 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
- const MAX_NAME_CHARACTERS = 80;
9
- const DEFAULT_MAX_ACTIVE = 100;
10
- const MAX_CONFIGURED_ACTIVE = 1_000;
11
- const encoder = new TextEncoder();
12
-
13
- interface StoredAccessToken {
14
- version: 1;
15
- id: string;
16
- name: string;
17
- tokenHash: string;
18
- tokenPrefix: string;
19
- createdAt: string;
20
- createdBy: string;
21
- revokedAt?: string;
22
- revokedBy?: string;
23
- }
24
-
25
- interface TokenLookup {
26
- version: 1;
27
- id: string;
28
- }
29
-
30
- export interface AccessTokenMetadata {
31
- id: string;
32
- name: string;
33
- tokenPrefix: string;
34
- createdAt: string;
35
- revokedAt?: string;
36
- }
37
-
38
- export interface CreatedAccessToken {
39
- token: string;
40
- accessToken: AccessTokenMetadata;
41
- }
42
-
43
- function recordKey(id: string): string {
44
- return `${RECORD_PREFIX}${id}`;
45
- }
46
-
47
- function lookupKey(hash: string): string {
48
- return `${LOOKUP_PREFIX}${hash}`;
49
- }
50
-
51
- function bytesToBase64Url(bytes: Uint8Array): string {
52
- let binary = "";
53
- for (const byte of bytes) binary += String.fromCharCode(byte);
54
- return btoa(binary)
55
- .replaceAll("+", "-")
56
- .replaceAll("/", "_")
57
- .replace(/=+$/u, "");
58
- }
59
-
60
- function bytesToHex(bytes: Uint8Array): string {
61
- return [...bytes]
62
- .map((byte) => byte.toString(16).padStart(2, "0"))
63
- .join("");
64
- }
65
-
66
- async function hashToken(token: string): Promise<string> {
67
- return bytesToHex(
68
- new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token))),
69
- );
70
- }
71
-
72
- function normalizeName(value: unknown): string {
73
- if (typeof value !== "string") {
74
- throw new Error("Token name must be a string");
75
- }
76
- const compact = value.replace(/\s+/gu, " ").trim();
77
- if (!compact) throw new Error("Token name cannot be empty");
78
- if (Array.from(compact).length > MAX_NAME_CHARACTERS) {
79
- throw new Error(
80
- `Token name cannot exceed ${MAX_NAME_CHARACTERS} characters`,
81
- );
82
- }
83
- return compact;
84
- }
85
-
86
- function parseRecord(raw: string): StoredAccessToken {
87
- try {
88
- const value = JSON.parse(raw) as Partial<StoredAccessToken>;
89
- if (
90
- value.version !== 1 ||
91
- typeof value.id !== "string" ||
92
- !/^[0-9a-f-]{36}$/u.test(value.id) ||
93
- typeof value.name !== "string" ||
94
- typeof value.tokenHash !== "string" ||
95
- !/^[0-9a-f]{64}$/u.test(value.tokenHash) ||
96
- typeof value.tokenPrefix !== "string" ||
97
- typeof value.createdAt !== "string" ||
98
- typeof value.createdBy !== "string" ||
99
- (value.revokedAt !== undefined &&
100
- typeof value.revokedAt !== "string") ||
101
- (value.revokedBy !== undefined &&
102
- typeof value.revokedBy !== "string")
103
- ) {
104
- throw new Error("invalid token record");
105
- }
106
- return value as StoredAccessToken;
107
- } catch {
108
- throw new Error("Stored access token metadata is invalid or corrupted");
109
- }
110
- }
111
-
112
- function parseLookup(raw: string): TokenLookup | null {
113
- try {
114
- const value = JSON.parse(raw) as Partial<TokenLookup>;
115
- return value.version === 1 && typeof value.id === "string"
116
- ? { version: 1, id: value.id }
117
- : null;
118
- } catch {
119
- return null;
120
- }
121
- }
122
-
123
- function metadata(record: StoredAccessToken): AccessTokenMetadata {
124
- return {
125
- id: record.id,
126
- name: record.name,
127
- tokenPrefix: record.tokenPrefix,
128
- createdAt: record.createdAt,
129
- ...(record.revokedAt ? { revokedAt: record.revokedAt } : {}),
130
- };
131
- }
132
-
133
- function unauthorized(): AuthResult {
134
- return {
135
- ok: false,
136
- response: new Response(JSON.stringify({ error: "unauthorized" }), {
137
- status: 401,
138
- headers: {
139
- "Content-Type": "application/json",
140
- "WWW-Authenticate": "Bearer",
141
- },
142
- }),
143
- };
144
- }
145
-
146
- /**
147
- * Deployment-scoped personal access tokens. Secret material is never
148
- * recoverable: authentication indexes a SHA-256 digest of a random 256-bit
149
- * token, while separately enumerable metadata powers operator management.
150
- */
151
- export class AccessTokenManager {
152
- readonly auth: InboundAuth;
153
- private readonly maxActive: number;
154
-
155
- constructor(
156
- private readonly storage: KVStorage,
157
- options: { maxActive?: number } = {},
158
- ) {
159
- if (!storage.list) {
160
- throw new Error(
161
- "accessTokens requires a storage adapter that implements list(prefix)",
162
- );
163
- }
164
- const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
165
- if (
166
- !Number.isInteger(maxActive) ||
167
- maxActive < 1 ||
168
- maxActive > MAX_CONFIGURED_ACTIVE
169
- ) {
170
- throw new Error(
171
- `accessTokens.maxActive must be a whole number from 1 to ${MAX_CONFIGURED_ACTIVE}`,
172
- );
173
- }
174
- this.maxActive = maxActive;
175
- this.auth = {
176
- kind: "access_token",
177
- activityActorNamespace: "connecta:access-tokens:v1",
178
- activityActorLabel: async (id) => {
179
- try {
180
- return (await this.read(id))?.name;
181
- } catch {
182
- return undefined;
183
- }
184
- },
185
- authorize: (request) => this.authorize(request),
186
- };
187
- }
188
-
189
- private async read(id: string): Promise<StoredAccessToken | null> {
190
- const raw = await this.storage.get(recordKey(id));
191
- return raw ? parseRecord(raw) : null;
192
- }
193
-
194
- async list(): Promise<AccessTokenMetadata[]> {
195
- const keys = await this.storage.list!(RECORD_PREFIX);
196
- const records = await Promise.all(
197
- keys.map(async (key) => {
198
- const raw = await this.storage.get(key);
199
- return raw ? parseRecord(raw) : null;
200
- }),
201
- );
202
- return records
203
- .filter((record): record is StoredAccessToken => Boolean(record))
204
- .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
205
- .map(metadata);
206
- }
207
-
208
- async create(name: unknown, createdBy: string): Promise<CreatedAccessToken> {
209
- const normalizedName = normalizeName(name);
210
- const active = (await this.list()).filter((token) => !token.revokedAt);
211
- if (active.length >= this.maxActive) {
212
- throw new Error(
213
- `This deployment already has the maximum of ${this.maxActive} active access tokens`,
214
- );
215
- }
216
- const secretBytes = crypto.getRandomValues(new Uint8Array(TOKEN_BYTES));
217
- const token = TOKEN_PREFIX + bytesToBase64Url(secretBytes);
218
- const hash = await hashToken(token);
219
- if (await this.storage.get(lookupKey(hash))) {
220
- throw new Error("Access token collision; create another token");
221
- }
222
- const record: StoredAccessToken = {
223
- version: 1,
224
- id: crypto.randomUUID(),
225
- name: normalizedName,
226
- tokenHash: hash,
227
- tokenPrefix: token.slice(0, 12),
228
- createdAt: new Date().toISOString(),
229
- createdBy,
230
- };
231
- await this.storage.set(recordKey(record.id), JSON.stringify(record));
232
- try {
233
- await this.storage.set(
234
- lookupKey(hash),
235
- JSON.stringify({ version: 1, id: record.id } satisfies TokenLookup),
236
- );
237
- } catch (error) {
238
- await this.storage.delete(recordKey(record.id)).catch(() => {});
239
- throw error;
240
- }
241
- return { token, accessToken: metadata(record) };
242
- }
243
-
244
- async rename(
245
- id: string,
246
- name: unknown,
247
- ): Promise<AccessTokenMetadata | null> {
248
- const record = await this.read(id);
249
- if (!record) return null;
250
- record.name = normalizeName(name);
251
- await this.storage.set(recordKey(id), JSON.stringify(record));
252
- return metadata(record);
253
- }
254
-
255
- async revoke(
256
- id: string,
257
- revokedBy: string,
258
- ): Promise<AccessTokenMetadata | null> {
259
- const record = await this.read(id);
260
- if (!record) return null;
261
- if (!record.revokedAt) {
262
- // Admission disappears first. A metadata-write failure may leave the UI
263
- // calling the record active, but can never leave a token labelled
264
- // revoked while its lookup still admits requests.
265
- await this.storage.delete(lookupKey(record.tokenHash));
266
- record.revokedAt = new Date().toISOString();
267
- record.revokedBy = revokedBy;
268
- await this.storage.set(recordKey(id), JSON.stringify(record));
269
- }
270
- return metadata(record);
271
- }
272
-
273
- private async authorize(request: Request): Promise<AuthResult> {
274
- const header = request.headers.get("authorization") ?? "";
275
- const match = /^Bearer\s+(.+)$/iu.exec(header);
276
- const token = match?.[1];
277
- if (!token || !TOKEN_VALUE_RE.test(token)) return unauthorized();
278
- const hash = await hashToken(token);
279
- const lookupRaw = await this.storage.get(lookupKey(hash));
280
- if (!lookupRaw) return unauthorized();
281
- const lookup = parseLookup(lookupRaw);
282
- if (!lookup) return unauthorized();
283
- const record = await this.read(lookup.id);
284
- if (!record || record.revokedAt || record.tokenHash !== hash) {
285
- return unauthorized();
286
- }
287
- return { ok: true, subjectId: record.id };
288
- }
289
- }
package/src/activity.ts DELETED
@@ -1,234 +0,0 @@
1
- import { boundedEchoText } from "./errors.js";
2
- import type { Logger } from "./types.js";
3
-
4
- /**
5
- * How long an identity field may be before the store stops believing it.
6
- *
7
- * `connectorId` and `toolName` are ordinarily operator- and connector-authored,
8
- * and 128 bytes is far past any real one. But an address that resolved to
9
- * nothing is recorded *as written*, which puts a caller-authored string in both
10
- * fields — and "payload-free by construction" has to mean the event type has
11
- * nowhere to put a payload, not merely that connecta declines to. A 40 KB
12
- * invented connector id is a payload wearing an id's clothing.
13
- *
14
- * Clamped rather than dropped: the invented id is precisely what an operator
15
- * needs to see, and its first 128 bytes identify the mistake as well as all
16
- * 40,000 would. The `…` marker keeps a clamped value from reading as a real one.
17
- */
18
- const MAX_ACTIVITY_NAME_BYTES = 128;
19
-
20
- /** Two names and the dot between them. */
21
- const MAX_ACTIVITY_ADDRESS_BYTES = MAX_ACTIVITY_NAME_BYTES * 2 + 1;
22
-
23
- export type ActivityCallSource =
24
- | "call_tool"
25
- | "call_destructive_tool"
26
- // Read-only history. Nothing emits `batch_call` since issue #273 removed the
27
- // tool, but activity storage is append-only: rows written by older
28
- // deployments are still read back — the D1 example maps a stored row straight
29
- // into this type — and an operator's timeline should not have to lie about
30
- // where a call came from. Never widen this member back into a live source.
31
- | "batch_call"
32
- | "execute_code";
33
-
34
- export type ActivityOutcome =
35
- | "success"
36
- | "error"
37
- | "timeout"
38
- | "cancelled";
39
-
40
- export type AgentFriction =
41
- | "tool_not_found"
42
- | "schema_retry"
43
- | "destructive_reroute"
44
- | "auth_required"
45
- | "result_too_large";
46
-
47
- /** Coarse recovery class derived without inspecting payloads or error prose. */
48
- export function agentFrictionForCode(
49
- code: string | undefined,
50
- ): AgentFriction | undefined {
51
- switch (code) {
52
- case "unknown_address":
53
- case "unknown_tool":
54
- case "ambiguous_tool_alias":
55
- return "tool_not_found";
56
- case "invalid_args":
57
- return "schema_retry";
58
- case "destructive_tool_requires_approval":
59
- return "destructive_reroute";
60
- case "auth_required":
61
- return "auth_required";
62
- case "result_too_large":
63
- return "result_too_large";
64
- default:
65
- return undefined;
66
- }
67
- }
68
-
69
- /**
70
- * Authenticated identity attached to an activity event. `id` is intentionally
71
- * optional: open deployments and shared bearer tokens cannot honestly identify
72
- * a person.
73
- */
74
- export interface ActivityActor {
75
- kind: string;
76
- id?: string;
77
- /**
78
- * Stable, non-secret identity-directory namespace supplied by the admitting
79
- * auth provider. It lets authorized reads resolve ids through the same
80
- * provider rather than another provider that happens to share `kind`.
81
- */
82
- namespace?: string;
83
- }
84
-
85
- /**
86
- * Privacy-minimal history of one resolved downstream connector call.
87
- *
88
- * Arguments, results, generated code, search text, and raw errors are excluded
89
- * by construction. Deployments that need a safe human summary can add a
90
- * separate, explicit connector-level feature later.
91
- */
92
- export interface ToolCallActivityEvent {
93
- schemaVersion: 1;
94
- id: string;
95
- occurredAt: string;
96
- requestId: string;
97
- actor: ActivityActor;
98
- connectorId: string;
99
- toolName: string;
100
- address: string;
101
- source: ActivityCallSource;
102
- outcome: ActivityOutcome;
103
- durationMs: number;
104
- attempts: number;
105
- /** Set only when the call actually failed; a truncated success has none. */
106
- errorCode?: string;
107
- /**
108
- * Payload-free recovery class. Usually derived from `errorCode`, but it can
109
- * also stand alone: a result too large to return inline is friction for the
110
- * agent while remaining an `outcome: "success"` call with no error code.
111
- */
112
- friction?: AgentFriction;
113
- serverName: string;
114
- serverVersion: string;
115
- deploymentId?: string;
116
- }
117
-
118
- export interface ActivityPage {
119
- events: ToolCallActivityEvent[];
120
- nextCursor?: string;
121
- }
122
-
123
- /** Display-only actor returned by the authenticated activity read API. */
124
- export interface ActivityReadActor extends ActivityActor {
125
- label?: string;
126
- }
127
-
128
- export type ActivityReadEvent = Omit<ToolCallActivityEvent, "actor"> & {
129
- actor: ActivityReadActor;
130
- };
131
-
132
- export interface ActivityReadPage {
133
- events: ActivityReadEvent[];
134
- nextCursor?: string;
135
- }
136
-
137
- /** Write-only deployments can implement only this small, vendor-neutral seam. */
138
- export interface ActivitySink {
139
- record(event: ToolCallActivityEvent): void | Promise<void>;
140
- }
141
-
142
- /** Optional read side used by Connecta's authenticated Activity UI. */
143
- export interface ActivityReader {
144
- list(options: { cursor?: string; limit: number }): Promise<ActivityPage>;
145
- }
146
-
147
- export interface ActivityStore extends ActivitySink {
148
- list?: ActivityReader["list"];
149
- }
150
-
151
- /** Reader implementations throw this for an opaque cursor they cannot decode. */
152
- export class InvalidActivityCursorError extends Error {
153
- override name = "InvalidActivityCursorError";
154
-
155
- constructor() {
156
- super("invalid activity cursor");
157
- }
158
- }
159
-
160
- export type ActivityReadGate = (
161
- actor: ActivityActor,
162
- ) => boolean | Promise<boolean>;
163
-
164
- /** Request-scoped context shared by direct, batch, and code-mode call paths. */
165
- export interface ActivityRequestContext {
166
- sink: ActivitySink;
167
- actor: ActivityActor;
168
- requestId: string;
169
- serverInfo: { name: string; version: string };
170
- deploymentId?: string;
171
- defer?: (promise: Promise<unknown>) => void;
172
- logger: Logger;
173
- }
174
-
175
- export type ActivityEventInput = Pick<
176
- ToolCallActivityEvent,
177
- | "connectorId"
178
- | "toolName"
179
- | "address"
180
- | "source"
181
- | "outcome"
182
- | "durationMs"
183
- | "attempts"
184
- | "errorCode"
185
- | "friction"
186
- >;
187
-
188
- /**
189
- * Best-effort by design: activity storage can never change a tool result.
190
- * Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
191
- * Engine complete inline; Node promises remain detached from the response.
192
- */
193
- export function recordToolActivity(
194
- context: ActivityRequestContext | undefined,
195
- input: ActivityEventInput,
196
- ): void {
197
- if (!context) return;
198
- // A caller-supplied class wins because it knows something the code table
199
- // cannot: friction that belongs to a call which did not fail.
200
- const friction = input.friction ?? agentFrictionForCode(input.errorCode);
201
- const event: ToolCallActivityEvent = {
202
- schemaVersion: 1,
203
- id: crypto.randomUUID(),
204
- occurredAt: new Date().toISOString(),
205
- requestId: context.requestId,
206
- actor: context.actor,
207
- connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
208
- toolName: boundedEchoText(input.toolName, MAX_ACTIVITY_NAME_BYTES),
209
- address: boundedEchoText(input.address, MAX_ACTIVITY_ADDRESS_BYTES),
210
- source: input.source,
211
- outcome: input.outcome,
212
- durationMs: Math.max(0, Math.trunc(input.durationMs)),
213
- attempts: Math.max(1, Math.trunc(input.attempts)),
214
- ...(input.errorCode ? { errorCode: input.errorCode } : {}),
215
- ...(friction ? { friction } : {}),
216
- serverName: context.serverInfo.name,
217
- serverVersion: context.serverInfo.version,
218
- ...(context.deploymentId
219
- ? { deploymentId: context.deploymentId }
220
- : {}),
221
- };
222
- try {
223
- const result = context.sink.record(event);
224
- if (!result || typeof (result as Promise<unknown>).then !== "function") {
225
- return;
226
- }
227
- const pending = Promise.resolve(result).catch((error) => {
228
- context.logger.warn("[connecta] activity record failed", error);
229
- });
230
- if (context.defer) context.defer(pending);
231
- } catch (error) {
232
- context.logger.warn("[connecta] activity record failed", error);
233
- }
234
- }