@zackbart/connecta 0.28.1 → 0.29.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 (319) hide show
  1. package/CHANGELOG.md +788 -5
  2. package/README.md +62 -42
  3. package/bin/connecta.mjs +110 -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/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1584 -1580
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +93 -71
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +59 -7
  52. package/dist/connectors/api-connector.js +106 -19
  53. package/dist/connectors/api.js +4 -1
  54. package/dist/connectors/guarded-fetch.d.ts +144 -0
  55. package/dist/connectors/guarded-fetch.js +84 -35
  56. package/dist/connectors/negotiation-cache.js +46 -0
  57. package/dist/connectors/option-shapes.js +27 -0
  58. package/dist/connectors/remote-mcp.d.ts +30 -4
  59. package/dist/connectors/remote-mcp.js +1774 -320
  60. package/dist/connectors/resource-uri.js +335 -0
  61. package/dist/credential-contract.d.ts +10 -0
  62. package/dist/credential-rules.js +1 -3
  63. package/dist/credential-url.js +108 -0
  64. package/dist/credentials.d.ts +6 -1
  65. package/dist/credentials.js +27 -18
  66. package/dist/d1.d.ts +130 -0
  67. package/dist/d1.js +290 -0
  68. package/dist/describe-config.d.ts +165 -0
  69. package/dist/describe-config.js +315 -0
  70. package/dist/described.js +106 -0
  71. package/dist/downstream-input-context.js +119 -0
  72. package/dist/downstream-input.js +323 -0
  73. package/dist/errors.d.ts +97 -20
  74. package/dist/errors.js +158 -80
  75. package/dist/execute.js +453 -305
  76. package/dist/executor-admission.d.ts +1 -0
  77. package/dist/executor-admission.js +11 -15
  78. package/dist/executor-contract.d.ts +20 -0
  79. package/dist/executor-contract.js +48 -0
  80. package/dist/executor-result.js +24 -9
  81. package/dist/executors/quickjs-child.js +3 -4
  82. package/dist/executors/quickjs-runtime.js +147 -59
  83. package/dist/executors/quickjs.js +29 -35
  84. package/dist/favicon.js +1 -2
  85. package/dist/fix-prompt.js +1 -3
  86. package/dist/guest-runtime.js +188 -0
  87. package/dist/guest-types.d.ts +176 -0
  88. package/dist/guest-types.js +1 -0
  89. package/dist/html-security.js +24 -0
  90. package/dist/identity.js +1 -3
  91. package/dist/inbound-credential.js +52 -0
  92. package/dist/index.d.ts +24 -256
  93. package/dist/index.js +65 -478
  94. package/dist/invocation-auth.js +69 -0
  95. package/dist/invocation.js +230 -73
  96. package/dist/json-schema.d.ts +1 -1
  97. package/dist/mcp-client-context.js +35 -0
  98. package/dist/mcp-result.js +11 -7
  99. package/dist/meta-output.js +76 -0
  100. package/dist/meta-tool-names.d.ts +2 -0
  101. package/dist/meta-tool-names.js +3 -0
  102. package/dist/meta-tools.js +285 -209
  103. package/dist/module-contracts.d.ts +8 -23
  104. package/dist/node.d.ts +0 -2
  105. package/dist/node.js +2 -2
  106. package/dist/oauth-callback-outcome.js +1 -3
  107. package/dist/oauth-handoff.js +117 -0
  108. package/dist/oauth-sealing.js +20 -0
  109. package/dist/operator-record.d.ts +137 -0
  110. package/dist/operator-record.js +420 -0
  111. package/dist/operator-ui/assets.js +31 -0
  112. package/dist/operator-ui/contract.d.ts +72 -0
  113. package/dist/operator-ui/contract.js +1 -0
  114. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  115. package/dist/operator-ui/generated.js +5 -4
  116. package/dist/operator-ui/model.d.ts +12 -87
  117. package/dist/operator-ui/model.js +2 -9
  118. package/dist/operator-ui/setup-commands.js +1 -3
  119. package/dist/operator-ui/view.d.ts +323 -0
  120. package/dist/operator-ui/view.js +62 -189
  121. package/dist/page-styles.js +2 -3
  122. package/dist/program-source.js +14 -2
  123. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  124. package/dist/provider.d.ts +142 -0
  125. package/dist/provider.js +187 -0
  126. package/dist/providers/_shared/google/result-size.js +57 -0
  127. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  128. package/dist/providers/_shared/google/service-account.js +500 -0
  129. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  130. package/dist/providers/_shared/google/workspace.js +518 -0
  131. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  132. package/dist/providers/basecamp/index.js +997 -0
  133. package/dist/providers/basecamp/skill.generated.js +9 -0
  134. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  135. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  136. package/dist/providers/breeze/skill.generated.js +9 -0
  137. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  138. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  139. package/dist/providers/ccb/skill.generated.js +9 -0
  140. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  141. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  142. package/dist/providers/cloudflare/skill.generated.js +10 -0
  143. package/dist/providers/docs/index.d.ts +54 -0
  144. package/dist/providers/docs/index.js +1237 -0
  145. package/dist/providers/docs/skill.generated.js +9 -0
  146. package/dist/providers/drive/index.d.ts +53 -0
  147. package/dist/providers/drive/index.js +1523 -0
  148. package/dist/providers/drive/skill.generated.js +9 -0
  149. package/dist/providers/forms/index.d.ts +54 -0
  150. package/dist/providers/forms/index.js +1217 -0
  151. package/dist/providers/forms/skill.generated.js +11 -0
  152. package/dist/providers/github/auth.d.ts +32 -0
  153. package/dist/providers/github/auth.js +361 -0
  154. package/dist/providers/github/hosted.js +218 -0
  155. package/dist/providers/github/index.d.ts +13 -0
  156. package/dist/providers/github/index.js +140 -0
  157. package/dist/providers/github/key.js +100 -0
  158. package/dist/providers/github/rest.js +321 -0
  159. package/dist/providers/github/scope.d.ts +29 -0
  160. package/dist/providers/github/scope.js +124 -0
  161. package/dist/providers/github/skill.generated.js +8 -0
  162. package/dist/providers/gmail/index.d.ts +51 -0
  163. package/dist/providers/gmail/index.js +1436 -0
  164. package/dist/providers/gmail/skill.generated.js +9 -0
  165. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  166. package/dist/providers/linear/index.js +352 -0
  167. package/dist/providers/linear/skill.generated.js +8 -0
  168. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  169. package/dist/providers/mixpanel/index.js +416 -0
  170. package/dist/providers/mixpanel/skill.generated.js +9 -0
  171. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  172. package/dist/providers/{notion.js → notion/index.js} +226 -239
  173. package/dist/providers/notion/skill.generated.js +10 -0
  174. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  175. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  176. package/dist/providers/overflow/skill.generated.js +8 -0
  177. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  178. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  179. package/dist/providers/planning-center/skill.generated.js +10 -0
  180. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  181. package/dist/providers/revenuecat/index.js +557 -0
  182. package/dist/providers/revenuecat/skill.generated.js +11 -0
  183. package/dist/providers/sheets/index.d.ts +54 -0
  184. package/dist/providers/sheets/index.js +1237 -0
  185. package/dist/providers/sheets/skill.generated.js +10 -0
  186. package/dist/providers/slides/index.d.ts +53 -0
  187. package/dist/providers/slides/index.js +2645 -0
  188. package/dist/providers/slides/skill.generated.js +9 -0
  189. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  190. package/dist/providers/stripe/index.js +266 -0
  191. package/dist/providers/stripe/skill.generated.js +11 -0
  192. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  193. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  194. package/dist/providers/tithely/skill.generated.js +8 -0
  195. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  196. package/dist/providers/vercel/index.js +1038 -0
  197. package/dist/providers/vercel/mcp-ownership.js +212 -0
  198. package/dist/providers/vercel/skill.generated.js +11 -0
  199. package/dist/registry.d.ts +108 -171
  200. package/dist/registry.js +513 -925
  201. package/dist/request-state.js +21 -0
  202. package/dist/resource-read.js +100 -0
  203. package/dist/result-shapes.js +17 -49
  204. package/dist/retired-toolkits.js +1 -1
  205. package/dist/routes/access-tokens.js +3 -6
  206. package/dist/routes/activity-context.js +21 -0
  207. package/dist/routes/activity-disclosure.js +40 -0
  208. package/dist/routes/activity.js +59 -21
  209. package/dist/routes/actor-labels.js +3 -50
  210. package/dist/routes/connect.js +221 -0
  211. package/dist/routes/credentials.js +15 -27
  212. package/dist/routes/mcp.js +356 -130
  213. package/dist/routes/oauth-client-metadata.js +15 -0
  214. package/dist/routes/oauth-management.js +45 -107
  215. package/dist/routes/oauth.js +110 -57
  216. package/dist/routes/operator.js +3 -3
  217. package/dist/routes/shared.d.ts +25 -64
  218. package/dist/routes/shared.js +160 -80
  219. package/dist/routes/skills.js +30 -0
  220. package/dist/routes/ui-config.js +225 -0
  221. package/dist/routes/ui-data.js +40 -35
  222. package/dist/routes/ui.js +66 -56
  223. package/dist/runtime/admission.js +4 -2
  224. package/dist/runtime/call-admission.js +3 -1
  225. package/dist/runtime/connector-scope.js +1 -4
  226. package/dist/runtime/run.js +10 -39
  227. package/dist/runtime/services.js +12 -117
  228. package/dist/runtime/shared-read.js +1 -3
  229. package/dist/runtime/storage.js +8 -1
  230. package/dist/sent-secrets.d.ts +52 -0
  231. package/dist/sent-secrets.js +474 -0
  232. package/dist/server.js +28 -71
  233. package/dist/skills.js +406 -174
  234. package/dist/sqlite.d.ts +48 -0
  235. package/dist/sqlite.js +166 -0
  236. package/dist/storage/keys.js +428 -0
  237. package/dist/storage/memory.js +7 -0
  238. package/dist/storage/sql.d.ts +66 -0
  239. package/dist/storage/sql.js +534 -0
  240. package/dist/timeout.js +4 -2
  241. package/dist/tool-name.js +17 -0
  242. package/dist/tool-safety.d.ts +9 -35
  243. package/dist/tool-safety.js +20 -40
  244. package/dist/types.d.ts +320 -54
  245. package/dist/types.js +0 -1
  246. package/dist/ui.d.ts +7 -19
  247. package/dist/ui.js +76 -90
  248. package/dist/url-safety.js +3 -6
  249. package/dist/usage-guide.js +137 -0
  250. package/dist/validate.d.ts +7 -0
  251. package/dist/validate.js +309 -47
  252. package/dist/version.d.ts +1 -7
  253. package/dist/version.js +3 -7
  254. package/dist/worker.d.ts +11 -0
  255. package/dist/worker.js +434 -0
  256. package/documentation/architecture.md +664 -231
  257. package/documentation/auth.md +936 -546
  258. package/documentation/code-mode.md +501 -312
  259. package/documentation/meta-tools.md +449 -200
  260. package/documentation/operator-tests.md +93 -0
  261. package/documentation/provider-migration-0.29.md +60 -0
  262. package/examples/worker/AGENTS.md +34 -12
  263. package/examples/worker/README.md +286 -255
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  266. package/examples/worker/src/connecta.config.ts +151 -0
  267. package/examples/worker/src/index.ts +14 -197
  268. package/examples/worker/wrangler.jsonc +15 -35
  269. package/package.json +120 -54
  270. package/templates/node/.dockerignore +1 -2
  271. package/templates/node/.env.example +20 -21
  272. package/templates/node/AGENTS.md +21 -17
  273. package/templates/node/Dockerfile +3 -5
  274. package/templates/node/README.md +133 -58
  275. package/templates/node/docker-compose.yml +11 -12
  276. package/templates/node/package.json +4 -2
  277. package/templates/node/src/connecta.config.ts +119 -0
  278. package/templates/node/src/index.ts +9 -154
  279. package/templates/node/src/provision-token.ts +17 -0
  280. package/AGENTS.md +0 -170
  281. package/dist/artifacts/connector.d.ts +0 -58
  282. package/dist/artifacts/connector.js +0 -730
  283. package/dist/artifacts/document.js +0 -86
  284. package/dist/artifacts/guide.js +0 -140
  285. package/dist/artifacts/html-scan.js +0 -329
  286. package/dist/artifacts/json.js +0 -78
  287. package/dist/artifacts/kv-store.d.ts +0 -20
  288. package/dist/artifacts/kv-store.js +0 -140
  289. package/dist/artifacts/markdown.js +0 -478
  290. package/dist/artifacts/module.d.ts +0 -37
  291. package/dist/artifacts/module.js +0 -66
  292. package/dist/artifacts/operations.d.ts +0 -197
  293. package/dist/artifacts/operations.js +0 -1002
  294. package/dist/artifacts/refresh.d.ts +0 -55
  295. package/dist/artifacts/refresh.js +0 -236
  296. package/dist/artifacts/routes.js +0 -206
  297. package/dist/artifacts/types.d.ts +0 -214
  298. package/dist/artifacts/types.js +0 -22
  299. package/dist/artifacts/validate.d.ts +0 -62
  300. package/dist/artifacts/validate.js +0 -651
  301. package/dist/artifacts.d.ts +0 -6
  302. package/dist/artifacts.js +0 -9
  303. package/dist/auth/bearer.d.ts +0 -11
  304. package/dist/auth/bearer.js +0 -50
  305. package/dist/providers/basecamp.js +0 -445
  306. package/dist/providers/linear.js +0 -217
  307. package/dist/providers/mixpanel.js +0 -247
  308. package/dist/providers/revenuecat.js +0 -317
  309. package/dist/providers/stripe.js +0 -253
  310. package/dist/providers/vercel.js +0 -1253
  311. package/dist/storage/file.d.ts +0 -15
  312. package/dist/storage/file.js +0 -455
  313. package/ethos.md +0 -93
  314. package/examples/worker/src/cloudflare-kv.ts +0 -43
  315. package/examples/worker/src/d1-activity-row.ts +0 -155
  316. package/examples/worker/src/d1-activity.ts +0 -147
  317. package/examples/worker/src/d1-storage.ts +0 -138
  318. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  319. package/templates/node/src/file-activity.ts +0 -182
