@zackbart/connecta 0.7.9 → 0.8.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 (199) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +47 -148
  3. package/dist/activity.d.ts +0 -8
  4. package/dist/activity.d.ts.map +1 -1
  5. package/dist/activity.js +0 -1
  6. package/dist/activity.js.map +1 -1
  7. package/dist/auth/bearer.d.ts +1 -7
  8. package/dist/auth/bearer.d.ts.map +1 -1
  9. package/dist/auth/bearer.js +3 -24
  10. package/dist/auth/bearer.js.map +1 -1
  11. package/dist/auth/clerk.d.ts +2 -11
  12. package/dist/auth/clerk.d.ts.map +1 -1
  13. package/dist/auth/clerk.js +31 -46
  14. package/dist/auth/clerk.js.map +1 -1
  15. package/dist/call-admission.d.ts.map +1 -1
  16. package/dist/call-admission.js +9 -4
  17. package/dist/call-admission.js.map +1 -1
  18. package/dist/catalog-fingerprint.d.ts +20 -0
  19. package/dist/catalog-fingerprint.d.ts.map +1 -0
  20. package/dist/catalog-fingerprint.js +34 -0
  21. package/dist/catalog-fingerprint.js.map +1 -0
  22. package/dist/catalog-limits.d.ts +10 -0
  23. package/dist/catalog-limits.d.ts.map +1 -0
  24. package/dist/catalog-limits.js +10 -0
  25. package/dist/catalog-limits.js.map +1 -0
  26. package/dist/catalog-service.d.ts +137 -0
  27. package/dist/catalog-service.d.ts.map +1 -0
  28. package/dist/catalog-service.js +401 -0
  29. package/dist/catalog-service.js.map +1 -0
  30. package/dist/catalog.d.ts +6 -0
  31. package/dist/catalog.d.ts.map +1 -1
  32. package/dist/catalog.js +53 -2
  33. package/dist/catalog.js.map +1 -1
  34. package/dist/concurrency.d.ts +4 -0
  35. package/dist/concurrency.d.ts.map +1 -0
  36. package/dist/concurrency.js +31 -0
  37. package/dist/concurrency.js.map +1 -0
  38. package/dist/connectors/api.d.ts.map +1 -1
  39. package/dist/connectors/api.js +22 -12
  40. package/dist/connectors/api.js.map +1 -1
  41. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  42. package/dist/connectors/remote-mcp.js +53 -41
  43. package/dist/connectors/remote-mcp.js.map +1 -1
  44. package/dist/credentials.d.ts +3 -2
  45. package/dist/credentials.d.ts.map +1 -1
  46. package/dist/errors.d.ts +19 -1
  47. package/dist/errors.d.ts.map +1 -1
  48. package/dist/errors.js.map +1 -1
  49. package/dist/execute.d.ts +10 -16
  50. package/dist/execute.d.ts.map +1 -1
  51. package/dist/execute.js +143 -330
  52. package/dist/execute.js.map +1 -1
  53. package/dist/executor-result.d.ts +0 -3
  54. package/dist/executor-result.d.ts.map +1 -1
  55. package/dist/executor-result.js +3 -3
  56. package/dist/executor-result.js.map +1 -1
  57. package/dist/executors/quickjs-child.js +3 -3
  58. package/dist/executors/quickjs-child.js.map +1 -1
  59. package/dist/executors/quickjs-protocol.d.ts +4 -1
  60. package/dist/executors/quickjs-protocol.d.ts.map +1 -1
  61. package/dist/executors/quickjs-runtime.d.ts.map +1 -1
  62. package/dist/executors/quickjs-runtime.js +7 -2
  63. package/dist/executors/quickjs-runtime.js.map +1 -1
  64. package/dist/executors/quickjs.d.ts.map +1 -1
  65. package/dist/executors/quickjs.js +4 -1
  66. package/dist/executors/quickjs.js.map +1 -1
  67. package/dist/favicon.js +2 -2
  68. package/dist/favicon.js.map +1 -1
  69. package/dist/index.d.ts +8 -82
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +58 -104
  72. package/dist/index.js.map +1 -1
  73. package/dist/invocation.d.ts +101 -0
  74. package/dist/invocation.d.ts.map +1 -0
  75. package/dist/invocation.js +405 -0
  76. package/dist/invocation.js.map +1 -0
  77. package/dist/meta-tools.d.ts +9 -95
  78. package/dist/meta-tools.d.ts.map +1 -1
  79. package/dist/meta-tools.js +235 -725
  80. package/dist/meta-tools.js.map +1 -1
  81. package/dist/operator-ui/generated.d.ts +3 -0
  82. package/dist/operator-ui/generated.d.ts.map +1 -0
  83. package/dist/operator-ui/generated.js +5 -0
  84. package/dist/operator-ui/generated.js.map +1 -0
  85. package/dist/operator-ui/model.d.ts +73 -0
  86. package/dist/operator-ui/model.d.ts.map +1 -0
  87. package/dist/operator-ui/model.js +28 -0
  88. package/dist/operator-ui/model.js.map +1 -0
  89. package/dist/registry.d.ts +33 -184
  90. package/dist/registry.d.ts.map +1 -1
  91. package/dist/registry.js +345 -330
  92. package/dist/registry.js.map +1 -1
  93. package/dist/retired-toolkits.d.ts +3 -0
  94. package/dist/retired-toolkits.d.ts.map +1 -0
  95. package/dist/retired-toolkits.js +9 -0
  96. package/dist/retired-toolkits.js.map +1 -0
  97. package/dist/routes/activity.d.ts +3 -0
  98. package/dist/routes/activity.d.ts.map +1 -0
  99. package/dist/routes/activity.js +170 -0
  100. package/dist/routes/activity.js.map +1 -0
  101. package/dist/routes/credentials.d.ts +3 -0
  102. package/dist/routes/credentials.d.ts.map +1 -0
  103. package/dist/routes/credentials.js +170 -0
  104. package/dist/routes/credentials.js.map +1 -0
  105. package/dist/routes/mcp.d.ts +8 -0
  106. package/dist/routes/mcp.d.ts.map +1 -0
  107. package/dist/routes/mcp.js +270 -0
  108. package/dist/routes/mcp.js.map +1 -0
  109. package/dist/routes/oauth.d.ts +4 -0
  110. package/dist/routes/oauth.d.ts.map +1 -0
  111. package/dist/routes/oauth.js +263 -0
  112. package/dist/routes/oauth.js.map +1 -0
  113. package/dist/routes/shared.d.ts +76 -0
  114. package/dist/routes/shared.d.ts.map +1 -0
  115. package/dist/routes/shared.js +140 -0
  116. package/dist/routes/shared.js.map +1 -0
  117. package/dist/routes/ui.d.ts +3 -0
  118. package/dist/routes/ui.d.ts.map +1 -0
  119. package/dist/routes/ui.js +116 -0
  120. package/dist/routes/ui.js.map +1 -0
  121. package/dist/server.d.ts +9 -40
  122. package/dist/server.d.ts.map +1 -1
  123. package/dist/server.js +62 -1322
  124. package/dist/server.js.map +1 -1
  125. package/dist/skills.d.ts +3 -21
  126. package/dist/skills.d.ts.map +1 -1
  127. package/dist/skills.js +15 -43
  128. package/dist/skills.js.map +1 -1
  129. package/dist/storage/file.d.ts.map +1 -1
  130. package/dist/storage/file.js +3 -1
  131. package/dist/storage/file.js.map +1 -1
  132. package/dist/storage/memory.d.ts.map +1 -1
  133. package/dist/storage/memory.js +3 -1
  134. package/dist/storage/memory.js.map +1 -1
  135. package/dist/timeout.d.ts.map +1 -1
  136. package/dist/timeout.js +2 -4
  137. package/dist/timeout.js.map +1 -1
  138. package/dist/types.d.ts +9 -59
  139. package/dist/types.d.ts.map +1 -1
  140. package/dist/ui.d.ts +3 -94
  141. package/dist/ui.d.ts.map +1 -1
  142. package/dist/ui.js +30 -1427
  143. package/dist/ui.js.map +1 -1
  144. package/dist/version.d.ts +1 -1
  145. package/dist/version.js +1 -1
  146. package/package.json +15 -8
  147. package/src/activity.ts +0 -9
  148. package/src/auth/bearer.ts +4 -33
  149. package/src/auth/clerk.ts +35 -48
  150. package/src/call-admission.ts +8 -6
  151. package/src/catalog-fingerprint.ts +52 -0
  152. package/src/catalog-limits.ts +11 -0
  153. package/src/catalog-service.ts +601 -0
  154. package/src/catalog.ts +57 -2
  155. package/src/concurrency.ts +39 -0
  156. package/src/connectors/api.ts +22 -12
  157. package/src/connectors/remote-mcp.ts +63 -45
  158. package/src/credentials.ts +2 -2
  159. package/src/errors.ts +21 -1
  160. package/src/execute.ts +193 -423
  161. package/src/executor-result.ts +3 -3
  162. package/src/executors/quickjs-child.ts +8 -4
  163. package/src/executors/quickjs-protocol.ts +1 -1
  164. package/src/executors/quickjs-runtime.ts +5 -1
  165. package/src/executors/quickjs.ts +11 -8
  166. package/src/favicon.ts +2 -2
  167. package/src/index.ts +70 -222
  168. package/src/invocation.ts +580 -0
  169. package/src/meta-tools.ts +393 -985
  170. package/src/operator-ui/browser.css +360 -0
  171. package/src/operator-ui/browser.ts +987 -0
  172. package/src/operator-ui/generated.ts +4 -0
  173. package/src/operator-ui/model.ts +105 -0
  174. package/src/registry.ts +459 -439
  175. package/src/retired-toolkits.ts +14 -0
  176. package/src/routes/activity.ts +215 -0
  177. package/src/routes/credentials.ts +254 -0
  178. package/src/routes/mcp.ts +339 -0
  179. package/src/routes/oauth.ts +322 -0
  180. package/src/routes/shared.ts +240 -0
  181. package/src/routes/ui.ts +143 -0
  182. package/src/server.ts +64 -1731
  183. package/src/skills.ts +15 -43
  184. package/src/storage/file.ts +3 -1
  185. package/src/storage/memory.ts +3 -1
  186. package/src/timeout.ts +2 -4
  187. package/src/types.ts +8 -60
  188. package/src/ui.ts +60 -1538
  189. package/src/version.ts +1 -1
  190. package/dist/credential-health.d.ts +0 -227
  191. package/dist/credential-health.d.ts.map +0 -1
  192. package/dist/credential-health.js +0 -606
  193. package/dist/credential-health.js.map +0 -1
  194. package/dist/toolkits.d.ts +0 -146
  195. package/dist/toolkits.d.ts.map +0 -1
  196. package/dist/toolkits.js +0 -322
  197. package/dist/toolkits.js.map +0 -1
  198. package/src/credential-health.ts +0 -832
  199. package/src/toolkits.ts +0 -461
