@zackbart/connecta 0.37.1 → 0.38.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 (168) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +2 -2
  3. package/bin/connecta.mjs +1 -39
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +2 -4
  6. package/dist/agent-identity.js +1 -1
  7. package/dist/auth/clerk-sdk/generated.js +2 -1
  8. package/dist/auth/downstream-oauth.js +19 -153
  9. package/dist/auth/downstream-revocation.js +1 -1
  10. package/dist/auth/oauth-grant.js +11 -0
  11. package/dist/auth/oauth-token-response.js +1 -1
  12. package/dist/auth/vercel-agent.js +2 -1
  13. package/dist/auth-elicitation.js +2 -2
  14. package/dist/branding.d.ts +1 -15
  15. package/dist/branding.js +2 -2
  16. package/dist/bytes.js +42 -0
  17. package/dist/call-admission-context.js +2 -5
  18. package/dist/call-recovery.js +0 -3
  19. package/dist/catalog-fingerprint.js +2 -18
  20. package/dist/catalog-service.js +1 -1
  21. package/dist/config-defaults.d.ts +1 -0
  22. package/dist/config-defaults.js +1 -0
  23. package/dist/config-schema.d.ts +1 -1
  24. package/dist/config-schema.js +6 -2
  25. package/dist/config.d.ts +41 -14
  26. package/dist/config.js +21 -71
  27. package/dist/connectors/api-connector.js +4 -3
  28. package/dist/connectors/guarded-fetch.d.ts +0 -6
  29. package/dist/connectors/guarded-fetch.js +1 -11
  30. package/dist/connectors/remote-mcp.js +10 -43
  31. package/dist/connectors/resource-uri.js +0 -4
  32. package/dist/d1.d.ts +0 -89
  33. package/dist/d1.js +0 -34
  34. package/dist/downstream-input.js +2 -2
  35. package/dist/execute.js +22 -13
  36. package/dist/executor-result.js +2 -1
  37. package/dist/executors/quickjs-runtime.js +2 -25
  38. package/dist/executors/quickjs.js +1 -1
  39. package/dist/guest-runtime.js +5 -3
  40. package/dist/guest-types.d.ts +5 -5
  41. package/dist/http/client.js +1 -1
  42. package/dist/http/errors.js +2 -2
  43. package/dist/http/json-schema.js +1 -1
  44. package/dist/http/node-transport.js +0 -1
  45. package/dist/http/oauth.js +1 -1
  46. package/dist/http/paging.js +6 -9
  47. package/dist/http/provider.js +1 -1
  48. package/dist/http/safety.js +1 -1
  49. package/dist/http/services.js +1 -8
  50. package/dist/http/tools.js +1 -7
  51. package/dist/http/vendor.js +7 -7
  52. package/dist/identity.js +2 -6
  53. package/dist/invocation-auth.js +2 -1
  54. package/dist/meta-tools.js +27 -27
  55. package/dist/native-tools.js +2 -1
  56. package/dist/oauth-handoff.js +2 -2
  57. package/dist/operation-vocabulary.js +1 -1
  58. package/dist/operator-record.js +1 -49
  59. package/dist/operator-ui/contract.d.ts +2 -2
  60. package/dist/operator-ui/generated.js +3 -3
  61. package/dist/program-writes.js +3 -8
  62. package/dist/provider-call.js +0 -7
  63. package/dist/provider.d.ts +1 -1
  64. package/dist/providers/_shared/google/kit.js +1 -6
  65. package/dist/providers/_shared/google/service-account.js +1 -3
  66. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  67. package/dist/providers/_shared/guide.js +3 -0
  68. package/dist/providers/_shared/json.js +12 -0
  69. package/dist/providers/_shared/rest/compact-index.js +1 -1
  70. package/dist/providers/_shared/rest/kit.js +1 -3
  71. package/dist/providers/_shared/rest/operation-index.js +1 -3
  72. package/dist/providers/_shared/rest/value-safety.js +26 -28
  73. package/dist/providers/breeze/api.js +2 -7
  74. package/dist/providers/breeze/index.js +4 -2
  75. package/dist/providers/breeze/tools.js +3 -4
  76. package/dist/providers/ccb/api.js +1 -6
  77. package/dist/providers/ccb/index.js +2 -1
  78. package/dist/providers/ccb/tools.js +1 -6
  79. package/dist/providers/cloudflare/api.js +2 -2
  80. package/dist/providers/cloudflare/index.js +2 -1
  81. package/dist/providers/cloudflare/safety.js +3 -3
  82. package/dist/providers/docs/tools.js +2 -10
  83. package/dist/providers/drive/tools.js +12 -25
  84. package/dist/providers/forms/tools.js +1 -9
  85. package/dist/providers/gmail/tools.js +5 -21
  86. package/dist/providers/overflow/api.js +1 -3
  87. package/dist/providers/overflow/index.js +2 -1
  88. package/dist/providers/overflow/tools.js +3 -7
  89. package/dist/providers/planning-center/api.js +2 -1
  90. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  91. package/dist/providers/planning-center/lib/json-api.js +1 -9
  92. package/dist/providers/planning-center/tools.js +2 -1
  93. package/dist/providers/sheets/tools.js +1 -9
  94. package/dist/providers/slides/tools.js +4 -13
  95. package/dist/providers/stripe/index.js +2 -1
  96. package/dist/providers/stripe/safety.js +2 -2
  97. package/dist/providers/tithely/api.js +2 -7
  98. package/dist/providers/tithely/index.js +3 -2
  99. package/dist/providers/tithely/tools.js +7 -5
  100. package/dist/providers/vercel/api.js +1 -6
  101. package/dist/providers/vercel/lib/pin.js +1 -1
  102. package/dist/providers/vercel/safety.js +1 -1
  103. package/dist/providers/vercel/tools.js +2 -1
  104. package/dist/redaction-marker.js +2 -2
  105. package/dist/registry.d.ts +1 -1
  106. package/dist/registry.js +5 -7
  107. package/dist/request-principal.js +1 -1
  108. package/dist/request-state.js +2 -2
  109. package/dist/routes/access-tokens.js +1 -1
  110. package/dist/routes/activity-disclosure.js +1 -1
  111. package/dist/routes/connect.js +2 -2
  112. package/dist/routes/credentials.js +1 -1
  113. package/dist/routes/mcp.js +4 -3
  114. package/dist/routes/oauth-management.js +1 -1
  115. package/dist/routes/operator.js +4 -3
  116. package/dist/routes/shared.d.ts +8 -8
  117. package/dist/routes/shared.js +9 -9
  118. package/dist/routes/ui-config.js +1 -1
  119. package/dist/routes/ui.js +1 -1
  120. package/dist/runtime/call-admission.js +1 -1
  121. package/dist/runtime/call.js +1 -1
  122. package/dist/runtime/settlement.js +1 -1
  123. package/dist/runtime/shared-read.js +2 -15
  124. package/dist/sent-secrets.d.ts +1 -2
  125. package/dist/sent-secrets.js +3 -8
  126. package/dist/server.js +4 -4
  127. package/dist/skills.js +8 -35
  128. package/dist/sqlite.d.ts +0 -19
  129. package/dist/sqlite.js +2 -83
  130. package/dist/storage/keys.js +16 -125
  131. package/dist/storage/sql-native.js +0 -101
  132. package/dist/storage/sql.d.ts +1 -24
  133. package/dist/storage/sql.js +1 -12
  134. package/dist/tool-error.js +4 -0
  135. package/dist/url-safety.js +0 -4
  136. package/dist/usage-guide.js +2 -2
  137. package/dist/version.d.ts +1 -1
  138. package/dist/version.js +1 -1
  139. package/dist/worker.js +1 -1
  140. package/documentation/README.md +6 -42
  141. package/documentation/architecture.md +86 -496
  142. package/documentation/auth.md +12 -25
  143. package/documentation/code-mode.md +90 -66
  144. package/documentation/deploying.md +19 -40
  145. package/documentation/integrating.md +4 -19
  146. package/documentation/meta-tools.md +6 -6
  147. package/documentation/migration-0.33.md +5 -5
  148. package/documentation/migration-0.34.md +2 -4
  149. package/documentation/migration-retired-layouts.md +74 -0
  150. package/documentation/operator-tests.md +19 -119
  151. package/documentation/operator-ui.md +3 -4
  152. package/documentation/provider-auth-migration.md +4 -10
  153. package/documentation/provider-migration-0.29.md +4 -15
  154. package/examples/worker/AGENTS.md +4 -6
  155. package/examples/worker/README.md +20 -115
  156. package/guides/connecting-agents.md +7 -30
  157. package/guides/how-it-works.md +2 -2
  158. package/guides/safety-and-trust.md +4 -1
  159. package/package.json +19 -19
  160. package/templates/node/README.md +9 -26
  161. package/templates/node/package.json +1 -1
  162. package/dist/auth/oauth-v2-migration.js +0 -135
  163. package/dist/provider-call-facts.d.ts +0 -115
  164. package/dist/provider-call.d.ts +0 -112
  165. package/dist/storage/d1-copy.js +0 -255
  166. package/dist/storage/sql-schema.d.ts +0 -6
  167. package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
  168. package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.38.0 — 2026-10-28
