@zackbart/connecta 0.24.3 → 0.25.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 (145) hide show
  1. package/AGENTS.md +58 -30
  2. package/CHANGELOG.md +317 -1
  3. package/README.md +5 -6
  4. package/dist/advertised-schema.js +53 -0
  5. package/dist/auth/downstream-oauth.js +490 -106
  6. package/dist/branding.d.ts +31 -2
  7. package/dist/branding.js +116 -8
  8. package/dist/call-admission.js +139 -113
  9. package/dist/catalog-service.js +141 -105
  10. package/dist/catalog.js +258 -33
  11. package/dist/concurrency.js +24 -23
  12. package/dist/connector-scope.js +6 -37
  13. package/dist/connectors/api.d.ts +3 -3
  14. package/dist/connectors/api.js +10 -2
  15. package/dist/connectors/guarded-fetch.js +34 -4
  16. package/dist/connectors/remote-mcp.d.ts +6 -5
  17. package/dist/connectors/remote-mcp.js +325 -245
  18. package/dist/credential-contract.d.ts +9 -0
  19. package/dist/credential-rules.js +2 -2
  20. package/dist/credentials.d.ts +6 -1
  21. package/dist/credentials.js +45 -3
  22. package/dist/errors.d.ts +11 -3
  23. package/dist/errors.js +2 -1
  24. package/dist/execute.js +363 -257
  25. package/dist/executor-admission.js +67 -50
  26. package/dist/executors/quickjs-runtime.js +4 -2
  27. package/dist/executors/quickjs.js +251 -210
  28. package/dist/fix-prompt.js +37 -0
  29. package/dist/index.d.ts +11 -2
  30. package/dist/index.js +63 -21
  31. package/dist/invocation.js +324 -285
  32. package/dist/meta-tools.js +304 -114
  33. package/dist/oauth-callback-outcome.js +94 -0
  34. package/dist/oauth-sealing.js +29 -0
  35. package/dist/operator-ui/fix-prompts.js +87 -0
  36. package/dist/operator-ui/generated.js +2 -2
  37. package/dist/operator-ui/model.d.ts +46 -1
  38. package/dist/operator-ui/setup-commands.js +53 -0
  39. package/dist/operator-ui/view.js +109 -2
  40. package/dist/program-source.js +205 -0
  41. package/dist/providers/cloudflare.d.ts +14 -2
  42. package/dist/providers/cloudflare.js +107 -16
  43. package/dist/providers/linear.d.ts +26 -4
  44. package/dist/providers/linear.js +19 -4
  45. package/dist/providers/mixpanel.d.ts +16 -3
  46. package/dist/providers/mixpanel.js +13 -2
  47. package/dist/providers/notion.d.ts +8 -1
  48. package/dist/providers/notion.js +85 -12
  49. package/dist/providers/revenuecat.d.ts +30 -4
  50. package/dist/providers/revenuecat.js +70 -5
  51. package/dist/providers/stripe.d.ts +10 -3
  52. package/dist/providers/stripe.js +30 -4
  53. package/dist/providers/vercel.js +12 -2
  54. package/dist/registry.d.ts +13 -5
  55. package/dist/registry.js +317 -258
  56. package/dist/routes/activity.js +120 -137
  57. package/dist/routes/credentials.js +174 -163
  58. package/dist/routes/mcp.js +129 -70
  59. package/dist/routes/oauth-management.js +60 -71
  60. package/dist/routes/oauth.js +44 -16
  61. package/dist/routes/operator.js +82 -0
  62. package/dist/routes/shared.d.ts +2 -0
  63. package/dist/routes/ui-data.js +210 -0
  64. package/dist/routes/ui.js +64 -32
  65. package/dist/runtime/admission.js +43 -0
  66. package/dist/runtime/call-admission.js +67 -0
  67. package/dist/runtime/connector-scope.js +72 -0
  68. package/dist/runtime/run.js +174 -0
  69. package/dist/runtime/services.js +130 -0
  70. package/dist/runtime/storage.js +66 -0
  71. package/dist/server.js +111 -89
  72. package/dist/skills.js +1 -1
  73. package/dist/storage/file.js +39 -2
  74. package/dist/storage/memory.js +28 -16
  75. package/dist/timeout.js +17 -47
  76. package/dist/types.d.ts +64 -7
  77. package/dist/ui.d.ts +16 -2
  78. package/dist/ui.js +68 -176
  79. package/dist/url-safety.js +157 -0
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/documentation/architecture.md +368 -182
  83. package/documentation/auth.md +277 -176
  84. package/documentation/code-mode.md +479 -329
  85. package/documentation/meta-tools.md +494 -417
  86. package/ethos.md +44 -47
  87. package/examples/worker/AGENTS.md +2 -1
  88. package/examples/worker/README.md +62 -14
  89. package/examples/worker/src/d1-storage.ts +116 -0
  90. package/examples/worker/src/index.ts +18 -21
  91. package/examples/worker/wrangler.jsonc +16 -4
  92. package/package.json +11 -4
  93. package/templates/node/.env.example +3 -3
  94. package/templates/node/AGENTS.md +5 -4
  95. package/templates/node/README.md +2 -1
  96. package/templates/node/package.json +1 -1
  97. package/templates/node/src/index.ts +23 -22
  98. package/dist/auth/downstream-oauth.d.ts +0 -200
  99. package/dist/catalog-fingerprint.d.ts +0 -19
  100. package/dist/catalog-limits.d.ts +0 -9
  101. package/dist/catalog-service.d.ts +0 -263
  102. package/dist/catalog.d.ts +0 -87
  103. package/dist/concurrency.d.ts +0 -3
  104. package/dist/connectors/guarded-fetch.d.ts +0 -92
  105. package/dist/credential-rules.d.ts +0 -85
  106. package/dist/execute.d.ts +0 -154
  107. package/dist/executor-result.d.ts +0 -10
  108. package/dist/executors/quickjs-child.d.ts +0 -1
  109. package/dist/executors/quickjs-protocol.d.ts +0 -67
  110. package/dist/favicon.d.ts +0 -1
  111. package/dist/identity.d.ts +0 -4
  112. package/dist/invocation.d.ts +0 -74
  113. package/dist/mcp-result.d.ts +0 -8
  114. package/dist/meta-tools.d.ts +0 -129
  115. package/dist/operator-ui/generated.d.ts +0 -2
  116. package/dist/operator-ui/view.d.ts +0 -151
  117. package/dist/result-shapes.d.ts +0 -13
  118. package/dist/retired-toolkits.d.ts +0 -2
  119. package/dist/routes/activity.d.ts +0 -2
  120. package/dist/routes/credentials.d.ts +0 -2
  121. package/dist/routes/mcp.d.ts +0 -10
  122. package/dist/routes/oauth-management.d.ts +0 -2
  123. package/dist/routes/oauth.d.ts +0 -2
  124. package/dist/routes/ui.d.ts +0 -2
  125. package/dist/server.d.ts +0 -9
  126. package/dist/skills.d.ts +0 -44
  127. package/dist/timeout.d.ts +0 -21
  128. package/dist/tool-safety.d.ts +0 -9
  129. package/documentation/call-admission.md +0 -158
  130. package/documentation/cloudflare.md +0 -471
  131. package/documentation/connector-guides.md +0 -176
  132. package/documentation/connectors.md +0 -431
  133. package/documentation/linear.md +0 -193
  134. package/documentation/mixpanel.md +0 -160
  135. package/documentation/notion.md +0 -308
  136. package/documentation/operations.md +0 -359
  137. package/documentation/operator-ui.md +0 -135
  138. package/documentation/optional-modules-upgrade.md +0 -243
  139. package/documentation/provider-conventions.md +0 -729
  140. package/documentation/request-admission.md +0 -204
  141. package/documentation/revenuecat.md +0 -305
  142. package/documentation/storage-and-credentials.md +0 -254
  143. package/documentation/stripe.md +0 -262
  144. package/documentation/upgrading.md +0 -768
  145. package/documentation/vercel.md +0 -241
