@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
package/AGENTS.md CHANGED
@@ -6,14 +6,18 @@ symlink to this file so every agent works from the same conventions.
6
6
  A single MCP endpoint aggregating downstream connectors (remote MCP servers and
7
7
  plain HTTP APIs) behind seven meta-tools, `execute_code` among them. Every
8
8
  deployment configures an executor, and agents reach connectors by writing
9
- JavaScript against it. One fetch-native core, running on both Node and
10
- Cloudflare Workers.
9
+ JavaScript against it. One fetch-native core on Effect behind a Promise API,
10
+ running on both Node and Cloudflare Workers. Two `planned` decisions change
11
+ that picture — resumable writes with an eighth tool, and an artifacts module —
12
+ and until they ship, the code and the guides describe what exists.
11
13
 
12
14
  - **[`ethos.md`](./ethos.md) is the constitution.** It states what connecta is
13
15
  and isn't, and its decisions table carries a verdict for every shape already
14
- considered — refused, removed, provisional, or gated. Check the table before
15
- designing or building anything: a `refused` row is a "no" with the reason
16
- attached, and a `removed` row (toolkits
16
+ considered — accepted, planned, refused, removed, provisional, or gated.
17
+ Check the table before designing or building anything: a `planned` row is a
18
+ "yes" that has not shipped, binding on review now and absent from the guides
19
+ until it lands, so never document it there as if it exists; a `refused` row
20
+ is a "no" with the reason attached, and a `removed` row (toolkits
17
21
  [#178](https://github.com/zackbart/connecta/issues/178), proactive credential
18
22
  liveness [#179](https://github.com/zackbart/connecta/issues/179), the classic
19
23
  executor-free surface
@@ -30,24 +34,28 @@ Cloudflare Workers.
30
34
 
31
35
  `npm run check` must pass before you claim anything is done — `check:docs` →
32
36
  `check:operator-ui` → `check:lint` → `check:unused` → `typecheck` → `test`
33
- (both vitest projects) → `build` → `check:examples`. It is also the `prepack`
34
- hook. `npm run release:check` adds `check:security` and `check:package` and is
35
- what CI runs on publish; use it when touching packaging, dependencies, or
36
- exports.
37
+ (both vitest projects) → `test:browser` (the operator UI in Chromium; run
38
+ `npm run test:browser:install` once per machine, since `check` is also what
39
+ `npm pack` runs) → `build` (which prunes unreachable declarations) →
40
+ `check:declarations` (no Effect type in what ships) → `check:bundle` (per-entry
41
+ gzip caps in `scripts/bundle-budget.json`) → `check:examples`. It is also the
42
+ `prepack` hook. `npm run release:check` adds `check:security` and `check:package` and is
43
+ what CI runs on every pull request, on `main`, and again on publish; use it
44
+ when touching packaging, dependencies, or exports.
37
45
 
38
46
  ## The map
39
47
 
40
48
  - [`ethos.md`](./ethos.md) — what connecta is, what it refuses to be, the
41
49
  decisions table, and the invariants every change must preserve. Check it
42
50
  before building something new; "we already decided not to" is a real answer
43
- there, and its removed/provisional verdicts override anything staler.
44
- - [`documentation/`](./documentation/) — per-subsystem guides for agents
45
- working on the repo. All of them are written now
46
- ([#348](https://github.com/zackbart/connecta/issues/348)); the retired manual
47
- (`docs/<name>.md`) survives only in git history, which is worth mining for
48
- rationale a rewrite dropped and worth nothing where it disagrees with
49
- `ethos.md`. Start with `architecture.md`, then the guide for the subsystem
50
- you are changing.
51
+ there, and its planned/removed/provisional verdicts override anything staler.
52
+ - [`documentation/`](./documentation/) — four guides for agents working on the
53
+ repo: `architecture.md`, `meta-tools.md`, `code-mode.md`, and `auth.md`.
54
+ Everything else — connectors, providers, admission, storage, the operator
55
+ UI, operations — is documented where it lives, in the source and its tests.
56
+ The guides that once covered those survive only in git history, which is
57
+ worth mining for rationale and worth nothing where it disagrees with
58
+ `ethos.md` or the code. Start with `architecture.md`.
51
59
  - [`README.md`](./README.md) — the human-facing overview.
52
60
  - [`templates/node/`](./templates/node/) — the one standalone Node deployment
53
61
  shape copied by `connecta init`, Docker-ready rather than Docker-only. Keep
@@ -56,13 +64,14 @@ exports.
56
64
  diff away from one of them is the shape
57
65
  [#344](https://github.com/zackbart/connecta/issues/344) deleted.
58
66
 
59
- **Read `ethos.md` and the subsystem's guide before changing a subsystem.**
67
+ **Read `ethos.md` before changing a subsystem, and its guide when it has one.**
60
68
 
61
69
  ## Deployment setup
62
70
 
63
71
  `connecta init [directory]` is the golden path. It copies `templates/node/`,
64
72
  pins the generated deployment to the CLI package's exact version, restores the
65
- template `.gitignore`, and refuses to merge into an existing path.
73
+ template `.gitignore` and the `CLAUDE.md` symlink, and refuses to merge into an
74
+ existing path.
66
75
  `connecta doctor` verifies a running deployment's health, executor, and exact
67
76
  seven-tool surface. The template carries its own `Dockerfile` and
68
77
  `docker-compose.yml`, so the generated project is the container: setup changes
@@ -81,7 +90,11 @@ Two boundaries CI enforces that are not obvious from reading a file:
81
90
  (`src/executors/quickjs.ts` + child) are the Node-touching paths and must stay
82
91
  unreachable from the root entry. `test/purity.test.ts` walks the import graph
83
92
  and fails otherwise. Need a Node API? It goes behind an explicit Node-only
84
- subpath (`/node` or `/quickjs`), never the root.
93
+ subpath (`/node` or `/quickjs`), never the root. `effect/testing` is banned
94
+ from `src/` on the same terms — its test clocks and layers have no business
95
+ in a runtime graph — and the purity walk catches it. The same walk keeps
96
+ the root entry to `effect` itself and lets only `src/runtime/run.ts` start
97
+ a fiber.
85
98
  - **Optional modules.** Core owns catalog discovery, execution, invocation, and
86
99
  enforcement. UI, activity history, encrypted credentials, and bearer auth
87
100
  implementations stay behind explicit subpaths and outside the root import
@@ -96,10 +109,13 @@ Two boundaries CI enforces that are not obvious from reading a file:
96
109
  forbidden is a platform-bound adapter becoming importable from the package,
97
110
  not a file appearing in the artifact. `@clerk/backend` and
98
111
  `quickjs-emscripten` are optional peers behind the `./auth/clerk` and
99
- `./quickjs` subpaths and must never become dependencies or install with
100
- core. Enforced by `test/package-surface.test.ts` and
112
+ `./quickjs` subpaths, and `@cloudflare/codemode` is the third, peered
113
+ without a subpath because a Worker deployment imports it directly. None may
114
+ become a dependency or install with core. Enforced by `test/package-surface.test.ts` and
101
115
  `scripts/check-package.mjs`. Anything heavyweight or platform-bound gets a
102
- subpath and an optional peer.
116
+ subpath and an optional peer. The Effect core is the one deliberate
117
+ exception: it is the core, so it is a hard dependency rather than a peer,
118
+ under the rule in [Conventions](#conventions).
103
119
 
104
120
  ## Where new tests go
105
121
 
@@ -108,10 +124,7 @@ Every `*.test.ts` belongs to exactly one explicit list: runtime-portable suites
108
124
  in `WORKERS_SUITES`, Node-bound suites in `NODE_ONLY_SUITES` with a reason. The
109
125
  `node` project runs both lists; the `workers` project re-runs the portable list
110
126
  inside workerd. `test/suite-partition.test.ts` walks the directory and fails on
111
- an unclassified, double-classified, stale, or reasonless entry. New behavior
112
- also gets a row in the test map in `documentation/operations.md`, which
113
- classifies every suite — one missing from it is either new and undocumented or
114
- dead, and neither is a state to leave the repository in.
127
+ an unclassified, double-classified, stale, or reasonless entry.
115
128
 
116
129
  ## Conventions
117
130
 
@@ -119,6 +132,22 @@ dead, and neither is a state to leave the repository in.
119
132
  only; it does not enforce style. `npm run check:unused` runs Knip's
120
133
  unused-export and dependency gate. Keep both clean, and prefer removing dead
121
134
  declarations over suppressing a finding.
135
+ - **Effect inside, Promises at the edge.** The core runs on Effect v4 and
136
+ exports none of it; [Effect inside](./documentation/architecture.md#effect-inside)
137
+ has the shell/core shape, the one runner, and the cross-request rules.
138
+ `createConnecta`,
139
+ `remoteMcp()`, `api()`, custom connectors, and every published `.d.ts` stay
140
+ Promise-based and name no Effect type: a deployment author should never need
141
+ a second async paradigm to write a connector, so convert at the boundary,
142
+ not in the caller. Effect is a hard dependency pinned to one exact version,
143
+ never a range, and it is the version Alchemy pins, so a deployment that uses
144
+ both resolves one Effect rather than two. `effect/unstable/*` modules are
145
+ allowed, which is exactly why the pin is exact: they break in minor releases,
146
+ so an upgrade is its own deliberate pull request, never a drive-by in another
147
+ change. `effect/testing` stays out of `src/` (see import-graph purity). The
148
+ MCP edges do not move with the core — `@modelcontextprotocol/server` upward,
149
+ the SDK client downstream; Effect's `McpServer` is parked until the Effect
150
+ core is stable and those edges are re-proven.
122
151
  - **Style.** There is no formatter. Match the surrounding code. The docs voice
123
152
  is precise, occasionally wry, and always explains *why* — don't flatten it
124
153
  into boilerplate.
@@ -132,9 +161,8 @@ dead, and neither is a state to leave the repository in.
132
161
  documentation and OpenAPI contracts and never reads a provider credential.
133
162
  Remote MCP schemas remain owned by the live `tools/list` response rather than
134
163
  a vendored copy. Findings are read by a human and become GitHub issues;
135
- nothing files itself.
136
- See
137
- [`documentation/provider-conventions.md`](./documentation/provider-conventions.md#the-maintainer-run-drift-check).
164
+ nothing files itself. `scripts/drift-check.mjs` is the reference for what
165
+ each flag covers.
138
166
  - **Releases.** `npm run release:check`, tag `v<version>` matching
139
167
  `package.json` exactly (the publish workflow verifies this and fails
140
168
  otherwise), and publishing fires on GitHub **Release publication**, not on the
package/CHANGELOG.md CHANGED
@@ -2,6 +2,317 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.25.0 — 2026-09-23
6
+
7
+ The Effect core is the release. Admission, deadlines, invocation,
8
+ `execute_code`, discovery, the registry, remote MCP connections, OAuth refresh,
9
+ and the request pipeline now run on Effect v4 behind the same Promise API:
10
+ connectors, `createConnecta`, and every shipped declaration name no Effect
11
+ type, and no connector code changes. The rewrite paid for itself in lifetimes —
12
+ about a dozen bugs where a permit, a lease, or a wait outlived the request that
13
+ owned it, each listed under Fixed — and the release also seals downstream OAuth
14
+ state, stops treating a dead refresh grant as an outage, and bounds results
15
+ below what clients cut off. What breaks: `effect` is a new hard dependency,
16
+ pinned to exactly `4.0.0-rc.117` (about 53 MB installed, +44 KB gzip on the
17
+ root bundle, about +20 ms of Worker cold start); `calls.maxResultBytes`
18
+ defaults to 24,000 instead of 50,000, and a truncated result now leads with its
19
+ notice; `get_result` pages are a header line plus raw text, with `maxBytes`
20
+ clamped to the cap; with `encryptedCredentialVault()`, OAuth tokens are sealed
21
+ the first time they are read, so rolling back to 0.24 means authorizing every
22
+ OAuth connector again; `/ui/connectors/<id>` no longer returns `message`; the
23
+ OAuth callback page names a classified reason instead of echoing the
24
+ provider's; an OAuth URL a downstream advertises off its configured origin must
25
+ be HTTPS on a public host; and `npm run check` now needs Chromium. A deployment
26
+ can ignore the rest: every new surface is optional or defaulted
27
+ (`KVStorage.compareAndSet`, `CredentialVault.seal`/`open`,
28
+ `execute.watchdogMs`, the `typescript` schema format), no existing option
29
+ changes meaning (the result cap changes only its default), and results stashed
30
+ by 0.24 keep paging until they expire.
31
+
32
+ ### Added
33
+
34
+ - **`KVStorage.compareAndSet(key, expected, next, { ttlSeconds })`.** An
35
+ optional atomic claim: `expected: null` means absent (an expired entry
36
+ counts), `next: null` deletes, and a write takes a TTL exactly as `set` does.
37
+ Memory and file storage provide it — the file store atomically under its
38
+ lock, persisted across reopen, and rolled back when the write cannot be
39
+ persisted — and the namespaced views core hands connectors forward it only
40
+ when the store underneath has it. The Worker example gains a D1-backed
41
+ `KVStorage` with compare-and-set and TTL (`examples/worker/src/d1-storage.ts`,
42
+ its schema in the example README, a commented `STORAGE_DB` binding). The
43
+ Cloudflare KV adapter declares none, because eventually consistent storage
44
+ cannot keep that promise. Nothing requires it; downstream OAuth uses it where
45
+ it exists.
46
+ - **`CredentialVault.seal` / `open`.** Optional members for sealing values that
47
+ are not credentials. `encryptedCredentialVault()` implements them with the
48
+ vault's AES-GCM key, binding the connector id, the owner, and the physical
49
+ storage key (which carries the authorization epoch) into the authenticated
50
+ data, so ciphertext copied to another connector, principal, or epoch does not
51
+ open. A vault without them draws one startup warning per OAuth connector.
52
+ - **`execute.watchdogMs`.** A ceiling on how long connecta waits for an
53
+ executor to settle, enforced outside the sandbox: 120,000 ms by default,
54
+ above both executors' own deadlines, with an invalid value falling back like
55
+ its sibling keys. A run still unsettled at the ceiling ends as a
56
+ non-retryable `executor_failed` calling the sandbox unresponsive, and its
57
+ admission lease is released. See code mode `L3`.
58
+ - **TypeScript signatures in discovery.** `includeSchemas: "typescript"` on
59
+ `search_tools` and `connecta.search`, and `format: "typescript"` on
60
+ `connecta.describe`, replace both schema fields with one `signature` — the
61
+ function `connecta.call` resolves to, such as
62
+ `(args: { team: string }) => Promise<unknown>` — for an agent to read, never
63
+ to run; programs stay JavaScript. It rides the compact renderer's walk and
64
+ budgets, and an observed output opens with `/* observed, not declared */`.
65
+ The default stays `compact`: agents in the eval never chose `typescript`, even
66
+ when nudged.
67
+ - **The operator page says what each tool may do and how to fix what is
68
+ wrong.** Every tool carries the path core enforces for it — "runs in
69
+ programs" for an explicitly read-only tool, "needs approval" for everything
70
+ that crosses `call_destructive_tool`. Every failure the page shows offers
71
+ "Copy fix prompt", fixed text chosen by a server-classified problem and the
72
+ configured connector id, with no parameter an error message, token, or URL
73
+ could travel through. The endpoint block offers Claude Code, Codex, and
74
+ `mcpServers` JSON setup for `/mcp` and for each `/mcp/<pool>` the viewer's
75
+ grant admits; none carries a token.
76
+ - **An eval harness, for contributors.** `eval/` drives a deployment only
77
+ through its public surface: `eval:agent` runs headless Claude Code against six
78
+ stateful fake MCP servers and grades the fakes' final state and call ledger,
79
+ never the agent's prose; `eval:smoke` boots the Node template and the Worker
80
+ example; `eval:perf` measures the root Worker bundle and request latency;
81
+ `eval:report` renders a comparison against the baselines in
82
+ `eval/baselines/`. None of it runs in `npm run check` or ships in the
83
+ tarball.
84
+
85
+ ### Changed
86
+
87
+ - **The core runs on Effect.** Each converted module keeps its exported class
88
+ or function exactly and runs an Effect program behind it; one runner,
89
+ `src/runtime/run.ts`, is the only place a fiber starts, and it rethrows
90
+ connecta's own error classes and a caller's abort reason unchanged.
91
+ `documentation/architecture.md` describes the shape and the two workerd rules
92
+ it had to learn. `effect` is a runtime dependency at one exact version, never
93
+ a range — the version Alchemy pins, so a deployment using both resolves one
94
+ copy — and an upgrade is its own release. The cost, against 0.24.4: the root
95
+ entry 235,346 → 279,526 B gzip, the Worker example 263,949 → 314,336 B, each
96
+ provider about +36 KB, `./ui` +39 KB, `./activity` +32 KB, and about 53 MB
97
+ on disk (`effect` has no dependencies of its own). Effect Schema and
98
+ `HttpApi` were measured and not used; `zod` stays for the meta-tool inputs.
99
+ - **Truncated results lead with their handle, under client cutoffs.** Claude
100
+ Code (2.1.280) swaps any MCP result over 50,000 characters for a spill-file
101
+ pointer and a 2,000-character preview, and past twice
102
+ `MAX_MCP_OUTPUT_TOKENS` rejects it with no content at all. The old layout, a
103
+ 50,000-byte preview with the `get_result` notice at the tail, crossed that
104
+ line every time: agents never saw the handle, and two of three models re-ran
105
+ a billable export three times to look again. A `call_tool` or
106
+ `call_destructive_tool` result over its cap now opens with a one-line JSON
107
+ notice (`resultId`, `totalBytes`, `hint`, `nextAction`), then the preview;
108
+ the default `calls.maxResultBytes` drops to 24,000 bytes so notice plus
109
+ preview stays under 25,000; a lone text block is measured, previewed, and
110
+ paged as its text rather than a JSON-escaped content envelope; and a call not
111
+ explicitly read-only says the write already ran and must not be repeated.
112
+ Per-connector overrides and validation are unchanged, and a deployment whose
113
+ clients take more can raise the cap. A result between 24,000 and 50,000
114
+ bytes that used to arrive whole now pages.
115
+ - **A truncated read offers a program before paging.** The notice on an
116
+ explicitly read-only call now says: to find something specific, repeat the
117
+ read inside `execute_code` with `connecta.call` and filter or search it
118
+ there; to read it in full, page with `get_result`. It used to name paging
119
+ alone, and once the handle was visible the eval's Haiku paged a 185 KB CI
120
+ log 24,000 bytes at a time, skipped ranges or stopped after two pages, and
121
+ named the flaky test near the top in all three trials. With the new hint it
122
+ reduced the log in a program and found the real failure in three of three,
123
+ at about a third of the cost per trial. A write's notice is unchanged, since
124
+ repeating a write is what it forbids, and `nextAction` is still the page
125
+ handle for both.
126
+ - **`get_result` pages are raw text.** A page is one text block: a JSON header
127
+ (`resultId`, `offset`, `bytes`, `totalBytes`, `hasMore`, `nextAction`), a
128
+ newline, then the page as stored, where it used to be
129
+ `{ offset, nextOffset?, totalBytes, text }` with every quote and newline in
130
+ the page escaped a second time. `maxBytes` is an upper bound clamped to the
131
+ inline cap of the call that stashed the result, recorded in the stash entry;
132
+ a larger request is answered, not refused, because agents that could finally
133
+ see the handle asked for 50,000-byte pages and Claude Code rejected every
134
+ answer. Entries stashed before the cap was recorded page at the deployment
135
+ cap.
136
+ - **Connector status text stays in the server log.** A status message can quote
137
+ a downstream error body, and that body can quote the secret it rejected, so
138
+ `/ui/connectors/<id>` no longer returns `message` and the page shows fixed
139
+ copy for a classified `problem` instead. The raw text is logged as
140
+ `[connecta] connector "<id>" operator status <state>: <message>`, at warn for
141
+ failures and info for `auth_required`.
142
+ - **The OAuth callback page names a closed reason** — `denied`,
143
+ `provider_error`, `invalid_callback`, `handoff_failed`, or `exchange_failed` —
144
+ instead of echoing the provider's `error` parameter or the exchange error,
145
+ which goes to the operator log. An expired callback reads as
146
+ `invalid_callback`, so refusals stay indistinguishable.
147
+ - **Faster requests.** Each `/mcp` request builds a fresh `McpServer`, and zod
148
+ re-rendered all seven meta-tool input schemas for every one — about 65% of
149
+ `tools/list` CPU. They are now derived once and memoized; `tools/list`
150
+ bodies are byte-identical. On the eval's Node deployment, p50 against 0.24.4:
151
+ `tools/list` 1.43 → 0.68 ms, `call_tool` 2.95 → 1.34 ms, `execute_code`
152
+ 4.94 → 3.45 ms.
153
+ - **The tarball ships only declarations an export reaches.** The build prunes
154
+ every `.d.ts` file no `exports` target reaches — about thirty,
155
+ `src/runtime/` included — and `npm run check:declarations` fails if a shipped
156
+ declaration names an Effect type or is left orphaned. Nothing importable
157
+ changed; those paths were never a supported import.
158
+ - **`npm run check` needs Chromium.** It runs the operator UI's browser suite
159
+ after vitest (`npm run test:browser:install` once per machine; `npm pack`
160
+ runs `check` too), then `check:declarations` and `check:bundle`, which caps
161
+ every entry's gzip size against `scripts/bundle-budget.json`. CI and publish
162
+ install Chromium first.
163
+ - RevenueCat's product store-state plan family and targeting-rule tools arrive
164
+ classified instead of failing closed; the five tools RevenueCat deprecated
165
+ keep their verdicts, and the usage guide routes store changes through the
166
+ plan workflow (#560).
167
+
168
+ ### Fixed
169
+
170
+ - **Downstream OAuth state at rest.** A `remoteMcp()` OAuth connector's tokens,
171
+ registered client secret, and PKCE verifier were written to storage as
172
+ plaintext JSON beside vault-encrypted credentials. With a vault that has
173
+ `seal`/`open` they are now sealed on write, and plaintext left by an older
174
+ release is resealed where it lies on first read, conditional on nothing
175
+ having changed it meanwhile. A value that does not open reads as absent:
176
+ `auth_required`, with a warning. Without a vault, stored bytes are
177
+ unchanged.
178
+ - **Dead refresh grants.** A refused refresh — any 4xx but 408, 425, or 429,
179
+ or a 2xx carrying an OAuth error such as GitHub's `bad_refresh_token` — was
180
+ reported as a generic failure and the dead token sent again, while an outage
181
+ the SDK could not parse was reported as needing consent. A refusal now ends
182
+ as `auth_required` with the refused tokens deleted, so the next
183
+ `authorize_connector` reaches consent; a 5xx, 408, 425, 429, or network
184
+ failure is a retryable `unavailable` (or `rate_limited`, with `retryAfterMs`
185
+ from `Retry-After`), the grant is kept, and a passive call writes no consent
186
+ URL. Every request joined on the refresh gets the same verdict.
187
+ - **A refused refresh token could be redeemed twice.** A request that owned a
188
+ refresh and was cancelled while deleting the refused grant released its
189
+ joiners early, so a request arriving meanwhile sent the refused token again.
190
+ Deleting it also no longer overwrites a consent that lands between the read
191
+ and the delete, where the store has `compareAndSet`.
192
+ - **OAuth URLs a downstream advertises.** Connecta fetched the authorization
193
+ servers, token endpoints, and registration endpoints a remote server's
194
+ metadata names from the host, so a compromised downstream could aim a Node or
195
+ Docker deployment at `169.254.169.254` or its LAN. A URL off the connector's
196
+ configured origin must now be HTTPS on a host that is not private by syntax,
197
+ and a refused one is never requested; it fails as a non-retryable
198
+ `connector_call_failed` naming only the host. A loopback-configured connector
199
+ may still learn loopback URLs. DNS rebinding is out of scope, as the auth
200
+ guide says.
201
+ - **Admission permits and leases that outlived their caller.**
202
+ - An `/mcp` request that stalled after admission — in inbound auth, a pool
203
+ grant, or a tool handler that ignores cancellation — kept its permit after
204
+ its client left. On Workers, where such a request is cancelled as hung, the
205
+ permit leaked for the isolate's life.
206
+ - `execute_code` awaited its executor with no outer bound. A wedged Dynamic
207
+ Worker never fired its in-isolate deadline, so the run held its code-pool
208
+ lease forever; two of those filled the default pool and every later
209
+ program queued behind them. `execute.watchdogMs` ends that wait.
210
+ - An admitting executor whose `acquire()` ignored the signal held a cancelled
211
+ request until it granted a lease.
212
+ - **Work that outlived a cancelled request.** `connecta.search` and
213
+ `connecta.describe` kept loading catalogs after their run was cancelled, and
214
+ one in flight held the program until its probe deadline; `search_tools`
215
+ probes ran on after their caller left; operator activity pages kept looking
216
+ up labels from the identity provider after the reader had gone; and legacy
217
+ `/mcp` requests never closed their `McpServer`.
218
+ - **Call admission on Workers.** Handing a permit to a queued call read the
219
+ waiter's abort signal from inside the releasing request, which workerd
220
+ refuses: the releasing call reported `connector_call_failed` despite
221
+ succeeding, and the waiter stranded until its deadline.
222
+ - **Remote MCP connections.** A call waiting on a connect could resume after
223
+ teardown, a forced re-authorization, or a credential rotation had cleared the
224
+ client, and fail with "Cannot read properties of null (reading 'callTool')".
225
+ And a transport whose close never settled held the connection's teardown
226
+ forever, hanging direct callers such as the credential Test action; closing a
227
+ session and the transport are now bounded to a second each.
228
+ - **A stale catalog persisted after invalidation.** Evicting a personal
229
+ registry while its catalog refresh was in flight let a replacement for the
230
+ same principal invalidate the catalog, and the evicted refresh then persisted
231
+ the pre-change listing again. Eviction now skips a registry with a refresh or
232
+ catalog write in flight.
233
+ - **QuickJS shutdown.** A child that ignored SIGTERM outlived `close()`, which
234
+ stopped waiting after a second; it now gets SIGKILL. `close()` also waits for
235
+ children recycled before it, not only its own.
236
+ - **Operator routes.** A credential PUT read its whole body before the
237
+ 20,000-character check; reading now stops past 60,000 bytes with the same
238
+ 413. An OAuth disconnect that rejected with no reason reported 204.
239
+ - A program ending in `};` failed on QuickJS with `SyntaxError: expecting ')'`,
240
+ because both executors evaluate a program inside parentheses; on a Dynamic
241
+ Worker it failed the same way or, with a comment after it, quietly returned
242
+ `undefined`. `execute_code` now drops semicolons that trail the arrow
243
+ expression, with whitespace and comments after them, before either executor
244
+ sees the program. Anything else stays as written, so `async () => 1; 2` is
245
+ still more than one expression (code mode `P1`).
246
+ - A scoped `connecta.search` whose probe timed out named `search_tools` in
247
+ `queryAnalysis.catalogError`, a tool a program cannot call.
248
+ - Recovery text for a missing or rejected static credential sent operators and
249
+ agents to a `/credentials` page that no longer exists; it now points at
250
+ `authorize_connector` for recovery options and at the connection in the
251
+ operator UI (#551).
252
+ - Compact schema rendering accumulated every member's text of an oversized
253
+ composite before checking it against the byte budget — megabytes of
254
+ transient text for a wide schema with long descriptions. It now stops
255
+ consuming members once the budget cannot be met; output within budget is
256
+ unchanged (#558).
257
+
258
+ ## 0.24.4 — 2026-09-17
259
+
260
+ The operator UI is the release. Its connections page is now a summary line and
261
+ one row per connector instead of a wall of expanded cards, and the stylesheet
262
+ behind it resolves through design tokens a deployment can set with the new
263
+ `branding.theme`. Nothing a deployment configures today changes meaning, no
264
+ storage format moved, and the page shows exactly what it showed before under
265
+ the same gates — an operator who has the page bookmarked will find it reads
266
+ differently, and that is the whole of the upgrade. The documentation cut to
267
+ four guides lands here too, along with one execution-path fix.
268
+
269
+ ### Added
270
+
271
+ - **`branding.theme`.** Five tokens — `accent`, `radius`, `fontFamily`,
272
+ `monoFamily`, and `colorScheme` — set on `operatorUi({ branding })`. Every
273
+ other color on the operator page is mixed from those, so setting one accent
274
+ themes the page rather than leaving half of it on the defaults. Light and
275
+ dark are the same tokens and follow the operator's OS setting unless
276
+ `colorScheme` pins one. Each token is gated the way the branding URLs already
277
+ were: a hex color, a CSS length, a plain font-family list, one of
278
+ `system`/`light`/`dark`. A rejected value takes the default and gets named in
279
+ a startup warning. The gates are narrow because these values land in a
280
+ `:root` block on the page, where anything unvalidated would be CSS injection.
281
+
282
+ ### Changed
283
+
284
+ - **The operator UI reads as a dashboard.** The connections page opens with one
285
+ summary line — connected, anything needing authorization, anything
286
+ unavailable, tools available — and then one row per connector: status dot,
287
+ name, auth scope, tool count, state. What used to be stacked in every card at
288
+ once (the description, the permission line, OAuth actions, the credential
289
+ panel, diagnostics, drift, and the tool list) is now behind a row an operator
290
+ expands, so a deployment with twenty connectors is a screen instead of a
291
+ scroll. Nothing about what the page may show or do changed: same payload,
292
+ same gates, and the drift panel still refuses to render a tool name or a
293
+ schema (#554).
294
+ - **The documentation is four guides.** `documentation/` now ships
295
+ `architecture.md`, `meta-tools.md`, `code-mode.md`, and `auth.md`. The
296
+ operations, upgrading, connector, provider, admission, storage, and operator
297
+ UI guides are removed; the rationale source comments used to defer to them
298
+ for now lives in those comments, and the provider conventions H1–H14 and
299
+ P1–P13 are defined in `test/provider-conventions.test.ts`. Each release's
300
+ opening paragraph here is the upgrade guidance. `records/` and `eval/` are
301
+ removed with the `load:admission` script and the CI job that served them. The
302
+ template and Worker example keep their `AGENTS.md`, which now points at this
303
+ changelog for upgrades (#552, #553).
304
+
305
+ ### Fixed
306
+
307
+ - **`get_result` paging cost.** A stashed result is stored as base64 chunks
308
+ under one key each — 48 KiB of text per chunk, wider for a result over
309
+ roughly 1.5 MB so the key and write count stays bounded — and a page reads
310
+ and decodes only the chunks it covers. Paging a large result no longer reads
311
+ the whole stash — let alone re-encodes it — once per page, so cost tracks the
312
+ page rather than the total. Offsets, `nextOffset`, `totalBytes`, and
313
+ character-boundary alignment are unchanged, and entries stashed in the
314
+ previous formats stay readable for the rest of their 15-minute TTL (#540).
315
+
5
316
  ## 0.24.3 — 2026-09-16
6
317
 
7
318
  A bug-fix release from a full audit of the execution path, invocation, catalog
@@ -163,7 +474,7 @@ returns `"all"` or connector ids and declares no pools.
163
474
  - **Tool-level grants in `identity.connectorAccess`.** Entries may be a
164
475
  connector id (every tool) or an exact `connector.tool` address (that tool
165
476
  only); grants are additive. The scoped registry view filters below the
166
- catalog service, so `search_tools`, `describe_tools`, `call_tool`,
477
+ catalog service, so `search_tools`, `call_tool`,
167
478
  `call_destructive_tool`, a program's `connecta.search` and `connecta.call`,
168
479
  and the connection UI all see the same list, and an ungranted tool fails as
169
480
  `unknown_tool` exactly like an absent one. There is no wildcard: a remote
@@ -173,6 +484,11 @@ returns `"all"` or connector ids and declares no pools.
173
484
 
174
485
  ## 0.24.1 — 2026-09-08
175
486
 
487
+ A small additive release for deployments whose providers run long: two
488
+ optional `execute.*` settings and a log line for failed calls. Nothing breaks
489
+ and nothing needs migrating; a deployment that sets neither option behaves as
490
+ it did on 0.24.0.
491
+
176
492
  ### Added
177
493
 
178
494
  - `execute.maxHostCalls` and `execute.hostCallTimeoutMs` configure the
package/README.md CHANGED
@@ -95,9 +95,7 @@ Cloudflare Access supplies Worker identity; Node can use Clerk or the optional
95
95
  configured bearer adapter. Connecta owns no accounts or groups and issues no
96
96
  client access tokens. Shared-credential administration and personal connection
97
97
  setup require separate explicit permissions, both denied by default. See
98
- [inbound auth](./documentation/auth.md#principals-visibility-and-operators),
99
- [shared and personal auth](./documentation/storage-and-credentials.md#shared-and-personal-auth),
100
- and the [module migration guide](./documentation/upgrading.md#0240-optional-modules).
98
+ [inbound auth](./documentation/auth.md#principals-visibility-and-operators).
101
99
 
102
100
  Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
103
101
  service. Those are decisions, and the [ethos](./ethos.md) records each one
@@ -107,9 +105,10 @@ and why.
107
105
 
108
106
  Setup is written for an agent. Point yours at [`AGENTS.md`](./AGENTS.md) and
109
107
  ask it to set up a Connecta deployment; the
110
- [documentation](./documentation/) covers every subsystem if you want to go
111
- deeper, and [upgrading](./documentation/upgrading.md) an existing deployment
112
- is its own runbook.
108
+ [documentation](./documentation/) covers the architecture, the seven tools,
109
+ code mode, and inbound auth if you want to go deeper. When upgrading an
110
+ existing deployment, each [changelog](./CHANGELOG.md) release opens with what
111
+ breaks and what a deployment can ignore.
113
112
 
114
113
  ## Status
115
114
 
@@ -0,0 +1,53 @@
1
+ function deepFreeze(value) {
2
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
3
+ Object.freeze(value);
4
+ for (const child of Object.values(value))
5
+ deepFreeze(child);
6
+ }
7
+ return value;
8
+ }
9
+ /**
10
+ * A meta-tool input schema whose JSON Schema rendering is derived once per
11
+ * process, not once per request.
12
+ *
13
+ * Every `/mcp` request builds a fresh `McpServer` (stateless per request), and
14
+ * the SDK renders each registered tool's input schema through
15
+ * `~standard.jsonSchema` at registration and again for `tools/list`. Zod
16
+ * re-walks the whole schema on every one of those calls, which made the
17
+ * rendering most of a `tools/list` request's CPU. The schemas themselves are
18
+ * module constants — no deployment option, identity, or scoped view reaches
19
+ * them; what does vary (descriptions) is a string computed per registration
20
+ * and never passes through here — so the rendering is immutable data that can
21
+ * outlive a request without being request-bound.
22
+ *
23
+ * This is the SDK's own `StandardSchemaWithJSON` contract, not a patch of it:
24
+ * validation delegates to zod unchanged, so a tool call is checked exactly as
25
+ * before, and the JSON Schema is zod's own output for the same target,
26
+ * memoized and deep-frozen so no request can alter what the next one sees.
27
+ * Conversions with library options are passed through uncached; nothing in
28
+ * the SDK sends them.
29
+ */
30
+ export function advertisedSchema(schema) {
31
+ const standard = schema["~standard"];
32
+ const rendered = new Map();
33
+ const convert = (io) => (options) => {
34
+ if (options.libraryOptions !== undefined) {
35
+ return standard.jsonSchema[io](options);
36
+ }
37
+ const key = `${io}:${options.target}`;
38
+ let json = rendered.get(key);
39
+ if (json === undefined) {
40
+ json = deepFreeze(standard.jsonSchema[io](options));
41
+ rendered.set(key, json);
42
+ }
43
+ return json;
44
+ };
45
+ return {
46
+ "~standard": {
47
+ version: 1,
48
+ vendor: standard.vendor,
49
+ validate: (value) => standard.validate(value),
50
+ jsonSchema: { input: convert("input"), output: convert("output") },
51
+ },
52
+ };
53
+ }