6
+
7
+ 0.38.0 is a minor release that retires the old storage and OAuth layout migrations, adds a configurable program return cap, makes in-program skill calls more forgiving, and moves the Eve helper to Eve 0.86.
8
+
9
+ Breaking: the pre-layout-3 OAuth grant converter, the Workers KV → D1 copy APIs and script, and the Node JSON-state import API and `connecta migrate-state` command are removed. A deployment still holding old state must first upgrade through 0.37.1, run `copyKvToD1` or `migrate-state` as needed, and open every OAuth grant once on that version before moving to 0.38.0; otherwise old OAuth grants need fresh consent and old KV or JSON state is not imported. Retired pause, identity, grant and trust options still fail construction, now as ordinary unknown options without migration hints. `documentation/migration-retired-layouts.md` covers verification and recovery. Deployments that already run on 0.37.x layouts need no change.
10
+
11
+ `execute.maxResultChars` sets how many serialized characters an `execute_code` program may return before Connecta pages it. The default stays 24,000, and paging notices now ask for pages sized to the configured cap. Programs can call `connecta.skill({ name })` and `connecta.skills(...)` as well as `connecta.skill(name)`, and the `execute_code` description states each host-call deadline truthfully when call and host-call timeouts differ.
12
+
13
+ Also breaking: `@zackbart/connecta-eve` now requires Eve `>=0.86.0 <0.87.0`, so Eve 0.84 apps upgrade Eve to 0.86.0 together with the helper. Eve 0.86.0 still pins undici 8.10.2, so pnpm apps rekey their override to `"eve@0.86.0>undici": "8.12.3"`; npm apps keep the same override. The rest of the release is contributor-facing: refreshed development dependencies, a slimmer test suite, consolidated internals and trimmed documentation, with no runtime behavior change.
14
+
15
+ ### Added
16
+
17
+ - Add `execute.maxResultChars`, the serialized characters an `execute_code` program may return before Connecta stashes the return behind a paging notice. The default stays 24,000; values from 4,000 to 1,000,000 are accepted, and anything else fails at construction. Emitted text shares the configured cap, and the paging notices that `execute_code` and direct calls return ask for pages sized to it instead of a fixed 3,829 bytes, never more than the stashed call's `maxResultBytes` or the 32 KiB bridge ceiling. A connector whose cap is below 3,829 bytes now gets a notice asking for the page size it actually serves. `describeConfig()` and the operator settings report it. Stash pages still obey each call's `maxResultBytes`. Connecta has no response-size limit, so keep the cap below what every client accepts: Claude Code passes at most 50,000 characters of text.
18
+
19
+ ### Changed
20
+
21
+ - Accept the shapes models copy from the top-level `skills` tool into programs. `connecta.skill({ name })` now works like `connecta.skill(name)`, and `connecta.skills` is an undeclared alias of `connecta.skill` with the same host-call accounting, so neither costs a failed run. Any other argument fails `invalid_args` naming `connecta.skill("usage")`. The `execute_code` description, the server instructions, the usage guide and the program-error hint now show the in-program form `connecta.skill("usage")` and call the top-level one the skills tool, instead of giving `skills({ name: "usage" })` beside the guest API.
22
+ - Consolidate internal helpers and remove unused code without changing runtime behavior.
23
+ - Trim historical narration and duplicate procedures from documentation, correct contributor evidence references, and remove unreferenced eval baselines.
24
+ - Refresh development dependencies: TypeScript 6.0, Vitest 5 with Vite 8.3, `@cloudflare/vitest-plugin` 1.4 (renamed from `@cloudflare/vitest-pool-workers`) with Wrangler 4.151, Playwright 1.64, and current oxlint, oxfmt, knip, esbuild, tsx, React and Radix releases, in core and the Eve helper. Published dependency and peer ranges are unchanged, and deployments need no change.
25
+ - **Breaking:** `@zackbart/connecta-eve` now requires Eve 0.86 (`>=0.86.0 <0.87.0`) and is tested against Eve 0.86.0; Eve 0.84 apps must upgrade Eve to 0.86.0 together with the helper. Eve 0.86.0 still pins undici 8.10.2, so pnpm apps rekey their override to `"eve@0.86.0>undici": "8.12.3"`; npm apps keep the same override.
26
+ - Contributor test suite slimmed: about 100 regression, lifetime, parity and meta-guard suites are removed, and workerd now runs only the suites that need real Workers bindings or isolates. No runtime behavior changes.
27
+
28
+ ### Fixed
29
+
30
+ - The `execute_code` description no longer overstates the deadline of `connecta.result`, `connecta.skill` and `connecta.read`. When `calls.defaultTimeoutMs` differs from `execute.hostCallTimeoutMs`, it states both, for example `Limits: 20 host calls, 10 writes, 25s/call (15s result/skill/read).`, instead of advertising the call default for every host call. Enforcement is unchanged; deployments without `calls.defaultTimeoutMs` see the same `15s/host call` text as before.
31
+
32
+ ### Removed
33
+
34
+ - **Breaking:** Remove the pre-layout-3 OAuth grant converter and cleanup, Workers KV → D1 copy APIs/types/script, and Node JSON-state import API and `connecta migrate-state` command. Retired pause, identity, grant, and trust options still fail construction as ordinary unknown options, without migration hints. Before upgrading, deployments with old state must upgrade through 0.37.1, run `copyKvToD1` or `migrate-state` as needed, and open every OAuth grant once on that version. Old OAuth grants otherwise require fresh consent; old KV/JSON state is not imported. Follow `documentation/migration-retired-layouts.md` for verification and recovery.
35
+
5
36
  ## 0.37.1 — 2026-10-28