@@ -20,5 +20,14 @@ export interface CredentialVault {
20
20
  set(connectorId: string, value: string, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
21
21
  setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
22
22
  delete(connectorId: string, owner?: string): Promise<void>;
23
+ /**
24
+ * Encrypt downstream OAuth state (tokens, a registered client, a PKCE
25
+ * verifier) before it enters storage. `purpose` is the storage key the value
26
+ * is written under, so the result is bound to one connector, owner, and key.
27
+ * Without both `seal` and `open`, that state is stored as plaintext.
28
+ */
29
+ seal?(connectorId: string, purpose: string, plaintext: string, owner?: string): Promise<string>;
30
+ /** Reverse `seal` for the same connector, purpose, and owner; rejects otherwise. */
31
+ open?(connectorId: string, purpose: string, sealed: string, owner?: string): Promise<string>;
23
32
  }
24
33
  export {};
@@ -3,7 +3,7 @@ export const STORED_CREDENTIAL_SHAPE_MISMATCH_ERROR = "Stored credential fields
3
3
  /**
4
4
  * Compare a connector's current declaration with the keys in its stored
5
5
  * credential. Values are deliberately ignored: callers may pass decrypted
6
- * values or `/credentials`' masked field metadata and get the same answer.
6
+ * values or the operator UI's masked credential field metadata and get the same answer.
7
7
  *
8
8
  * The test is CONTAINMENT, not equality: the stored key set is compatible when
9
9
  * it holds every field currently declared — the reserved `value` key for a
@@ -65,7 +65,7 @@ export function describeUndeclaredCredentialFields(fields) {
65
65
  }
66
66
  /**
67
67
  * The one rule deciding whether a connector's credential can be tested — read
68
- * by /credentials' `testable` flag, by `POST /ui/credentials/<id>/test` when
68
+ * by the operator UI's credential `testable` flag, by `POST /ui/credentials/<id>/test` when
69
69
  * it picks a hook, and by the construction-time mismatch warning, so those
70
70
  * three cannot drift apart.
71
71
  *
@@ -3,7 +3,8 @@ import type { ConnectorCredentialValues, KVStorage } from "./types.js";
3
3
  /**
4
4
  * Encrypted, connector-scoped credential vault over the deployment's existing
5
5
  * KVStorage. Only ciphertext enters KV; the AES-GCM key remains an environment
6
- * secret outside the store.
6
+ * secret outside the store. The same key seals downstream OAuth state, which
7
+ * the OAuth provider stores itself.
7
8
  */
8
9
  export declare class CredentialVault implements Vault {
9
10
  private readonly storage;
@@ -17,5 +18,9 @@ export declare class CredentialVault implements Vault {
17
18
  set(connectorId: string, value: string, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
18
19
  setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
19
20
  delete(connectorId: string, owner?: string): Promise<void>;
21
+ /** Encrypt downstream OAuth state under the vault key; nothing is stored. */
22
+ seal(connectorId: string, purpose: string, plaintext: string, owner?: string): Promise<string>;
23
+ /** Decrypt what `seal` produced for this connector, purpose, and owner. */
24
+ open(connectorId: string, purpose: string, sealed: string, owner?: string): Promise<string>;
20
25
  }
21
26
  export declare function encryptedCredentialVault(storage: KVStorage, encryptionKey: string): Vault;
@@ -3,6 +3,16 @@ const IV_BYTES = 12;
3
3
  const MAX_CREDENTIAL_BYTES = 16_384;
4
4
  const encoder = new TextEncoder();
5
5
  const decoder = new TextDecoder();
6
+ /** Version tag of the compact `seal` output: `v1.<iv>.<ciphertext>`. */
7
+ const SEALED_VERSION = "v1";
8
+ /**
9
+ * Binds sealed OAuth state to its connector, owner, and physical storage key
10
+ * (which carries the authorization epoch), so ciphertext copied anywhere else
11
+ * does not open. Connector ids cannot contain `|`, and the owner is a hash.
12
+ */
13
+ function oauthStateAdditionalData(connectorId, purpose, owner) {
14
+ return encoder.encode(`connecta:oauth-state:v1|${connectorId}|${owner ?? ""}|${purpose}`);
15
+ }
6
16
  function storageKey(connectorId, owner) {
7
17
  return owner
8
18
  ? `principal:${owner}:conn:${connectorId}:credential:v1`
@@ -20,7 +30,7 @@ function base64ToBytes(value) {
20
30
  binary = atob(value);
21
31
  }
22
32
  catch {
23
- throw new Error("credentials.encryptionKey must be a base64-encoded 32-byte key");
33
+ throw new Error("The credential vault encryption key must be a base64-encoded 32-byte key");
24
34
  }
25
35
  return Uint8Array.from(binary, (char) => char.charCodeAt(0));
26
36
  }
@@ -87,7 +97,8 @@ function validateValues(values) {
87
97
  /**
88
98
  * Encrypted, connector-scoped credential vault over the deployment's existing
89
99
  * KVStorage. Only ciphertext enters KV; the AES-GCM key remains an environment
90
- * secret outside the store.
100
+ * secret outside the store. The same key seals downstream OAuth state, which
101
+ * the OAuth provider stores itself.
91
102
  */
92
103
  export class CredentialVault {
93
104
  storage;
@@ -96,7 +107,7 @@ export class CredentialVault {
96
107
  this.storage = storage;
97
108
  const raw = base64ToBytes(encryptionKey.trim());
98
109
  if (raw.byteLength !== KEY_BYTES) {
99
- throw new Error("credentials.encryptionKey must be a base64-encoded 32-byte key");
110
+ throw new Error("The credential vault encryption key must be a base64-encoded 32-byte key");
100
111
  }
101
112
  this.key = crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
102
113
  }
@@ -182,6 +193,37 @@ export class CredentialVault {
182
193
  async delete(connectorId, owner) {
183
194
  await this.storage.delete(storageKey(connectorId, owner));
184
195
  }
196
+ /** Encrypt downstream OAuth state under the vault key; nothing is stored. */
197
+ async seal(connectorId, purpose, plaintext, owner) {
198
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
199
+ const ciphertext = await crypto.subtle.encrypt({
200
+ name: "AES-GCM",
201
+ iv,
202
+ additionalData: oauthStateAdditionalData(connectorId, purpose, owner),
203
+ }, await this.key, encoder.encode(plaintext));
204
+ return `${SEALED_VERSION}.${bytesToBase64(iv)}.${bytesToBase64(new Uint8Array(ciphertext))}`;
205
+ }
206
+ /** Decrypt what `seal` produced for this connector, purpose, and owner. */
207
+ async open(connectorId, purpose, sealed, owner) {
208
+ const [version, iv, ciphertext, ...rest] = sealed.split(".");
209
+ if (version !== SEALED_VERSION ||
210
+ iv === undefined ||
211
+ ciphertext === undefined ||
212
+ rest.length > 0) {
213
+ throw new Error("Sealed OAuth state is invalid or corrupted");
214
+ }
215
+ try {
216
+ const plaintext = await crypto.subtle.decrypt({
217
+ name: "AES-GCM",
218
+ iv: base64ToBytes(iv),
219
+ additionalData: oauthStateAdditionalData(connectorId, purpose, owner),
220
+ }, await this.key, base64ToBytes(ciphertext));
221
+ return decoder.decode(plaintext);
222
+ }
223
+ catch {
224
+ throw new Error("Sealed OAuth state could not be decrypted");
225
+ }
226
+ }
185
227
  }
186
228
  export function encryptedCredentialVault(storage, encryptionKey) {
187
229
  return new CredentialVault(storage, encryptionKey);
package/dist/errors.d.ts CHANGED
@@ -2,11 +2,19 @@
2
2
  * Machine-readable classification of a failed connector tool call.
3
3
  *
4
4
  * A code earns its place by changing what the caller does next, never by
5
- * naming a cause — the rule provider conventions call H11.
5
+ * naming a cause — the rule provider conventions call H11, defined in
6
+ * `test/provider-conventions.test.ts`.
6
7
  */
7
8
  export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited" | "unavailable" | "invalid_args"
8
- /** Provider-owned absence; see provider-conventions.md H11 for the rule and
9
- * the permission-ambiguity exception. */
9
+ /**
10
+ * Provider-owned absence: the caller re-addresses rather than waiting,
11
+ * re-authorizing, or repairing arguments, and a program looping over ids can
12
+ * continue past it where `connector_call_failed` would abort. Map a status
13
+ * here only where the provider distinguishes absence from a permission gap —
14
+ * where it does not (Notion's `object_not_found` covers both), the honest
15
+ * code stays `connector_call_failed` or `auth_required` with a message that
16
+ * states the ambiguity (H11).
17
+ */
10
18
  | "not_found" | "input_required_unsupported" | "connector_call_failed";
11
19
  /** One bounded, payload-free explanation of an input-schema mismatch. */
12
20
  export interface ArgumentValidationIssue {
package/dist/errors.js CHANGED
@@ -20,7 +20,8 @@ function boundedIssueText(value, maxChars) {
20
20
  * the payload lands in both the text content and `structuredContent`. The agent
21
21
  * already holds what it sent; the echo is a convenience, never the record.
22
22
  */
23
- // Shared by argument and text echoes; see meta-tools.md lines 360-372.
23
+ // One budget shared by the argument and text echoes, so a caller cannot make a
24
+ // refusal larger by splitting what it sent across the two.
24
25
  const MAX_ECHOED_BYTES = 512;
25
26
  /**
26
27
  * The same budget spent on caller-authored *text* — the address it mistyped,