@@ -1,15 +1,15 @@
1
1
  import type { ExecuteResult } from "./types.js";
2
2
 
3
3
  /** ~6k tokens. Sandbox code should filter data down before returning. */
4
- export const MAX_EXECUTE_RESULT_CHARS = 24_000;
4
+ const MAX_EXECUTE_RESULT_CHARS = 24_000;
5
5
  export const MAX_EXECUTE_LOG_CHARS = 4_000;
6
6
 
7
7
  function msg(err: unknown): string {
8
8
  return err instanceof Error ? err.message : String(err);
9
9
  }
10
10
 
11
- export function serializeExecuteValue(value: unknown): string {
12
- const serialized = JSON.stringify(value, null, 2);
11
+ function serializeExecuteValue(value: unknown): string {
12
+ const serialized = JSON.stringify(value);
13
13
  return serialized === undefined ? String(value) : serialized;
14
14
  }
15
15
 
@@ -37,7 +37,11 @@ function send(message: ChildToParentMessage): void {
37
37
  process.send(message);
38
38
  }
39
39
 
40
- function provider(name: string, jobId: number): ExecutorProvider {
40
+ function provider(
41
+ name: string,
42
+ jobId: number,
43
+ prelude?: string,
44
+ ): ExecutorProvider {
41
45
  const functions = new Proxy(
42
46
  Object.create(null) as ExecutorProvider["fns"],
43
47
  {
@@ -72,7 +76,7 @@ function provider(name: string, jobId: number): ExecutorProvider {
72
76
  : undefined,
73
77
  },
74
78
  );
75
- return { name, fns: functions };
79
+ return { name, fns: functions, ...(prelude ? { prelude } : {}) };
76
80
  }
77
81
 
78
82
  function fixedTransportFailure(message: string): ExecutionPayload {
@@ -90,8 +94,8 @@ async function run(payload: RunPayload): Promise<void> {
90
94
  }
91
95
  activeJobId = payload.id;
92
96
  try {
93
- const providers = payload.providerNames.map((name) =>
94
- provider(name, payload.id),
97
+ const providers = payload.providers.map(({ name, prelude }) =>
98
+ provider(name, payload.id, prelude),
95
99
  );
96
100
  const raw = await executeQuickJs(
97
101
  payload.code,
@@ -15,7 +15,7 @@ export const MAX_QUICKJS_HOST_RPC_BYTES = 256 * 1024;
15
15
  export interface RunPayload {
16
16
  id: number;
17
17
  code: string;
18
- providerNames: string[];
18
+ providers: Array<{ name: string; prelude?: string }>;
19
19
  options: QuickJsRuntimeOptions;
20
20
  }
21
21
 
@@ -88,7 +88,8 @@ function exceedsUtf8ByteLimit(value: string, limit: number): boolean {
88
88
  export function normalizeCode(code: string): string {
89
89
  let c = code.trim();
90
90
  const fence = /^```[\w-]*\s*\n([\s\S]*?)\n?```$/.exec(c);
91
- if (fence) c = fence[1].trim();
91
+ const fencedCode = fence?.[1];
92
+ if (fencedCode !== undefined) c = fencedCode.trim();
92
93
  // Sniff for an existing function expression past any leading comments/blank
93
94
  // lines — models routinely prefix their code with a `//` or `/* */` note,
94
95
  // which would otherwise defeat the detection and wrap the whole thing.
@@ -122,6 +123,9 @@ function setupScript(providers: ExecutorProvider[]): string {
122
123
  const ns = JSON.stringify(p.name);
123
124
  lines.push(`globalThis[${ns}] = __namespace(${ns});`);
124
125
  }
126
+ for (const p of providers) {
127
+ if (p.prelude) lines.push(p.prelude);
128
+ }
125
129
  lines.push(`delete globalThis.__namespace;`);
126
130
  return lines.join("\n");
127
131
  }
@@ -67,13 +67,13 @@ interface ActiveRun {
67
67
  }
68
68
 
69
69
  interface ChildSlot {
70
- child?: ChildProcess;
71
- ready?: Promise<void>;
72
- resolveReady?: () => void;
73
- rejectReady?: (error: Error) => void;
74
- readyTimer?: ReturnType<typeof setTimeout>;
75
- active?: ActiveRun;
76
- expectedExit?: ChildProcess;
70
+ child?: ChildProcess | undefined;
71
+ ready?: Promise<void> | undefined;
72
+ resolveReady?: (() => void) | undefined;
73
+ rejectReady?: ((error: Error) => void) | undefined;
74
+ readyTimer?: ReturnType<typeof setTimeout> | undefined;
75
+ active?: ActiveRun | undefined;
76
+ expectedExit?: ChildProcess | undefined;
77
77
  consecutiveCrashes: number;
78
78
  notBefore: number;
79
79
  }
@@ -360,7 +360,10 @@ class QuickJsChildPool implements AdmittingExecutor {
360
360
  {
361
361
  id,
362
362
  code,
363
- providerNames: providers.map((item) => item.name),
363
+ providers: providers.map((item) => ({
364
+ name: item.name,
365
+ ...(item.prelude ? { prelude: item.prelude } : {}),
366
+ })),
364
367
  options: this.runtimeOptions,
365
368
  } satisfies RunPayload,
366
369
  "QuickJS run payload",
package/src/favicon.ts CHANGED
@@ -41,8 +41,8 @@ function buildIco(): Uint8Array {
41
41
  const pixel = pixels + (row * size + x) * 4;
42
42
  ico[pixel + 3] = opaque ? 255 : 0;
43
43
  if (!opaque) {
44
- ico[mask + row * maskRowLength + Math.floor(x / 8)] |=
45
- 0x80 >> (x % 8);
44
+ const maskIndex = mask + row * maskRowLength + Math.floor(x / 8);
45
+ ico[maskIndex] = (ico[maskIndex] ?? 0) | (0x80 >> (x % 8));
46
46
  }
47
47
  }
48
48
  }
package/src/index.ts CHANGED
@@ -6,12 +6,6 @@ import {
6
6
  import { Registry } from "./registry.js";
7
7
  import { createFetchHandler } from "./server.js";
8
8
  import { droppedBrandingUrls, droppedUiAuthUrls } from "./ui.js";
9
- import {
10
- resolveToolkits,
11
- validateToolkitBindings,
12
- type Toolkit,
13
- type ToolkitConfig,
14
- } from "./toolkits.js";
15
9
  import { memoryStorage } from "./storage/memory.js";
16
10
  import { CONNECTA_VERSION } from "./version.js";
17
11
  import {
@@ -20,11 +14,6 @@ import {
20
14
  withExecutorAdmission,
21
15
  } from "./executor-admission.js";
22
16
  import type { ActivityReadGate, ActivityStore } from "./activity.js";
23
- import type {
24
- CredentialCheckResult,
25
- CredentialHealthConfig,
26
- } from "./credential-health.js";
27
- import type { DeferredWork } from "./connector-scope.js";
28
17
  import type {
29
18
  Connector,
30
19
  ConnectaBranding,
@@ -50,28 +39,22 @@ export interface ConnectaActivityConfig {
50
39
  deploymentId?: string;
51
40
  }
52
41
 
53
- /** Operator-vault encryption and proactive credential-health tuning. */
42
+ /** Operator-vault encryption. */
54
43
  export interface ConnectaCredentialsConfig {
55
44
  /**
56
45
  * Base64-encoded 32-byte AES key for credentials managed on /credentials.
57
46
  * Keep this in the runtime's secret store, never in KV or source control.
58
47
  */
59
48
  encryptionKey?: string;
60
- /**
61
- * Tuning for proactive credential liveness checks that let a connector's
62
- * status flip to `auth_required` before an agent's call fails. Defaults: one
63
- * check per connector per 15 minutes, four in flight, 30 seconds each,
64
- * triggered opportunistically by inbound authenticated traffic.
65
- *
66
- * Optional even without an encryption key because downstream OAuth connectors
67
- * manage their own grants. `Connecta.checkCredentials()` runs the same checks
68
- * on demand for a Worker cron trigger or Node interval.
69
- */
70
- health?: CredentialHealthConfig;
71
49
  }
72
50
 
73
51
  /** Tool-catalog caching, persistence, stale fallback, and probe deadlines. */
74
52
  export interface ConnectaDiscoveryConfig {
53
+ /**
54
+ * Maximum connector catalogs/status probes fetched at once by discovery
55
+ * operations. Default 4.
56
+ */
57
+ concurrency?: number;
75
58
  /** Tool-list cache TTL (seconds). Default 300. */
76
59
  catalogTtlSeconds?: number;
77
60
  /**
@@ -151,38 +134,6 @@ export interface ConnectaAdmissionConfig {
151
134
 
152
135
  export interface ConnectaConfig {
153
136
  connectors: Connector[];
154
- /**
155
- * Named scoped views over `connectors`, selected per client connection with
156
- * `?toolkit=<name>` on the `/mcp` URL. One deployment belongs to one org;
157
- * a toolkit is the slice of it a group of team members sees.
158
- *
159
- * ```ts
160
- * toolkits: {
161
- * support: { connectors: ["zendesk", "notion"] },
162
- * exec: {
163
- * connectors: ["zendesk", "notion", "gmail"],
164
- * excludeTools: ["gmail.send_message"],
165
- * },
166
- * }
167
- * ```
168
- *
169
- * Inside a toolkit-scoped session every meta-tool behaves as if out-of-scope
170
- * connectors and tools do not exist, and an out-of-scope address fails
171
- * exactly as a nonexistent one does. No `?toolkit=` ⇒ the full registry, so
172
- * adding toolkits changes nothing for connections that don't ask for one; an
173
- * unknown name is an error, never a silent fallback.
174
- *
175
- * Selection is self-service until a toolkit is BOUND to an inbound identity:
176
- * pass `toolkits` to an auth adapter — `bearerToken(secret, { toolkits:
177
- * ["support"] })` — and that credential may open only those toolkits, and may
178
- * not connect unscoped unless it also passes `unscoped: true`. An unbound
179
- * identity keeps the self-service behavior.
180
- *
181
- * Definitions are validated at construction: an unknown connector id, an
182
- * empty connector selection, an empty `includeTools`, a malformed tool
183
- * address, or an address naming no tool on an in-code connector all throw.
184
- */
185
- toolkits?: ToolkitConfig;
186
137
  /** Inbound auth adapters. Includes bearerToken(...); omit for open (dev). */
187
138
  auth?: InboundAuth | InboundAuth[];
188
139
  /** KVStorage impl. Defaults to memoryStorage(). */
@@ -194,7 +145,7 @@ export interface ConnectaConfig {
194
145
  publicUrl?: string;
195
146
  /** Payload-free tool activity storage and operator-read policy. */
196
147
  activity?: ConnectaActivityConfig;
197
- /** Operator credential vault and proactive liveness-check settings. */
148
+ /** Operator credential vault settings. */
198
149
  credentials?: ConnectaCredentialsConfig;
199
150
  /** Tool-catalog caching, persistence, stale fallback, and probe deadlines. */
200
151
  discovery?: ConnectaDiscoveryConfig;
@@ -230,37 +181,6 @@ export interface Connecta {
230
181
  /** Web-standard fetch handler. Usable as `export default { fetch: connecta.fetch }`. */
231
182
  fetch: (request: Request, env?: unknown, ctx?: unknown) => Promise<Response>;
232
183
  registry: Registry;
233
- /**
234
- * Check the stored downstream credentials now — the scheduler-facing half of
235
- * credential health (issue #24). Wire it to whatever timer the runtime has:
236
- *
237
- * ```ts
238
- * // Cloudflare Workers (wrangler.jsonc: "triggers": { "crons": ["*\/15 * * * *"] })
239
- * async scheduled(_c, env, ctx) {
240
- * const defer = ctx.waitUntil.bind(ctx);
241
- * ctx.waitUntil(build(env).checkCredentials({ defer }));
242
- * }
243
- * // Node
244
- * setInterval(() => void connecta.checkCredentials(), 15 * 60_000).unref();
245
- * ```
246
- *
247
- * Returns one outcome per connector considered, including why a connector was
248
- * skipped (`fresh` is the rate limit: a connector checked less than
249
- * `credentials.health.intervalSeconds` ago is not re-checked unless `force`).
250
- * Never rejects on a connector failure — a broken connector becomes an `error`
251
- * verdict. Needs a base URL for connector contexts: `publicUrl` supplies it,
252
- * or pass one.
253
- */
254
- checkCredentials: (opts?: {
255
- baseUrl?: string;
256
- force?: boolean;
257
- ids?: string[];
258
- /**
259
- * Runtime continuation for bounded teardown after the check result is ready.
260
- * Workers should pass `ctx.waitUntil.bind(ctx)`.
261
- */
262
- defer?: DeferredWork;
263
- }) => Promise<CredentialCheckResult[]>;
264
184
  /** Drain and release configured executor resources. Idempotent. */
265
185
  close: () => Promise<void>;
266
186
  }
@@ -313,7 +233,6 @@ const LEGACY_CONFIG_MIGRATIONS = [
313
233
  ["activityReadGate", "activity.readGate"],
314
234
  ["activityDeploymentId", "activity.deploymentId"],
315
235
  ["credentialEncryptionKey", "credentials.encryptionKey"],
316
- ["credentialHealth", "credentials.health"],
317
236
  ["toolCacheTtlSeconds", "discovery.catalogTtlSeconds"],
318
237
  ["persistToolCatalog", "discovery.persistCatalog"],
319
238
  ["toolCatalogStaleSeconds", "discovery.staleCatalogSeconds"],
@@ -332,6 +251,23 @@ const hasOwn = (value: object, key: PropertyKey): boolean =>
332
251
  */
333
252
  function assertNoLegacyConfig(config: ConnectaConfig): void {
334
253
  const candidate = config as unknown as Record<PropertyKey, unknown>;
254
+ if (hasOwn(candidate, "toolkits")) {
255
+ throw new Error(
256
+ "ConnectaConfig.toolkits was removed in issue #178. Deploy one " +
257
+ "connecta instance per audience instead; see ethos.md.",
258
+ );
259
+ }
260
+ const credentials = candidate.credentials;
261
+ const hasNestedHealth =
262
+ typeof credentials === "object" &&
263
+ credentials !== null &&
264
+ hasOwn(credentials, "health");
265
+ if (hasOwn(candidate, "credentialHealth") || hasNestedHealth) {
266
+ throw new Error(
267
+ "`credentials.health` and legacy `credentialHealth` were removed in " +
268
+ "issue #179. Credentials now fail at use; see ethos.md.",
269
+ );
270
+ }
335
271
  const found: Array<readonly [string, string]> = [];
336
272
  if (hasOwn(candidate, "activity")) {
337
273
  const activity = candidate.activity;
@@ -371,7 +307,6 @@ function assertNoLegacyConfig(config: ConnectaConfig): void {
371
307
  function warnInsecureConfig(
372
308
  config: ConnectaConfig,
373
309
  inboundAuth: InboundAuth[],
374
- toolkits: ReadonlyMap<string, Toolkit> | undefined,
375
310
  logger: Logger,
376
311
  ): void {
377
312
  const oauthConnectors = config.connectors.filter((c) => c.finishAuth);
@@ -402,64 +337,6 @@ function warnInsecureConfig(
402
337
  );
403
338
  }
404
339
 
405
- // Toolkits that nothing binds to an identity: selection is then self-service,
406
- // and the boundary organizes the surface rather than protecting it. Three
407
- // distinct shapes, so three distinct warnings — an operator can only act on
408
- // the one they are actually in.
409
- //
410
- // All are keyed off the RESOLVED toolkits, which is the same map `?toolkit=`
411
- // resolves against, rather than the presence of the config key: `toolkits: {}`
412
- // is a truthy object that resolves to nothing selectable, so warning about a
413
- // choice no caller can make would name a risk that does not exist.
414
- if (toolkits) {
415
- const unbound = inboundAuth.filter((provider) => !provider.toolkitBinding);
416
- if (inboundAuth.length === 0) {
417
- // No auth at all ⇒ no identity exists to bind, so binding is not even the
418
- // fix here. The open-mode warning above covers the wider exposure.
419
- logger.warn(
420
- "[connecta] toolkits are configured but there is no inbound " +
421
- "authentication: with no identity to bind a toolkit to, any caller " +
422
- "can choose any toolkit or omit ?toolkit= and see every connector. " +
423
- "Configure `auth` (for example bearerToken(...) or Clerk), then bind " +
424
- "each credential with `toolkits: [...]`.",
425
- );
426
- } else if (unbound.length === inboundAuth.length) {
427
- // Authenticated, but every credential may still select every view. This is
428
- // the shape issue #37 exists to close, and it is invisible without a line
429
- // saying so: nothing fails, the teams are simply not separated.
430
- logger.warn(
431
- "[connecta] toolkits are configured but no inbound identity is bound " +
432
- "to one: every credential `auth` admits can select any toolkit, or " +
433
- "omit ?toolkit= and see the whole deployment, so a token handed to " +
434
- "one team also opens the others' views. Bind each credential with " +
435
- "`toolkits: [...]` on its auth adapter (add `unscoped: true` for an " +
436
- "operator credential that should still see everything).",
437
- );
438
- } else if (unbound.length > 0) {
439
- // The dangerous middle: SOME credentials are bound, which is exactly when
440
- // an operator believes the deployment is separated — while one forgotten
441
- // provider still opens every view and the whole deployment-wide surface.
442
- // Naming the unbound providers is the point; an intentionally unrestricted
443
- // credential says so with `unscoped: true` and stops appearing here.
444
- const counted = new Map<string, number>();
445
- for (const provider of unbound) {
446
- counted.set(provider.kind, (counted.get(provider.kind) ?? 0) + 1);
447
- }
448
- const named = [...counted]
449
- .map(([kind, count]) => (count > 1 ? `${kind} x${count}` : kind))
450
- .join(", ");
451
- logger.warn(
452
- `[connecta] toolkits are bound on some inbound auth providers but not ` +
453
- `all: ${named} ${unbound.length === 1 ? "declares" : "declare"} no ` +
454
- "binding, so a caller that provider admits can still select any " +
455
- "toolkit, connect unscoped, and read the deployment-wide operator " +
456
- "surfaces — whatever the bound credentials beside it allow. Bind it " +
457
- "too, or declare the exemption with `toolkits: [...], unscoped: true` " +
458
- "if it is meant to be an operator credential.",
459
- );
460
- }
461
- }
462
-
463
340
  // Branding URLs that failed their scheme gate. Rendering silently falls back
464
341
  // (a bad URL must not take the page down), so this warning is the only way an
465
342
  // operator learns their value never reached the page.
@@ -536,8 +413,10 @@ export function createConnecta(config: ConnectaConfig): Connecta {
536
413
  const credentialConnectors = config.connectors.filter((c) => c.credential);
537
414
  const encryptionKey = config.credentials?.encryptionKey;
538
415
  if (credentialConnectors.length > 0 && !encryptionKey) {
539
- throw new Error(
540
- `credentials.encryptionKey is required by connector credentials: ${credentialConnectors.map((c) => c.id).join(", ")}`,
416
+ logger.warn(
417
+ "Operator-managed credentials are unavailable because " +
418
+ "credentials.encryptionKey is not configured for connectors: " +
419
+ credentialConnectors.map((c) => c.id).join(", "),
541
420
  );
542
421
  }
543
422
  const credentialVault = encryptionKey
@@ -546,25 +425,25 @@ export function createConnecta(config: ConnectaConfig): Connecta {
546
425
  const registry = new Registry(config.connectors, {
547
426
  storage,
548
427
  logger,
549
- credentialVault,
550
- toolCacheTtlSeconds: config.discovery?.catalogTtlSeconds,
551
- persistToolCatalog: config.discovery?.persistCatalog,
552
- toolCatalogStaleSeconds: config.discovery?.staleCatalogSeconds,
553
- maxResultBytes: config.calls?.maxResultBytes,
554
- maxBatchResultBytes: config.calls?.maxBatchResultBytes,
555
- credentialHealth: config.credentials?.health,
428
+ ...(credentialVault !== undefined ? { credentialVault } : {}),
429
+ ...(config.discovery?.catalogTtlSeconds !== undefined
430
+ ? { toolCacheTtlSeconds: config.discovery.catalogTtlSeconds }
431
+ : {}),
432
+ ...(config.discovery?.persistCatalog !== undefined
433
+ ? { persistToolCatalog: config.discovery.persistCatalog }
434
+ : {}),
435
+ ...(config.discovery?.staleCatalogSeconds !== undefined
436
+ ? { toolCatalogStaleSeconds: config.discovery.staleCatalogSeconds }
437
+ : {}),
438
+ ...(config.calls?.maxResultBytes !== undefined
439
+ ? { maxResultBytes: config.calls.maxResultBytes }
440
+ : {}),
441
+ ...(config.calls?.maxBatchResultBytes !== undefined
442
+ ? { maxBatchResultBytes: config.calls.maxBatchResultBytes }
443
+ : {}),
556
444
  });
557
- // Throws on every structural mistake it can see (see resolveToolkits): a
558
- // typo must not become a scope the operator never wrote. Note this is about
559
- // the scope being *intended*, not about it being an access check — a toolkit
560
- // scopes visibility, and `auth` remains the thing deciding who gets in.
561
- const toolkits = resolveToolkits(config.toolkits, config.connectors);
562
445
  const inboundAuth = normalizeAuth(config.auth);
563
- // Same contract for the identity half: a binding that names a toolkit this
564
- // deployment does not declare would deny that credential every connection,
565
- // with a 403 its client reports as a transport failure. Throw here instead.
566
- validateToolkitBindings(inboundAuth, toolkits);
567
- warnInsecureConfig(config, inboundAuth, toolkits, logger);
446
+ warnInsecureConfig(config, inboundAuth, logger);
568
447
  const requestAdmission = admissionController(
569
448
  config.admission?.requests,
570
449
  REQUEST_ADMISSION_DEFAULTS,
@@ -588,24 +467,38 @@ export function createConnecta(config: ConnectaConfig): Connecta {
588
467
  const handler = createFetchHandler({
589
468
  registry,
590
469
  auth: inboundAuth,
591
- publicUrl: config.publicUrl,
470
+ ...(config.publicUrl !== undefined ? { publicUrl: config.publicUrl } : {}),
592
471
  serverInfo: {
593
472
  ...config.serverInfo,
594
473
  name: config.serverInfo?.name ?? "connecta",
595
474
  version: config.serverInfo?.version ?? CONNECTA_VERSION,
596
475
  },
597
476
  logger,
598
- activity: config.activity?.store,
599
- activityReadGate: config.activity?.readGate,
600
- activityDeploymentId: config.activity?.deploymentId,
601
- executor,
477
+ ...(config.activity?.store !== undefined
478
+ ? { activity: config.activity.store }
479
+ : {}),
480
+ ...(config.activity?.readGate !== undefined
481
+ ? { activityReadGate: config.activity.readGate }
482
+ : {}),
483
+ ...(config.activity?.deploymentId !== undefined
484
+ ? { activityDeploymentId: config.activity.deploymentId }
485
+ : {}),
486
+ ...(executor !== undefined ? { executor } : {}),
602
487
  requestAdmission,
603
- defaultToolTimeoutMs: config.calls?.defaultTimeoutMs,
604
- probeTimeoutMs: config.discovery?.probeTimeoutMs,
605
- credentialVault,
606
- deploymentInfo: config.deploymentInfo,
607
- branding: config.branding,
608
- ...(toolkits ? { toolkits } : {}),
488
+ ...(config.calls?.defaultTimeoutMs !== undefined
489
+ ? { defaultToolTimeoutMs: config.calls.defaultTimeoutMs }
490
+ : {}),
491
+ ...(config.discovery?.probeTimeoutMs !== undefined
492
+ ? { probeTimeoutMs: config.discovery.probeTimeoutMs }
493
+ : {}),
494
+ ...(config.discovery?.concurrency !== undefined
495
+ ? { discoveryConcurrency: config.discovery.concurrency }
496
+ : {}),
497
+ ...(credentialVault !== undefined ? { credentialVault } : {}),
498
+ ...(config.deploymentInfo !== undefined
499
+ ? { deploymentInfo: config.deploymentInfo }
500
+ : {}),
501
+ ...(config.branding !== undefined ? { branding: config.branding } : {}),
609
502
  });
610
503
  let closePromise: Promise<void> | undefined;
611
504
  return {
@@ -617,33 +510,6 @@ export function createConnecta(config: ConnectaConfig): Connecta {
617
510
  : undefined,
618
511
  ),
619
512
  registry,
620
- checkCredentials: (opts = {}) => {
621
- // A scheduled check has no inbound request to derive an origin from, and
622
- // a connector context without one would mint OAuth redirect URIs against
623
- // a guess. Say so instead: the fix is one config line.
624
- const baseUrl = opts.baseUrl ?? config.publicUrl;
625
- if (!baseUrl) {
626
- // Rejected, not thrown: the callers this is written for are
627
- // `ctx.waitUntil(...)` and `.catch(...)` on the returned promise, and a
628
- // synchronous throw escapes both — it would take down a scheduled
629
- // handler instead of being reported by it.
630
- return Promise.reject(
631
- new Error(
632
- "checkCredentials() needs a base URL: set `publicUrl` on the " +
633
- "config (recommended — it is also what downstream OAuth " +
634
- "callbacks use) or pass checkCredentials({ baseUrl }).",
635
- ),
636
- );
637
- }
638
- return registry.checkCredentialHealth(
639
- baseUrl,
640
- {
641
- ...(opts.force !== undefined ? { force: opts.force } : {}),
642
- ...(opts.ids ? { ids: opts.ids } : {}),
643
- },
644
- opts.defer,
645
- );
646
- },
647
513
  close: async () => {
648
514
  closePromise ??= Promise.resolve().then(async () => {
649
515
  requestAdmission.close();
@@ -673,23 +539,6 @@ export { CONNECTA_VERSION } from "./version.js";
673
539
  // the class itself, the credential vault, and the meta-tool/sandbox factories
674
540
  // are internal factoring and are deliberately not part of the API surface.
675
541
  export type { Registry } from "./registry.js";
676
- // Config-as-code shapes for `ConnectaConfig.toolkits` and the identity bindings
677
- // that gate them. The resolved `Toolkit` and the `ScopedRegistry` that enforces
678
- // it are internal factoring.
679
- export type {
680
- ToolkitBindingOptions,
681
- ToolkitConfig,
682
- ToolkitDefinition,
683
- } from "./toolkits.js";
684
- // Credential health: the config shape, and the result shape a scheduled
685
- // `checkCredentials()` returns. The checker itself is internal factoring.
686
- export type {
687
- CredentialCheckResult,
688
- CredentialCheckSkip,
689
- CredentialCheckState,
690
- CredentialHealthConfig,
691
- CredentialHealthRecord,
692
- } from "./credential-health.js";
693
542
 
694
543
  export type {
695
544
  RemoteMcpOptions,
@@ -718,7 +567,6 @@ export type {
718
567
  ExecutorLease,
719
568
  ExecutorProvider,
720
569
  InboundAuth,
721
- ToolkitBinding,
722
570
  UiAuthConfig,
723
571
  AuthResult,
724
572
  JsonSchema,