6
37
 
7
38
  0.37.1 is a patch release of fixes and request-path performance work. It needs no configuration change, and deployments on 0.37.0 can upgrade in place. One type change is additive: connector status gains a `per_caller` state, so code that switches exhaustively over status states must handle it.
package/README.md CHANGED
@@ -91,8 +91,8 @@ on Node, in Docker, or on Cloudflare Workers. The
91
91
  | [Safety and trust](./guides/safety-and-trust.md) | Uncertain changes, secrets, read-only access, and your responsibilities. |
92
92
  | [Operator documentation](./documentation/README.md) | Setup, feature availability, upgrades, and exact contracts. |
93
93
 
94
- <a id="existing-client-tokens"></a>
95
-
96
94
  Upgrading? Follow the [upgrade guide](./documentation/deploying.md#upgrade-an-existing-deployment).
95
+ Pre-0.29 storage and OAuth layouts must first migrate on 0.37.1; see the
96
+ [required bridge upgrade](./documentation/migration-retired-layouts.md).
97
97
  Breaking changes are expected before 1.0. See the [changelog](./CHANGELOG.md)
98
98
  and [security policy](./SECURITY.md).
package/bin/connecta.mjs CHANGED
@@ -4,7 +4,7 @@ import { copyFile, cp, lstat, mkdtemp, readFile, rename, rm, symlink, writeFile
4
4
  import { CONNECTA_VERSION } from "./version.mjs";
5
5
  import { META_TOOL_NAMES } from "./meta-tool-names.mjs";
6
6
  import { basename, dirname, join, resolve } from "node:path";
7
- import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { fileURLToPath } from "node:url";
8
8
 
9
9
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
10
  const [command, ...args] = process.argv.slice(2);
@@ -19,7 +19,6 @@ function shellCd(path) {
19
19
  function usage() {
20
20
  console.log(`Usage:
21
21
  connecta init [directory]
22
- connecta migrate-state <state.json> <connecta.sqlite>
23
22
  CONNECTA_TOKEN=<bearer> connecta doctor [--config] [--url http://localhost:8787] [--pool <name>]
24
23
  CF_ACCESS_CLIENT_ID=<id> CF_ACCESS_CLIENT_SECRET=<secret> connecta doctor --url https://worker.example`);
25
24
  }
@@ -307,46 +306,9 @@ async function doctor() {
307
306
  );
308
307
  }
309
308
 
310
- /**
311
- * One-shot copy of a 0.28 `fileStorage` JSON state file into the SQLite
312
- * database 0.29 stores everything in. Stop the old deployment first; the file
313
- * is read, never changed.
314
- */
315
- async function migrateState() {
316
- if (args.length !== 2 || args.some((arg) => arg.startsWith("--"))) {
317
- usage();
318
- process.exitCode = 1;
319
- return;
320
- }
321
- const [statePath, databasePath] = args.map((arg) => resolve(process.cwd(), arg));
322
- const { importStateFile, openSqlite } = await import(pathToFileURL(join(packageRoot, "dist", "sqlite.js")).href);
323
- let database;
324
- try {
325
- database = openSqlite(databasePath);
326
- } catch {
327
- throw new Error("State migration failed: could not open the SQLite database");
328
- }
329
- let closeFailed = false;
330
- try {
331
- const result = importStateFile(database, statePath);
332
- console.log(
333
- `Imported ${result.imported} entries from ${statePath} into ${databasePath}` +
334
- ` (${result.kept} already present, ${result.expired} expired).`,
335
- );
336
- } finally {
337
- try {
338
- database.close();
339
- } catch {
340
- closeFailed = true;
341
- }
342
- }
343
- if (closeFailed) throw new Error("State migration failed: could not close the SQLite database");
344
- }
345
-
346
309
  try {
347
310
  if (command === "init") await init();
348
311
  else if (command === "doctor") await doctor();
349
- else if (command === "migrate-state") await migrateState();
350
312
  else {
351
313
  usage();
352
314
  process.exitCode = command === "--help" || command === "-h" ? 0 : 1;
package/bin/version.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs.
2
2
  // Workers-safe: no runtime filesystem read or deployment override.
3
- export const CONNECTA_VERSION = "0.37.1";
3
+ export const CONNECTA_VERSION = "0.38.0";
@@ -1,3 +1,4 @@
1
+ import { sha256Hex } from "./bytes.js";
1
2
  import { withAuthenticationDeadline } from "./auth-admission.js";
2
3
  import { authorizationCredential, isMachineCredential } from "./inbound-credential.js";
3
4
  import { assertKnownOptions, ConfigError, optionsOf, readPlain } from "./config-schema.js";
@@ -137,11 +138,8 @@ function bytesToBase64Url(bytes) {
137
138
  binary += String.fromCharCode(byte);
138
139
  return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
139
140
  }
140
- function bytesToHex(bytes) {
141
- return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
142
- }
143
141
  async function hashToken(token) {
144
- return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token))));
142
+ return sha256Hex(token);
145
143
  }