@@ -0,0 +1,21 @@
1
+ import { ProtocolError, INVALID_PARAMS } from "@modelcontextprotocol/server";
2
+ export const REQUEST_STATE_TTL_MS = 10 * 60_000;
3
+ export const MAX_INPUT_ROUNDS = 3;
4
+ export function stateObject(value) {
5
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ export function invalidRequestState() {
8
+ throw new ProtocolError(INVALID_PARAMS, "Invalid or expired requestState", { reason: "invalid_request_state" });
9
+ }
10
+ /** Object order is immaterial; array order and every submitted value are bound. */
11
+ export function canonicalState(value) {
12
+ return JSON.stringify(value, (_key, item) => stateObject(item)
13
+ ? Object.fromEntries(Object.keys(item)
14
+ .sort()
15
+ .map((key) => [key, item[key]]))
16
+ : item);
17
+ }
18
+ export async function requestDigest(tool, args) {
19
+ const bytes = new TextEncoder().encode(canonicalState({ tool, args }));
20
+ return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
21
+ }
@@ -0,0 +1,100 @@
1
+ import { Effect } from "effect";
2
+ import { classifyCallError, ConnectorCallError } from "./errors.js";
3
+ import { InvocationFailure, timed } from "./invocation.js";
4
+ import { withDeadlineEffect } from "./runtime/run.js";
5
+ import { sentSecretsFor } from "./sent-secrets.js";
6
+ import { hasControlCharacters } from "./tool-name.js";
7
+ const encoder = new TextEncoder();
8
+ const URI_HINT = "Use resource://<connectorId>/<encodeURIComponent(downstreamUri)> with a whole-connector grant.";
9
+ /** Parse once without URL normalization or interpreting the downstream URI. */
10
+ function resourceTarget(registry, raw) {
11
+ const invalid = () => new ConnectorCallError("invalid_args", `Invalid resource URI. ${URI_HINT}`);
12
+ if (typeof raw !== "string" || raw.length > 8192 || encoder.encode(raw).length > 8192)
13
+ throw invalid();
14
+ const match = /^resource:\/\/([a-z0-9_-]+)\/(.+)$/.exec(raw);
15
+ if (!match)
16
+ throw invalid();
17
+ let uri;
18
+ try {
19
+ uri = decodeURIComponent(match[2]);
20
+ }
21
+ catch {
22
+ throw invalid();
23
+ }
24
+ if (encodeURIComponent(uri) !== match[2] || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(uri) || hasControlCharacters(uri))
25
+ throw invalid();
26
+ const connector = registry.getResourceConnector(match[1]);
27
+ if (!connector?.readResource) {
28
+ throw new InvocationFailure({
29
+ code: "unknown_address",
30
+ message: `No resource reader is available for this address. ${URI_HINT}`,
31
+ retryable: false,
32
+ });
33
+ }
34
+ return { connector, uri };
35
+ }
36
+ /** A protocol read, under the same request scope, call permit and deadline as tools. */
37
+ export function readResource(registry, raw, options) {
38
+ let connector;
39
+ return withDeadlineEffect((signal) => Effect.scoped(Effect.gen(function* () {
40
+ const target = yield* Effect.try({ try: () => resourceTarget(registry, raw), catch: (error) => error });
41
+ connector = target.connector;
42
+ // Await this Promise in the reader's request context, as invocation.ts does.
43
+ yield* Effect.acquireRelease(Effect.suspend(() => {
44
+ const pending = registry.admitCall(target.connector.id, {
45
+ toolName: "resources/read",
46
+ args: { uri: target.uri },
47
+ signal,
48
+ });
49
+ return Effect.tryPromise({ try: () => pending, catch: (error) => error }).pipe(Effect.onInterrupt(() => Effect.sync(() => {
50
+ pending.then((permit) => permit.release(), () => { });
51
+ })));
52
+ }), (permit) => Effect.sync(() => permit.release()), { interruptible: true });
53
+ return yield* timed((elapsed) => options.onConnectorTime?.(elapsed), Effect.tryPromise({
54
+ try: () => {
55
+ if (signal.aborted)
56
+ throw signal.reason;
57
+ const ctx = registry.contextFor(target.connector.id, options.baseUrl, options.requestScope, {
58
+ signal,
59
+ timeoutMs: options.timeoutMs,
60
+ });
61
+ options.sentSecrets.include(sentSecretsFor(ctx));
62
+ if (target.connector.credential && !ctx.credential) {
63
+ throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured.");
64
+ }
65
+ return target.connector.readResource(target.uri, ctx);
66
+ },
67
+ catch: (error) => error,
68
+ }));
69
+ })), {
70
+ timeoutMs: options.timeoutMs,
71
+ ...(options.signal ? { signal: options.signal } : {}),
72
+ timeoutError: new ConnectorCallError("timeout", `Resource read timed out after ${options.timeoutMs}ms.`),
73
+ }).pipe(Effect.catch((error) => {
74
+ let details = options.signal?.aborted
75
+ ? { code: "cancelled", message: "Resource read was cancelled because the run ended.", retryable: false }
76
+ : error instanceof InvocationFailure
77
+ ? error.details
78
+ : classifyCallError(options.sentSecrets.redact(error));
79
+ if (connector && (details.code === "auth_required" || details.code === "downstream_oauth_required")) {
80
+ details = {
81
+ ...details,
82
+ connector: connector.id,
83
+ recovery: connector.startAuth
84
+ ? "oauth"
85
+ : registry.credentialUiAvailable() &&
86
+ connector.credential &&
87
+ registry.contextFor(connector.id, options.baseUrl, options.requestScope).credential
88
+ ? "operator_config"
89
+ : "unavailable",
90
+ nextAction: {
91
+ tool: "authorize_connector",
92
+ arguments: { connector: connector.id },
93
+ operatorHandoff: "Give the URL and instructions it returns to the operator.",
94
+ },
95
+ retry: "Retry connecta.read with the same resource URI after the operator completes recovery.",
96
+ };
97
+ }
98
+ return Effect.fail(new InvocationFailure(options.sentSecrets.redact(details)));
99
+ }));
100
+ }
@@ -1,4 +1,3 @@
1
- import { isExplicitlyReadOnly } from "./tool-safety.js";
2
1
  const OBSERVATION_TTL_MS = 24 * 60 * 60 * 1000;
3
2
  const MAX_CACHED_SHAPES = 256;
4
3
  const MAX_DEFINITION_BYTES = 64 * 1024;
@@ -8,11 +7,7 @@ const MAX_SCHEMA_NODES = 128;
8
7
  const MAX_OBJECT_PROPERTIES = 48;
9
8
  const MAX_ARRAY_ITEMS = 32;
10
9
  const MAX_PROPERTY_NAME_BYTES = 128;
11
- const UNSAFE_PROPERTY_NAMES = new Set([
12
- "__proto__",
13
- "constructor",
14
- "prototype",
15
- ]);
10
+ const UNSAFE_PROPERTY_NAMES = new Set(["__proto__", "constructor", "prototype"]);
16
11
  const encoder = new TextEncoder();
17
12
  function broadType(value) {
18
13
  if (value === null)
@@ -34,9 +29,7 @@ function broadType(value) {
34
29
  function serializedSchema(schema) {
35
30
  try {
36
31
  const text = JSON.stringify(schema);
37
- return encoder.encode(text).byteLength <= MAX_SCHEMA_BYTES
38
- ? text
39
- : undefined;
32
+ return encoder.encode(text).byteLength <= MAX_SCHEMA_BYTES ? text : undefined;
40
33
  }
41
34
  catch {
42
35
  return undefined;
@@ -56,9 +49,7 @@ function cloneSchema(schema) {
56
49
  function definitionIdentity(definition) {
57
50
  try {
58
51
  const serialized = JSON.stringify(definition);
59
- return encoder.encode(serialized).byteLength <= MAX_DEFINITION_BYTES
60
- ? serialized
61
- : undefined;
52
+ return encoder.encode(serialized).byteLength <= MAX_DEFINITION_BYTES ? serialized : undefined;
62
53
  }
63
54
  catch {
64
55
  return undefined;
@@ -68,8 +59,7 @@ function schemaType(schema) {
68
59
  return typeof schema.type === "string" ? schema.type : undefined;
69
60
  }
70
61
  function safePropertyName(name) {
71
- return (!UNSAFE_PROPERTY_NAMES.has(name) &&
72
- encoder.encode(name).byteLength <= MAX_PROPERTY_NAME_BYTES);
62
+ return !UNSAFE_PROPERTY_NAMES.has(name) && encoder.encode(name).byteLength <= MAX_PROPERTY_NAME_BYTES;
73
63
  }
74
64
  function boundedPropertyNames(value) {
75
65
  const names = [];
@@ -83,9 +73,7 @@ function boundedPropertyNames(value) {
83
73
  return names.sort();
84
74
  }
85
75
  function unionBranches(schema) {
86
- return Array.isArray(schema.anyOf)
87
- ? schema.anyOf
88
- : [schema];
76
+ return Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
89
77
  }
90
78
  function branchOrder(schema) {
91
79
  return ["null", "boolean", "number", "string", "array", "object"].indexOf(schemaType(schema) ?? "");
@@ -94,40 +82,25 @@ function mergeSchemas(left, right) {
94
82
  const leftType = schemaType(left);
95
83
  const rightType = schemaType(right);
96
84
  if (leftType === "object" && rightType === "object") {
97
- const leftProperties = left.properties && typeof left.properties === "object"
98
- ? left.properties
99
- : {};
100
- const rightProperties = right.properties && typeof right.properties === "object"
101
- ? right.properties
102
- : {};
85
+ const leftProperties = left.properties && typeof left.properties === "object" ? left.properties : {};
86
+ const rightProperties = right.properties && typeof right.properties === "object" ? right.properties : {};
103
87
  const properties = Object.create(null);
104
- for (const key of [...new Set([
105
- ...Object.keys(leftProperties),
106
- ...Object.keys(rightProperties),
107
- ])].sort()) {
88
+ for (const key of [...new Set([...Object.keys(leftProperties), ...Object.keys(rightProperties)])].sort()) {
108
89
  const leftProperty = leftProperties[key];
109
90
  const rightProperty = rightProperties[key];
110
91
  properties[key] =
111
- leftProperty && rightProperty
112
- ? mergeSchemas(leftProperty, rightProperty)
113
- : (leftProperty ?? rightProperty);
92
+ leftProperty && rightProperty ? mergeSchemas(leftProperty, rightProperty) : (leftProperty ?? rightProperty);
114
93
  }
115
94
  return { type: "object", properties };
116
95
  }
117
96
  if (leftType === "array" && rightType === "array") {
118
- const leftItems = left.items && typeof left.items === "object"
119
- ? left.items
120
- : undefined;
121
- const rightItems = right.items && typeof right.items === "object"
122
- ? right.items
123
- : undefined;
97
+ const leftItems = left.items && typeof left.items === "object" ? left.items : undefined;
98
+ const rightItems = right.items && typeof right.items === "object" ? right.items : undefined;
124
99
  return {
125
100
  type: "array",
126
101
  ...(leftItems || rightItems
127
102
  ? {
128
- items: leftItems && rightItems
129
- ? mergeSchemas(leftItems, rightItems)
130
- : (leftItems ?? rightItems),
103
+ items: leftItems && rightItems ? mergeSchemas(leftItems, rightItems) : (leftItems ?? rightItems),
131
104
  }
132
105
  : {}),
133
106
  };
@@ -212,8 +185,7 @@ function boundSchema(schema, budget, depth = 0) {
212
185
  const properties = Object.create(null);
213
186
  let included = 0;
214
187
  for (const key of Object.keys(source).sort()) {
215
- if (included >= MAX_OBJECT_PROPERTIES ||
216
- !safePropertyName(key)) {
188
+ if (included >= MAX_OBJECT_PROPERTIES || !safePropertyName(key)) {
217
189
  continue;
218
190
  }
219
191
  const property = boundSchema(source[key], budget, depth + 1);
@@ -231,9 +203,7 @@ function boundSchema(schema, budget, depth = 0) {
231
203
  function observedSchema(value) {
232
204
  try {
233
205
  const inferred = inferSchema(value, { nodes: 0, seen: new WeakSet() });
234
- const schema = inferred
235
- ? boundSchema(inferred, { nodes: 0 })
236
- : undefined;
206
+ const schema = inferred ? boundSchema(inferred, { nodes: 0 }) : undefined;
237
207
  return schema && serializedSchema(schema) ? schema : undefined;
238
208
  }
239
209
  catch {
@@ -267,7 +237,7 @@ export class ObservedOutputSchemas {
267
237
  this.entries.delete(key);
268
238
  return undefined;
269
239
  }
270
- if (!isExplicitlyReadOnly(definition)) {
240
+ if (definition.classification !== "read") {
271
241
  this.entries.delete(key);
272
242
  return undefined;
273
243
  }
@@ -298,7 +268,7 @@ export class ObservedOutputSchemas {
298
268
  this.entries.delete(key);
299
269
  return;
300
270
  }
301
- if (!isExplicitlyReadOnly(definition)) {
271
+ if (definition.classification !== "read") {
302
272
  this.entries.delete(key);
303
273
  return;
304
274
  }
@@ -311,9 +281,7 @@ export class ObservedOutputSchemas {
311
281
  if (!inferred)
312
282
  return;
313
283
  const current = this.entries.get(key);
314
- const merged = boundSchema(current &&
315
- current.definition === identity &&
316
- current.expiresAt > Date.now()
284
+ const merged = boundSchema(current && current.definition === identity && current.expiresAt > Date.now()
317
285
  ? mergeSchemas(current.schema, inferred)
318
286
  : inferred, { nodes: 0 });
319
287
  if (!merged || !serializedSchema(merged))
@@ -4,5 +4,5 @@ export function assertNoRetiredToolkitOptions(source, options) {
4
4
  if (!hasOwn(options, "toolkits") && !hasOwn(options, "unscoped"))
5
5
  return;
6
6
  throw new Error(`${source} options \`toolkits\` and \`unscoped\` were removed in issue ` +
7
- "#178. Deploy one connecta instance per audience instead; see ethos.md.");
7
+ "#178. Deploy one connecta instance per audience instead; see PRINCIPLES.md.");
8
8
  }
@@ -1,9 +1,6 @@
1
- import { authorizeUiIdentity, isSameOrigin, privateJson, } from "./shared.js";
1
+ import { authorizeUiIdentity, isSameOrigin, privateJson } from "./shared.js";
2
2
  async function readName(request) {
3
- if (!request.headers
4
- .get("content-type")
5
- ?.toLowerCase()
6
- .startsWith("application/json")) {
3
+ if (!request.headers.get("content-type")?.toLowerCase().startsWith("application/json")) {
7
4
  return {
8
5
  ok: false,
9
6
  response: privateJson({ error: "Content-Type must be application/json" }, { status: 415 }),
@@ -70,7 +67,7 @@ export async function routeAccessTokens(context, manager) {
70
67
  if (mutating && !isSameOrigin(request, baseUrl)) {
71
68
  return privateJson({ error: "same-origin request required" }, { status: 403 });
72
69
  }
73
- const admin = await authorizeUiIdentity(request, baseUrl, opts.auth, "access token management", context.runtimeContext, opts.identity);
70
+ const admin = await authorizeUiIdentity(request, baseUrl, opts.config.auth, "access token management", context.runtimeContext, opts.config.identity);
74
71
  if (!admin.ok)
75
72
  return admin.response;
76
73
  if (!admin.accessTokenManagement || !admin.identity.principal) {
@@ -0,0 +1,21 @@
1
+ /** Attribute catalog refreshes initiated by an admitted operator request. */
2
+ export function operatorActivityContext(context, authz) {
3
+ const { opts, defer } = context;
4
+ const activity = opts.config.activity;
5
+ if (!activity)
6
+ return undefined;
7
+ const principal = authz.identity.principal;
8
+ return {
9
+ sink: activity.store,
10
+ recordTool: activity.recordTool,
11
+ actor: authz.actor,
12
+ ...(principal
13
+ ? { principalActor: { kind: authz.actor.kind, id: principal.id, namespace: principal.namespace } }
14
+ : {}),
15
+ requestId: crypto.randomUUID(),
16
+ serverInfo: opts.config.serverInfo,
17
+ ...(activity.deploymentId ? { deploymentId: activity.deploymentId } : {}),
18
+ ...(defer ? { defer } : {}),
19
+ logger: opts.config.logger,
20
+ };
21
+ }
@@ -0,0 +1,40 @@
1
+ import { intersectAccess } from "../connector-access.js";
2
+ import { recordedToolName } from "../operator-record.js";
3
+ /** Historical disclosure uses the verdict at call time; old guarded rows fail closed. */
4
+ function activityToolVisible(authz, event) {
5
+ const allowed = authz.toolAccess?.get(event.connectorId);
6
+ return (recordedToolName({ name: event.toolName }) === event.toolName &&
7
+ (!allowed || allowed.has(event.toolName)) &&
8
+ (!authz.guardedToolAccess?.get(event.connectorId)?.has(event.toolName) || event.classification === "read"));
9
+ }
10
+ /**
11
+ * One disclosure rule for the timeline and the UI's last-call overlay.
12
+ * After Activity access and the read gate, operators and activityAccess machine
13
+ * callers may see other principals' shared-connector history when connector/tool
14
+ * and recorded-pool grants permit, including request IDs, client name/version,
15
+ * and actor kind/id/namespace. Interactive reads may add directory actor labels;
16
+ * machine reads do not. Personal history requires a matching principal owner.
17
+ */
18
+ export function activityEventVisible(context, authz, registry, admittedPools, event) {
19
+ const connector = registry.getConnector(event.connectorId);
20
+ if (!connector || (event.pool !== undefined && !admittedPools.has(event.pool)))
21
+ return false;
22
+ if (connector.authScope === "personal" || (event.kind === "catalog_drift" && event.actorBasis === "principal")) {
23
+ const owner = authz.identity.principal;
24
+ if (!owner ||
25
+ event.actorBasis !== "principal" ||
26
+ event.actor.id !== owner.id ||
27
+ event.actor.namespace !== owner.namespace)
28
+ return false;
29
+ }
30
+ const pool = event.pool ? context.opts.pools.get(event.pool) : undefined;
31
+ if (event.pool && !pool)
32
+ return false;
33
+ const access = pool ? intersectAccess(authz, pool.access) : authz;
34
+ if (access.connectorIds !== "all" && !access.connectorIds.includes(event.connectorId))
35
+ return false;
36
+ // Connector-wide counts reveal tools outside an address-only grant.
37
+ if (event.kind === "catalog_drift")
38
+ return !access.toolAccess?.has(event.connectorId);
39
+ return activityToolVisible(access, event);
40
+ }
@@ -1,7 +1,9 @@
1
+ import { activityEventVisible } from "./activity-disclosure.js";
1
2
  import { Duration, Effect } from "effect";
2
- import { InvalidActivityCursorError, } from "../activity.js";
3
+ import { activityBehaviorFacts, activityPackageVersion, activityClientFact, InvalidActivityCursorError, } from "../activity.js";
4
+ import { failureRecord, logFailure } from "../operator-record.js";
3
5
  import { ACTOR_LABEL_BUDGET_MS, ACTOR_LABEL_CONCURRENCY, actorKey, cleanActorLabel, labelLookups, } from "./actor-labels.js";
4
- import { authorized, refuse, serveOperator } from "./operator.js";
6
+ import { authorized, refuse, serveOperator, visibleRegistry } from "./operator.js";
5
7
  import { privateJson } from "./shared.js";
6
8
  /**
7
9
  * Add display-only actor labels to one authorized activity page. Resolution is
@@ -25,20 +27,25 @@ function enrichActivityActorLabels(page, auth) {
25
27
  }).pipe(Effect.timeoutOption(Duration.millis(ACTOR_LABEL_BUDGET_MS)), Effect.map(() => ({
26
28
  ...page,
27
29
  events: page.events.map((event) => {
28
- const resolved = event.actor.id
29
- ? labels.get(actorKey(event.actor))
30
- : undefined;
30
+ const resolved = event.actor.id ? labels.get(actorKey(event.actor)) : undefined;
31
31
  // Never trust or echo a `label` supplied by storage. The persisted event
32
32
  // schema has no label; only this authenticated read path may add one.
33
33
  const actor = {
34
34
  kind: event.actor.kind,
35
35
  ...(event.actor.id ? { id: event.actor.id } : {}),
36
- ...(event.actor.namespace
37
- ? { namespace: event.actor.namespace }
38
- : {}),
36
+ ...(event.actor.namespace ? { namespace: event.actor.namespace } : {}),
39
37
  };
38
+ // Re-check stored client facts, including custom readers and old rows.
39
+ const { packageVersion: suppliedPackageVersion, clientName: suppliedName, clientVersion: suppliedVersion, classification: _classification, resultBytes: _resultBytes, kind: _kind, drift: _drift, pool: _pool, actorBasis: _actorBasis, ...record } = event;
40
+ const packageVersion = activityPackageVersion(suppliedPackageVersion);
41
+ const clientName = activityClientFact(suppliedName, "name");
42
+ const clientVersion = activityClientFact(suppliedVersion, "version");
40
43
  return {
41
- ...event,
44
+ ...record,
45
+ ...activityBehaviorFacts(event),
46
+ ...(packageVersion !== undefined ? { packageVersion } : {}),
47
+ ...(clientName !== undefined ? { clientName } : {}),
48
+ ...(clientVersion !== undefined ? { clientVersion } : {}),
42
49
  actor: resolved ? { ...actor, label: resolved } : actor,
43
50
  };
44
51
  }),
@@ -48,41 +55,72 @@ function enrichActivityActorLabels(page, auth) {
48
55
  function activityRead(context) {
49
56
  const { url, opts } = context;
50
57
  return Effect.gen(function* () {
51
- const authz = yield* authorized(context, false);
58
+ const authz = yield* authorized(context);
59
+ const registry = yield* visibleRegistry(context, authz);
52
60
  if (!authz.operator)
53
- return yield* refuse("operator access required", 403);
54
- if (opts.activityReadGate &&
55
- !(yield* Effect.promise(async () => opts.activityReadGate(authz.actor)))) {
61
+ return yield* refuse("activity access required", 403);
62
+ if (opts.config.activity?.readGate &&
63
+ !(yield* Effect.promise(async () => opts.config.activity?.readGate(authz.actor)))) {
56
64
  return yield* refuse("forbidden", 403);
57
65
  }
58
- const list = opts.activity?.list?.bind(opts.activity);
66
+ const list = opts.config.activity?.store.list?.bind(opts.config.activity?.store);
59
67
  if (!list)
60
68
  return yield* refuse("activity history is not configured", 404);
69
+ const admittedPools = new Set();
70
+ for (const [name, pool] of opts.pools) {
71
+ if (yield* Effect.promise(async () => {
72
+ try {
73
+ return (await pool.grant(authz.identity)) === true;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }))
79
+ admittedPools.add(name);
80
+ }
81
+ const visible = (event) => activityEventVisible(context, authz, registry, admittedPools, event);
61
82
  const cursor = url.searchParams.get("cursor") ?? undefined;
62
83
  if (cursor && cursor.length > 500)
63
84
  return yield* refuse("invalid cursor", 400);
64
85
  const requestedLimit = Number(url.searchParams.get("limit") ?? "50");
65
- const limit = Number.isFinite(requestedLimit)
66
- ? Math.min(100, Math.max(1, Math.trunc(requestedLimit)))
67
- : 50;
68
- return yield* Effect.tryPromise({
86
+ const limit = Number.isFinite(requestedLimit) ? Math.min(100, Math.max(1, Math.trunc(requestedLimit))) : 50;
87
+ const readPage = (cursor, limit) => Effect.tryPromise({
69
88
  try: () => list({ ...(cursor !== undefined ? { cursor } : {}), limit }),
70
89
  catch: (error) => error,
71
- }).pipe(Effect.flatMap((page) => enrichActivityActorLabels(page, opts.auth)), Effect.map((page) => privateJson(page)),
90
+ });
91
+ return yield* Effect.gen(function* () {
92
+ let page = yield* readPage(cursor, limit);
93
+ const events = page.events.filter(visible);
94
+ // Store cursors may encode their boundary row's timestamp and id. A
95
+ // rejected boundary must never reach the caller. Advance one row at a
96
+ // time until the boundary is visible or history is exhausted.
97
+ const seen = new Set();
98
+ let boundaryReads = 0;
99
+ while (page.nextCursor && (!page.events.length || !visible(page.events[page.events.length - 1]))) {
100
+ if (boundaryReads++ >= 1000)
101
+ throw new Error("activity disclosure scan exhausted");
102
+ if (seen.has(page.nextCursor))
103
+ throw new Error("activity cursor did not advance");
104
+ seen.add(page.nextCursor);
105
+ page = yield* readPage(page.nextCursor, 1);
106
+ events.push(...page.events.filter(visible));
107
+ }
108
+ return { events, ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}) };
109
+ }).pipe(Effect.flatMap((page) => enrichActivityActorLabels(page, authz.identity.interactive ? opts.config.auth : [])), Effect.map((page) => privateJson(page)),
72
110
  // A page too malformed to label or serialize is the store's failure,
73
111
  // like any other.
74
112
  Effect.catchDefect((defect) => Effect.fail(defect)), Effect.catch((error) => {
75
113
  if (error instanceof InvalidActivityCursorError) {
76
114
  return refuse(error.message, 400);
77
115
  }
78
- opts.logger.error("[connecta] activity read failed", error);
116
+ logFailure(opts.config.logger, "activity read failed", failureRecord({}, error), "error");
79
117
  return refuse("activity history is temporarily unavailable", 503);
80
118
  }));
81
119
  });
82
120
  }
83
121
  export async function routeActivity(context) {
84
122
  const { path, request } = context;
85
- if (path !== "/ui/activity")
123
+ if (path !== "/ui/activity" && path !== "/ui/api/activity")
86
124
  return null;
87
125
  if (request.method !== "GET") {
88
126
  return privateJson({ error: "method not allowed" }, { status: 405 });
@@ -1,5 +1,4 @@
1
- // Display labels for stored actors, shared by the activity page and the
2
- // artifact library. An actor is stored as a stable id; a label is resolved at
1
+ // Display labels for stored activity actors. An actor is stored as a stable id; a label is resolved at
3
2
  // read time from the one auth provider that owns the actor's directory, and
4
3
  // only on an authorized read path. Nothing here is persisted, and a label a
5
4
  // store happens to carry is never trusted or echoed.
@@ -43,11 +42,9 @@ export function labelLookups(actors, auth) {
43
42
  const eligible = identity.namespace
44
43
  ? candidates.filter(({ provider }) => activityActorNamespace(provider) === identity.namespace)
45
44
  : (() => {
46
- const directoryKey = ({ provider, index, }) => {
45
+ const directoryKey = ({ provider, index }) => {
47
46
  const namespace = activityActorNamespace(provider);
48
- return namespace === undefined
49
- ? `provider:${index}`
50
- : `namespace:${namespace}`;
47
+ return namespace === undefined ? `provider:${index}` : `namespace:${namespace}`;
51
48
  };
52
49
  // Every same-kind provider participates in the ambiguity check,
53
50
  // even if it cannot resolve labels. Otherwise a legacy ID owned
@@ -68,47 +65,3 @@ export function labelLookups(actors, auth) {
68
65
  }
69
66
  return lookups;
70
67
  }
71
- /**
72
- * Resolve labels for `actors`, best-effort: at most eight lookups at once,
73
- * 1.5 seconds in all, and whatever has resolved by then is the answer. A
74
- * provider outage yields no labels, never a failed read.
75
- */
76
- export async function resolveActorLabels(actors, auth) {
77
- const labels = new Map();
78
- const queue = labelLookups(actors, auth);
79
- let cursor = 0;
80
- let expired = false;
81
- const worker = async () => {
82
- while (!expired && cursor < queue.length) {
83
- const lookup = queue[cursor++];
84
- if (!lookup)
85
- return;
86
- try {
87
- const label = cleanActorLabel(await Promise.resolve(lookup.provider.activityActorLabel?.(lookup.id)));
88
- if (label && !expired)
89
- labels.set(lookup.key, label);
90
- }
91
- catch {
92
- // A failed lookup leaves the actor unlabeled.
93
- }
94
- }
95
- };
96
- let timer;
97
- const budget = new Promise((resolve) => {
98
- timer = setTimeout(() => {
99
- expired = true;
100
- resolve();
101
- }, ACTOR_LABEL_BUDGET_MS);
102
- });
103
- try {
104
- await Promise.race([
105
- Promise.all(Array.from({ length: Math.min(ACTOR_LABEL_CONCURRENCY, queue.length) }, worker)),
106
- budget,
107
- ]);
108
- }
109
- finally {
110
- if (timer !== undefined)
111
- clearTimeout(timer);
112
- }
113
- return new Map(labels);
114
- }