@zackbart/connecta 0.28.0 → 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 (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  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 +1632 -1509
  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 +526 -0
  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 +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. 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
- }