146
144
  const REVISION_RE = /^[A-Za-z0-9_-]{22}$/u;
147
145
  /** Whether `value` has the shape of a slot revision. */
@@ -1,4 +1,4 @@
1
- // Code-declared agents (#837): the names deployment code lists in `agents`,
1
+ // Code-declared agents: the names deployment code lists in `agents`,
2
2
  // the identity an admitted agent carries, and the built-in providers trusted
3
3
  // to assert it. Pure, so core reads it without reaching credential modules.
4
4
  import { ConfigError } from "./config-schema.js";
@@ -1833,4 +1833,5 @@ var Bc = new Set(["error", "warn", "info", "debug", "trace"]), Nt = { samplingRa
1833
1833
  };
1834
1834
  var ud = Mn(Z);
1835
1835
  function jc(t) { let e = { ...t }, r = he(e), s = qn({ options: e, apiClient: r }), n = new Jt({ publishableKey: e.publishableKey, secretKey: e.secretKey, samplingRate: .1, ...e.sdkMetadata ? { sdk: e.sdkMetadata.name, sdkVersion: e.sdkMetadata.version } : {}, ...e.telemetry || {} }); return { ...r, ...s, telemetry: n }; }
1836
- export { jc as createClerkClient };
1836
+ const createClerkClient = jc;
1837
+ export { createClerkClient };
@@ -1,3 +1,4 @@
1
+ import { bytesToHex, sha256Hex } from "../bytes.js";
1
2
  import { UnauthorizedError } from "@modelcontextprotocol/client";
2
3
  import { Effect, Exit } from "effect";
3
4
  import { byteReadResponse } from "../byte-read-response.js";
@@ -8,12 +9,12 @@ import { keepErrorName } from "../redaction-identity.js";
8
9
  import { fromSignal, runEdge } from "../runtime/run.js";
9
10
  import { Settlement } from "../runtime/settlement.js";
10
11
  import { SentSecrets } from "../sent-secrets.js";
11
- import { OAUTH_FLOW_TTL_SECONDS, OAUTH_REFRESH_LEASE_SECONDS, oauthFlowKeys, oauthGrantKeys, oauthRefreshActiveKeys, oauthRefreshKeys, oauthRefreshSpentKeys, } from "../storage/keys.js";
12
+ import { OAUTH_FLOW_TTL_SECONDS, OAUTH_REFRESH_LEASE_SECONDS, oauthFlowKeys, oauthGrantKeys, retiredOAuthKeys, oauthRefreshActiveKeys, oauthRefreshKeys, oauthRefreshSpentKeys, } from "../storage/keys.js";
12
13
  import { authenticateRemoteClient, downstreamClientMetadata, remoteClientAuthMethod, } from "./downstream-client-metadata.js";
13
14
  import { revokeDownstreamGrant } from "./downstream-revocation.js";
14
15
  import { authAwait, authCall, authCatch, authDetached, authFlow, authInvoke, authMethod, authOwnMethod, authProgram, authStorage, authThen, authValue, registerAuth, registerAuthMethod, } from "./native.js";
15
16
  import { isOAuthCredentialRequest, isRefreshTokenRequest, refreshResponseOutcome, sdkSafeTokenResponse, tokenGrantType, } from "./oauth-token-response.js";
16
- import { deleteV2Keys, discoveryIssuer, readV2Grant, } from "./oauth-v2-migration.js";
17
+ import { discoveryIssuer } from "./oauth-grant.js";
17
18
  /** @internal Keep the SDK's cloned body reads inside the code exchange's request. */
18
19
  export function codeExchangeResponse(response, signal) {
19
20
  if (!response.body)
@@ -105,12 +106,11 @@ export class OAuthRequestNotSentError extends ConnectorCallError {
105
106
  function randomState() {
106
107
  const bytes = new Uint8Array(32);
107
108
  crypto.getRandomValues(bytes);
108
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
109
+ return bytesToHex(bytes);
109
110
  }
110
111
  /** Lowercase hex SHA-256 of a consent's `state`: the name of its flow record. */
111
112
  export async function oauthStateDigest(state) {
112
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(state));
113
- return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
113
+ return sha256Hex(state);
114
114
  }
115
115
  function storedRefreshSpent(raw) {
116
116
  const value = raw === null ? undefined : parsed(raw);
@@ -818,7 +818,7 @@ export class KvOAuthProvider {
818
818
  if (client)
819
819
  this.addClientAuthentication = (headers, params) => authenticateRemoteClient(client, headers, params);
820
820
  }
821
- /** Stored facts only: no migration, sealing writes, refresh or discovery. */
821
+ /** Stored facts only: no sealing writes, refresh or discovery. */
822
822
  storedAuthorization(issuer) {
823
823
  return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "storedAuthorization", [issuer]));
824
824
  }
@@ -834,39 +834,6 @@ export class KvOAuthProvider {
834
834
  wait(work, signal = this.signal) {
835
835
  return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "wait", [work, signal]));
836
836
  }
837
- /** Legacy helpers must fence their inner reads/deletes too, not just our join. */
838
- preparationStorage(signal) {
839
- if (!signal)
840
- return this.storage;
841
- const storage = this.storage;
842
- return {
843
- ...storage,
844
- get: authMethod((key) => {
845
- return authInvoke(this, "wait", [
846
- authMethod(() => {
847
- return authStorage(storage, "get", [key]);
848
- }),
849
- signal,
850
- ]);
851
- }),
852
- list: authMethod((prefix) => {
853
- return authInvoke(this, "wait", [
854
- authMethod(() => {
855
- return authStorage(storage, "list", [prefix]);
856
- }),
857
- signal,
858
- ]);
859
- }),
860
- delete: authMethod((key) => {
861
- return authInvoke(this, "wait", [
862
- authMethod(() => {
863
- return authStorage(storage, "delete", [key]);
864
- }),
865
- signal,
866
- ]);
867
- }),
868
- };
869
- }
870
837
  // --- the grant record ---------------------------------------------------
871
838
  /** The grant's additional authenticated data: record and epoch. */
872
839
  grantLabel(epoch) {
@@ -884,21 +851,13 @@ export class KvOAuthProvider {
884
851
  return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "openGrant", [raw, signal]));
885
852
  }
886
853
  /**
887
- * The grant as stored now, migrating layout 2 on first sight. A plaintext
854
+ * The current grant record. A plaintext
888
855
  * body, written before the vault could seal, is sealed where it lies by
889
856
  * compare-and-set; a failure leaves it readable for the next write to seal.
890
857
  */
891
858
  readGrant(signal = this.signal) {
892
859
  return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "readGrant", [signal]));
893
860
  }
894
- /** Layout 2 migration and its cleanup retain the reading operation's owner. */
895
- migrate(signal) {
896
- return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "migrate", [signal]));
897
- }
898
- /** Deletion is retried on every read, including reads after a reset. */
899
- cleanupV2(raw, signal = this.signal) {
900
- return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "cleanupV2", [raw, signal]));
901
- }
902
861
  /** The grant a bound flow reads: a replaced epoch fails it. */
903
862
  boundGrant(signal = this.signal) {
904
863
  return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "boundGrant", [signal]));
@@ -928,7 +887,6 @@ export class KvOAuthProvider {
928
887
  throw this.flowSuperseded();
929
888
  return {
930
889
  epoch: `${ACTIVE_EPOCH_PREFIX}${crypto.randomUUID()}`,
931
- ...(grant.cleanupPending ? { cleanupPending: true } : {}),
932
890
  body,
933
891
  };
934
892
  }
@@ -1410,7 +1368,6 @@ export class KvOAuthProvider {
1410
1368
  const stored = {
1411
1369
  connectaOAuth: GRANT_VERSION,
1412
1370
  epoch: grant.epoch,
1413
- ...(grant.cleanupPending ? { cleanupPending: true } : {}),
1414
1371
  ...(grant.flow !== undefined ? { flow: grant.flow } : {}),
1415
1372
  };
1416
1373
  if (Object.keys(grant.body).length > 0) {
@@ -1466,7 +1423,6 @@ export class KvOAuthProvider {
1466
1423
  }
1467
1424
  return {
1468
1425
  epoch: stored.epoch,
1469
- ...(stored.cleanupPending ? { cleanupPending: true } : {}),
1470
1426
  ...(stored.flow !== undefined ? { flow: stored.flow } : {}),
1471
1427
  body: grantBody(body),
1472
1428
  };
@@ -1497,10 +1453,17 @@ export class KvOAuthProvider {
1497
1453
  }),
1498
1454
  signal,
1499
1455
  ]));
1500
- if (raw === null)
1501
- raw = (yield authInvoke(this, "migrate", [signal]));
1502
- if (raw !== null && storedGrant(raw)?.cleanupPending)
1503
- raw = (yield authInvoke(this, "cleanupV2", [raw, signal]));
1456
+ if (raw === null) {
1457
+ // Inspect names only: retired credentials are never opened or copied.
1458
+ const keys = (yield authInvoke(this, "wait", [
1459
+ authMethod(() => authStorage(this.storage, "list", [retiredOAuthKeys.scan])),
1460
+ signal,
1461
+ ]));
1462
+ if (keys.some((key) => retiredOAuthKeys.prefixes.some((prefix) => key.startsWith(prefix))))
1463
+ (this.sealer ?? console).warn(`[connecta] connector "${this.connectorId}" has an unsupported pre-layout-3 OAuth grant; ` +
1464
+ "treating it as absent. Connect again to authorize, or restore a backup and run the " +
1465
+ "OAuth migration on Connecta 0.37.1 before upgrading.");
1466
+ }
1504
1467
  const grant = (yield authInvoke(this, "openGrant", [raw, signal]));
1505
1468
  if (raw !== null && this.sealer && storedGrant(raw)?.body !== undefined) {
1506
1469
  const sealed = (yield authInvoke(this, "encodeGrant", [grant, signal]));
@@ -1515,99 +1478,6 @@ export class KvOAuthProvider {
1515
1478
  return { raw, grant };
1516
1479
  }.bind(this));
1517
1480
  }, KvOAuthProvider.prototype.readGrant);
1518
- registerAuthMethod(KvOAuthProvider.prototype, "migrate", function (signal) {
1519
- return authFlow(function* () {
1520
- const sealer = this.sealer;
1521
- const found = (yield authInvoke(this, "wait", [
1522
- authMethod(() => {
1523
- return authCall(readV2Grant, [
1524
- this.preparationStorage(signal),
1525
- sealer && {
1526
- ...sealer,
1527
- open: authMethod((label, value) => {
1528
- return authInvoke(this, "wait", [
1529
- authMethod(() => {
1530
- return authInvoke(sealer, "open", [label, value]);
1531
- }),
1532
- signal,
1533
- ]);
1534
- }),
1535
- },
1536
- ]);
1537
- }),
1538
- signal,
1539
- ]));
1540
- if (!found)
1541
- return null;
1542
- const prefix = found.disconnected ? DISCONNECTED_EPOCH_PREFIX : ACTIVE_EPOCH_PREFIX;
1543
- const encoded = (yield authInvoke(this, "encodeGrant", [
1544
- {
1545
- epoch: `${prefix}${crypto.randomUUID()}`,
1546
- body: found.body,
1547
- cleanupPending: true,
1548
- },
1549
- signal,
1550
- ]));
1551
- let written;
1552
- try {
1553
- written = (yield authInvoke(this, "wait", [
1554
- authMethod(() => {
1555
- return authStorage(this.storage, "compareAndSet", [GRANT, null, encoded]);
1556
- }),
1557
- signal,
1558
- ]));
1559
- }
1560
- catch {
1561
- if (signal?.aborted)
1562
- throw aborted(signal);
1563
- throw this.credentialWriteError();
1564
- }
1565
- return (yield written
1566
- ? authValue(() => encoded)
1567
- : authInvoke(this, "wait", [
1568
- authMethod(() => {
1569
- return authStorage(this.storage, "get", [GRANT]);
1570
- }),
1571
- signal,
1572
- ]));
1573
- }.bind(this));
1574
- }, KvOAuthProvider.prototype.migrate);
1575
- registerAuthMethod(KvOAuthProvider.prototype, "cleanupV2", function (raw, signal) {
1576
- signal = signal === undefined ? this.signal : signal;
1577
- return authFlow(function* () {
1578
- try {
1579
- if (!(yield authInvoke(this, "wait", [
1580
- authMethod(() => {
1581
- return authCall(deleteV2Keys, [this.preparationStorage(signal)]);
1582
- }),
1583
- signal,
1584
- ])))
1585
- return raw;
1586
- const grant = (yield authInvoke(this, "openGrant", [raw, signal]));
1587
- delete grant.cleanupPending;
1588
- const cleared = (yield authInvoke(this, "encodeGrant", [grant, signal]));
1589
- if ((yield authInvoke(this, "wait", [
1590
- authMethod(() => {
1591
- return authStorage(this.storage, "compareAndSet", [GRANT, raw, cleared]);
1592
- }),
1593
- signal,
1594
- ])))
1595
- return cleared;
1596
- return (yield authInvoke(this, "wait", [
1597
- authMethod(() => {
1598
- return authStorage(this.storage, "get", [GRANT]);
1599
- }),
1600
- signal,
1601
- ]));
1602
- }
1603
- catch {
1604
- if (signal?.aborted)
1605
- throw aborted(signal);
1606
- // The durable marker survives both deletion and marker-clear failures.
1607
- return raw;
1608
- }
1609
- }.bind(this));
1610
- }, KvOAuthProvider.prototype.cleanupV2);
1611
1481
  registerAuthMethod(KvOAuthProvider.prototype, "boundGrant", function (signal) {
1612
1482
  signal = signal === undefined ? this.signal : signal;
1613
1483
  return authFlow(function* () {
@@ -2763,10 +2633,7 @@ export class KvOAuthProvider {
2763
2633
  for (let attempt = 0; attempt < MAX_GRANT_WRITES; attempt++) {
2764
2634
  const { raw, grant } = (yield authInvoke(this, "readGrant", [null]));
2765
2635
  const carried = preserveClient && !operatorDisconnected ? this.carriableClient(grant) : undefined;
2766
- const encoded = (yield authInvoke(this, "encodeGrant", [
2767
- { epoch, body: carried ?? {}, cleanupPending: true },
2768
- null,
2769
- ]));
2636
+ const encoded = (yield authInvoke(this, "encodeGrant", [{ epoch, body: carried ?? {} }, null]));
2770
2637
  let written;
2771
2638
  try {
2772
2639
  written = (yield authStorage(this.storage, "compareAndSet", [GRANT, raw, encoded]));
@@ -2777,7 +2644,6 @@ export class KvOAuthProvider {
2777
2644
  if (written) {
2778
2645
  this.refreshCoordinator?.retire(grant.epoch);
2779
2646
  try {
2780
- (yield authInvoke(this, "cleanupV2", [encoded, null]));
2781
2647
  (yield authInvoke(this, "sweepConsents", []));
2782
2648
  (yield authInvoke(this, "sweepRefreshLeases", []));
2783
2649
  }
@@ -4,7 +4,7 @@ import { fromSignal, runEdge } from "../runtime/run.js";
4
4
  import { classifyHost } from "../url-safety.js";
5
5
  import { authenticateRemoteClient, remoteClientAuthMethod, } from "./downstream-client-metadata.js";
6
6
  import { authCall, authFlow, authProgram, registerAuth } from "./native.js";
7
- import { discoveryIssuer } from "./oauth-v2-migration.js";
7
+ import { discoveryIssuer } from "./oauth-grant.js";
8
8
  /** Local removal succeeded; provider-side removal needs the operator's attention. */
9
9
  export class OAuthRevocationError extends ConnectorCallError {
10
10
  constructor() {
@@ -0,0 +1,11 @@
1
+ function plainObject(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ /** The issuer a discovery record names, as the SDK derives it. */
5
+ export function discoveryIssuer(state) {
6
+ if (!plainObject(state) || typeof state.authorizationServerUrl !== "string")
7
+ return undefined;
8
+ const metadata = state.authorizationServerMetadata;
9
+ const issuer = plainObject(metadata) && typeof metadata.issuer === "string" ? metadata.issuer : state.authorizationServerUrl;
10
+ return issuer === "" ? undefined : issuer;
11
+ }
@@ -3,7 +3,7 @@ import { authCall, authFlow, authInvoke, authProgram, registerAuth } from "./nat
3
3
  // The token endpoint's answers as downstream OAuth classifies them and the SDK
4
4
  // gets to parse them: a refresh's re-consent verdict, and every failure
5
5
  // rebuilt from its OAuth `error` code alone, so nothing else a token endpoint
6
- // wrote reaches the SDK's console output (#695).
6
+ // wrote reaches the SDK's console output.
7
7
  /** The `grant_type` of a token request, or undefined for any other request. */
8
8
  export function tokenGrantType(init) {
9
9
  if ((init?.method ?? "GET").toUpperCase() !== "POST")
@@ -1,3 +1,4 @@
1
+ import { decodeBase64url } from "../bytes.js";
1
2
  import { runEdge } from "../runtime/run.js";
2
3
  import { authCall, authCatch, authFetch, authFlow, authInvoke, authMethod, authProgram, registerAuth, } from "./native.js";
3
4
  import { AGENT_NAMESPACE, agentSubjectId, trustAgentProvider } from "../agent-identity.js";
@@ -14,7 +15,7 @@ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
14
15
  function bytes(value) {
15
16
  if (!/^[A-Za-z0-9_-]+(?![\s\S])/.test(value))
16
17
  throw new Error("Invalid JWT encoding");
17
- return Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0));
18
+ return decodeBase64url(value);
18
19
  }
19
20
  function record(value) {
20
21
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -1,3 +1,4 @@
1
+ import { toolError } from "./tool-error.js";
1
2
  import { RequestScope, requestOwner } from "./runtime/request-scope.js";
2
3
  import { upwardStep } from "./upward.js";
3
4
  import { authAwait, authCall, authInvoke, authFlow, authMethod, registerAuthMethod, authOwnMethod, } from "./auth/native.js";
@@ -17,8 +18,7 @@ import { canonicalState as canonical, requestDigest as digest, stateObject as ob
17
18
  const INPUT_KEY = "connecta_auth";
18
19
  const MESSAGE = "Connect this service in your browser, then retry the request.";
19
20
  function failure(code, message) {
20
- const structuredContent = { ok: false, error: { code, message, retryable: false } };
21
- return { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent, isError: true };
21
+ return toolError({ code, message, retryable: false }, { ok: false });
22
22
  }
23
23
  /** Request-local auth recovery at the MCP boundary, never inside a guest provider. */
24
24
  export class AuthElicitation {
@@ -1,7 +1,7 @@
1
1
  import type { ConnectaBranding, ConnectaTheme, UiAuthConfig } from "./types.js";
2
2
  /** Connecta's default monochrome "C" mark. */
3
3
  export declare const CONNECTA_FAVICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\">\n <style>\n .fg { fill: #000 }\n @media (prefers-color-scheme: dark) { .fg { fill: #fff } }\n </style>\n <path class=\"fg\" d=\"M27 9.4A13 13 0 1 0 27 22.6l-4.4-2.5a8 8 0 1 1 0-8.2z\"/>\n</svg>";
4
- export interface ResolvedTheme {
4
+ interface ResolvedTheme {
5
5
  accent?: string;
6
6
  radius?: string;
7
7
  fontFamily?: string;
@@ -28,26 +28,12 @@ interface ResolvedBranding {
28
28
  theme: ResolvedTheme;
29
29
  }
30
30
  export declare function resolveBranding(branding?: ConnectaBranding): ResolvedBranding;
31
- /**
32
- * Read a theme the way branding URLs are read: gate every field, drop what
33
- * fails, never throw. This runs during `createConnecta`, so a malformed value
34
- * has to fall back to the stylesheet default instead of refusing to serve the
35
- * page. `droppedThemeTokens` names the drops for the startup warning.
36
- */
37
- export declare function resolveTheme(theme?: ConnectaTheme): ResolvedTheme;
38
31
  /**
39
32
  * Names of the theme tokens the operator set that failed their gate. Same
40
33
  * contract as `droppedBrandingUrls`: rendering falls back silently, so this is
41
34
  * the only place an operator learns their value never reached the page.
42
35
  */
43
36
  export declare function droppedThemeTokens(theme?: ConnectaTheme): string[];
44
- /**
45
- * The resolved theme as a `:root` block, or "" when a deployment configured
46
- * nothing. It is emitted after the stylesheet so it overrides the defaults.
47
- * There is no escaping here: every value has already passed a gate above, and
48
- * anything that would need escaping is dropped rather than rewritten.
49
- */
50
- export declare function themeCss(theme: ResolvedTheme): string;
51
37
  /** Text or attribute value, escaped for any position in an HTML document. */
52
38
  export declare function escapeHtml(value: string): string;
53
39
  export interface PageLayout {
package/dist/branding.js CHANGED
@@ -71,7 +71,7 @@ const COLOR_SCHEMES = ["system", "light", "dark"];
71
71
  * has to fall back to the stylesheet default instead of refusing to serve the
72
72
  * page. `droppedThemeTokens` names the drops for the startup warning.
73
73
  */
74
- export function resolveTheme(theme) {
74
+ function resolveTheme(theme) {
75
75
  const accent = trimmedString(theme?.accent);
76
76
  const fontFamily = trimmedString(theme?.fontFamily);
77
77
  const monoFamily = trimmedString(theme?.monoFamily);
@@ -175,7 +175,7 @@ function accentTokens(accent) {
175
175
  * There is no escaping here: every value has already passed a gate above, and
176
176
  * anything that would need escaping is dropped rather than rewritten.
177
177
  */
178
- export function themeCss(theme) {
178
+ function themeCss(theme) {
179
179
  const derived = theme.accent ? accentTokens(theme.accent) : undefined;
180
180
  const declarations = [
181
181
  theme.accent ? `--accent:${theme.accent}` : "",
package/dist/bytes.js ADDED
@@ -0,0 +1,42 @@
1
+ /** Lowercase hexadecimal, preserving each byte's leading zero. */
2
+ export function bytesToHex(bytes) {
3
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
4
+ }
5
+ export async function sha256Hex(value) {
6
+ return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))));
7
+ }
8
+ /** UTF-8 byte count without allocating encoded bytes; stops once the limit is exceeded. */
9
+ export function utf8Length(value, limit = Infinity) {
10
+ let bytes = 0;
11
+ for (let i = 0; i < value.length; i++) {
12
+ const code = value.charCodeAt(i);
13
+ if (code < 0x80)
14
+ bytes++;
15
+ else if (code < 0x800)
16
+ bytes += 2;
17
+ else if (code >= 0xd800 &&
18
+ code <= 0xdbff &&
19
+ value.charCodeAt(i + 1) >= 0xdc00 &&
20
+ value.charCodeAt(i + 1) <= 0xdfff) {
21
+ bytes += 4;
22
+ i++;
23
+ }
24
+ else
25
+ bytes += 3;
26
+ if (bytes > limit)
27
+ return bytes;
28
+ }
29
+ return bytes;
30
+ }
31
+ export function decodeBase64(value) {
32
+ const binary = atob(value);
33
+ const bytes = new Uint8Array(binary.length);
34
+ for (let index = 0; index < binary.length; index += 1)
35
+ bytes[index] = binary.charCodeAt(index);
36
+ return bytes;
37
+ }
38
+ /** Optional padding preserves callers that pad before handing input to atob. */
39
+ export function decodeBase64url(value, pad = false) {
40
+ const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
41
+ return decodeBase64(pad ? base64 + "=".repeat((4 - (base64.length % 4)) % 4) : base64);
42
+ }
@@ -1,8 +1,5 @@
1
- import { Budget, callScopeKnown, chargeView, recordedService, setViewService } from "./runtime/call.js";
2
- import { chargeCallBudget } from "./runtime/call-admission.js";
3
- export function attachCallAdmission(ctx, permit) {
4
- setViewService(ctx, Budget, { charge: chargeCallBudget(permit, ctx.signal) });
5
- }
1
+ // The Promise view of the call's admission service; authority stays internal.
2
+ import { Budget, callScopeKnown, chargeView, recordedService } from "./runtime/call.js";
6
3
  export function chargeConnectorCall(ctx) {
7
4
  return recordedService(ctx, Budget) || callScopeKnown(ctx) ? chargeView(ctx) : Promise.resolve();
8
5
  }
@@ -43,6 +43,3 @@ export function recordRecovery(ctx, facts) {
43
43
  recordFor(ctx);
44
44
  serviceFor(ctx, Recovery)?.recordNow?.(facts);
45
45
  }
46
- export function recoveryFor(ctx) {
47
- return ctx ? serviceFor(ctx, RecordedRecovery)?.facts : undefined;
48
- }
@@ -1,7 +1,8 @@
1
+ import { bytesToHex } from "./bytes.js";
1
2
  const encoder = new TextEncoder();
2
3
  async function fingerprintBytes(bytes) {
3
4
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(bytes)));
4
- const hex = [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("");
5
+ const hex = bytesToHex(digest);
5
6
  return `sha256:${bytes.byteLength}:${hex}`;
6
7
  }
7
8
  /** Fingerprint an already serialized catalog without parsing/reserializing it. */
@@ -12,20 +13,3 @@ export async function fingerprintSerializedCatalog(serializedTools) {
12
13
  byteLength: bytes.byteLength,
13
14
  };
14
15
  }
15
- /**
16
- * Capture every persisted/discovered field in one deterministic snapshot.
17
- * Object key order is allowed to cause a conservative extra write; changing
18
- * any serialized field must change the digest.
19
- */
20
- export async function snapshotCatalog(tools) {
21
- const serializedTools = JSON.stringify(tools);
22
- if (serializedTools === undefined) {
23
- throw new TypeError("Tool catalog is not JSON-serializable.");
24
- }
25
- const bytes = encoder.encode(serializedTools);
26
- return {
27
- tools,
28
- fingerprint: await fingerprintBytes(bytes),
29
- serializedBytes: bytes,
30
- };
31
- }