@zackbart/connecta 0.40.0 → 0.42.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 (195) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/bin/connecta.mjs +26 -17
  3. package/bin/meta-tool-names.d.mts +3 -1
  4. package/bin/meta-tool-names.mjs +4 -1
  5. package/bin/version.mjs +1 -1
  6. package/dist/activity.d.ts +32 -5
  7. package/dist/activity.js +32 -6
  8. package/dist/advertised-schema.js +15 -0
  9. package/dist/agent-observation.js +24 -2
  10. package/dist/agent-routes.js +33 -44
  11. package/dist/auth/clerk.d.ts +6 -1
  12. package/dist/auth/clerk.js +16 -0
  13. package/dist/auth/cloudflare-access.js +49 -7
  14. package/dist/auth/downstream-oauth/consent.js +79 -29
  15. package/dist/auth/downstream-oauth/grant-store.js +58 -7
  16. package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
  17. package/dist/auth/downstream-oauth/refresh-records.js +6 -5
  18. package/dist/auth/downstream-oauth.js +15 -4
  19. package/dist/auth/oauth-request-not-sent.js +4 -0
  20. package/dist/auth/static-oauth.js +77 -39
  21. package/dist/auth-elicitation.js +118 -61
  22. package/dist/authorization-handoff.js +36 -3
  23. package/dist/catalog-cache.js +352 -43
  24. package/dist/catalog-freshness.js +25 -0
  25. package/dist/catalog-service.js +408 -78
  26. package/dist/catalog.js +216 -62
  27. package/dist/config-defaults.d.ts +2 -1
  28. package/dist/config-defaults.js +4 -1
  29. package/dist/config.d.ts +53 -21
  30. package/dist/config.js +56 -17
  31. package/dist/connectors/remote-mcp/call.js +1 -1
  32. package/dist/connectors/remote-mcp/catalog.js +52 -28
  33. package/dist/connectors/remote-mcp/connection.js +113 -22
  34. package/dist/connectors/remote-mcp/oauth.js +2 -2
  35. package/dist/connectors/remote-mcp/resources.js +5 -2
  36. package/dist/connectors/remote-mcp/transport.js +24 -6
  37. package/dist/connectors/remote-mcp.js +14 -5
  38. package/dist/credential-contract.d.ts +3 -1
  39. package/dist/describe-config.d.ts +4 -2
  40. package/dist/describe-config.js +1 -0
  41. package/dist/discovery-health.js +26 -11
  42. package/dist/downstream-input.js +11 -2
  43. package/dist/errors.d.ts +60 -4
  44. package/dist/errors.js +88 -3
  45. package/dist/execute.js +491 -202
  46. package/dist/executor-admission.js +2 -0
  47. package/dist/executor-contract.js +6 -0
  48. package/dist/executor-result.js +3 -3
  49. package/dist/executors/quickjs-child.js +14 -5
  50. package/dist/executors/quickjs-runtime.d.ts +2 -0
  51. package/dist/executors/quickjs-runtime.js +37 -20
  52. package/dist/executors/quickjs.js +24 -7
  53. package/dist/guarded-transport.js +2 -0
  54. package/dist/guest-functions.js +5 -4
  55. package/dist/guest-runtime.js +37 -1
  56. package/dist/guest-types.d.ts +18 -0
  57. package/dist/http/client.js +14 -2
  58. package/dist/http/errors.js +9 -4
  59. package/dist/http/fields.js +17 -5
  60. package/dist/http/hatch.js +11 -3
  61. package/dist/http/node-transport.js +110 -4
  62. package/dist/http/oauth.js +17 -6
  63. package/dist/http/paging.js +1 -1
  64. package/dist/http/provider.js +2 -6
  65. package/dist/http/vendor.js +6 -1
  66. package/dist/index.d.ts +1 -1
  67. package/dist/index.js +15 -21
  68. package/dist/invocation-failure.js +28 -0
  69. package/dist/invocation.js +167 -64
  70. package/dist/membership.d.ts +1 -1
  71. package/dist/meta-output.js +24 -18
  72. package/dist/meta-tool-names.d.ts +1 -1
  73. package/dist/meta-tool-names.js +1 -1
  74. package/dist/meta-tools.js +286 -73
  75. package/dist/module-contracts.d.ts +3 -1
  76. package/dist/native-recovery.js +4 -1
  77. package/dist/native-tools.js +177 -55
  78. package/dist/node.d.ts +7 -0
  79. package/dist/node.js +85 -4
  80. package/dist/oauth-handoff.js +53 -43
  81. package/dist/operation-activity.js +104 -0
  82. package/dist/operation-vocabulary.js +6 -0
  83. package/dist/operator-record.d.ts +9 -1
  84. package/dist/operator-record.js +16 -0
  85. package/dist/operator-ui/generated.js +2 -2
  86. package/dist/program-result.js +2 -0
  87. package/dist/program-source.js +1 -1
  88. package/dist/program-writes.js +112 -19
  89. package/dist/provider.d.ts +1 -14
  90. package/dist/provider.js +0 -3
  91. package/dist/providers/_shared/google/kit.js +2 -2
  92. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  93. package/dist/providers/breeze/api.js +2 -2
  94. package/dist/providers/breeze/index.js +1 -1
  95. package/dist/providers/breeze/skill.generated.js +1 -1
  96. package/dist/providers/ccb/api.js +2 -2
  97. package/dist/providers/ccb/index.js +1 -1
  98. package/dist/providers/ccb/skill.generated.js +1 -1
  99. package/dist/providers/cloudflare/api.js +2 -2
  100. package/dist/providers/cloudflare/index.js +3 -1
  101. package/dist/providers/cloudflare/skill.generated.js +1 -1
  102. package/dist/providers/docs/index.js +1 -1
  103. package/dist/providers/docs/skill.generated.js +1 -1
  104. package/dist/providers/drive/index.js +1 -1
  105. package/dist/providers/drive/skill.generated.js +1 -1
  106. package/dist/providers/forms/index.js +2 -1
  107. package/dist/providers/forms/skill.generated.js +1 -1
  108. package/dist/providers/github/api.js +98 -6
  109. package/dist/providers/github/index.js +8 -3
  110. package/dist/providers/github/lib/auth.d.ts +3 -1
  111. package/dist/providers/github/lib/auth.js +43 -12
  112. package/dist/providers/github/lib/discovery.js +11 -1
  113. package/dist/providers/github/lib/operations.js +8 -0
  114. package/dist/providers/github/safety.js +39 -0
  115. package/dist/providers/github/skill.generated.js +1 -1
  116. package/dist/providers/github/tools.js +12 -5
  117. package/dist/providers/gmail/index.js +2 -1
  118. package/dist/providers/gmail/skill.generated.js +1 -1
  119. package/dist/providers/granola/index.js +1 -1
  120. package/dist/providers/granola/skill.generated.js +1 -1
  121. package/dist/providers/overflow/index.js +3 -1
  122. package/dist/providers/overflow/skill.generated.js +1 -1
  123. package/dist/providers/planning-center/index.js +12 -6
  124. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  125. package/dist/providers/planning-center/skill.generated.js +1 -1
  126. package/dist/providers/quickbooks/api.js +28 -25
  127. package/dist/providers/quickbooks/index.js +3 -6
  128. package/dist/providers/quickbooks/lib/writes.js +55 -0
  129. package/dist/providers/quickbooks/safety.js +17 -5
  130. package/dist/providers/quickbooks/skill.generated.js +1 -1
  131. package/dist/providers/sheets/index.js +2 -1
  132. package/dist/providers/sheets/skill.generated.js +1 -1
  133. package/dist/providers/slides/index.js +3 -3
  134. package/dist/providers/slides/skill.generated.js +1 -1
  135. package/dist/providers/stripe/index.js +2 -2
  136. package/dist/providers/stripe/skill.generated.js +1 -1
  137. package/dist/providers/tithely/index.js +2 -1
  138. package/dist/providers/tithely/skill.generated.js +1 -1
  139. package/dist/providers/vercel/api.js +3 -2
  140. package/dist/providers/vercel/skill.generated.js +1 -1
  141. package/dist/registry.d.ts +36 -4
  142. package/dist/registry.js +142 -30
  143. package/dist/resource-read.js +21 -2
  144. package/dist/result-preview.js +210 -0
  145. package/dist/result-seal.js +168 -0
  146. package/dist/route-names.js +53 -0
  147. package/dist/routes/activity-context.js +1 -0
  148. package/dist/routes/activity-disclosure.js +26 -4
  149. package/dist/routes/activity.js +2 -1
  150. package/dist/routes/connect.js +21 -13
  151. package/dist/routes/mcp.js +83 -35
  152. package/dist/routes/shared.d.ts +6 -0
  153. package/dist/routes/ui-config.js +1 -1
  154. package/dist/routes/ui-data.js +3 -2
  155. package/dist/runtime/call.js +15 -10
  156. package/dist/runtime/native-storage.js +12 -0
  157. package/dist/runtime/request-reads.js +66 -14
  158. package/dist/runtime/request-scope.js +38 -2
  159. package/dist/runtime/run.js +4 -1
  160. package/dist/sent-secrets.d.ts +20 -0
  161. package/dist/sent-secrets.js +49 -1
  162. package/dist/skill-origin.js +16 -0
  163. package/dist/skills.js +213 -57
  164. package/dist/storage/d1-driver.js +33 -5
  165. package/dist/storage/keys.js +16 -7
  166. package/dist/storage/sql-native.js +60 -3
  167. package/dist/storage/sql.js +6 -0
  168. package/dist/timing-trace.js +84 -0
  169. package/dist/tool-safety.d.ts +33 -0
  170. package/dist/tool-safety.js +51 -15
  171. package/dist/types.d.ts +36 -0
  172. package/dist/url-safety.js +38 -20
  173. package/dist/usage-guide.js +14 -13
  174. package/dist/utility-recovery.js +33 -0
  175. package/dist/validate.js +29 -10
  176. package/dist/version.d.ts +1 -1
  177. package/dist/version.js +1 -1
  178. package/dist/worker.js +60 -15
  179. package/documentation/architecture.md +152 -43
  180. package/documentation/auth.md +160 -53
  181. package/documentation/code-mode.md +396 -119
  182. package/documentation/deploying.md +35 -10
  183. package/documentation/hosted-mcp-migration.md +9 -3
  184. package/documentation/integrating.md +27 -5
  185. package/documentation/meta-tools.md +761 -157
  186. package/documentation/operating.md +91 -4
  187. package/documentation/operator-ui.md +51 -2
  188. package/documentation/providers.md +34 -20
  189. package/examples/worker/README.md +3 -3
  190. package/guides/how-it-works.md +2 -1
  191. package/guides/safety-and-trust.md +1 -3
  192. package/package.json +10 -8
  193. package/templates/node/AGENTS.md +3 -3
  194. package/templates/node/README.md +1 -1
  195. package/templates/node/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,154 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.42.0 — 2026-11-05
6
+
7
+ This release makes Connecta easier for agents to use: shorter served text, failures that name the next step, a read-only program route, and better discovery. Six breaking changes can alter how existing clients or deployments behave, so check them before upgrading.
8
+
9
+ **Breaking:** A deployment `classification` override to `"read"` no longer applies to a tool whose source declares a write. That tool stays a write on every surface, so its calls prompt on hosts that prompt, a direct `call_tool` is refused, and a read-only pool loses it. Remove the override, or change it to the new `"read-repeatable"` if you have checked that a repeat is harmless. Native pool writes are now named `<connector>__WRITE__<tool>`, or a hashed form of it, so update anything that hardcodes a native write's name, such as host approval rules, allowlists and prompts. `execute.watchdogMs` now defaults to 55 s (was 120 s) and caps direct calls, and `workerExecutor()` defaults to a 50 s timeout and a 40 s queue deadline; raise them only below your clients' own request limit. GitHub answers a resource HTTP 403 as `provider_permission_denied` instead of `auth_required`, without a credential handoff. A `skills` fetch returns its text once, in `content`, not in `structuredContent.text`. `@zackbart/connecta-eve` now requires exactly Eve `1.0.0-rc.4` as its peer; its README lists what that brings to your app, including a default `edit_file` tool to gate wherever `write_file` is gated.
10
+
11
+ **Reads and programs:** Writable `/mcp` and `/mcp/<pool>` endpoints now list a seventh meta tool, `execute_read_code`, annotated read-only and refusing every write before dispatch, so hosts that gate on annotations can run read-only programs without a write approval. Set `execute: { readRoute: false }` to turn it off, and read the read programs section in `documentation/meta-tools.md` first: such a program runs unprompted on hosts that auto-approve reads, and a read tool that takes a URL or query can carry data out in its arguments. `connecta.result(handleOrId, { all: true })` reads a retained result whole up to 1.5 MiB. A direct read over its inline cap can now answer with a sample in the result's own shape rather than its first bytes, so a client that parses a truncated direct result should not assume the body is a prefix of the result.
12
+
13
+ **Failures and recovery:** Failure messages now give the exact next step, and anything branching on `nextAction.tool === "authorize_connector"` must also accept the new handoff shapes. A program that may have written warns against rerunning it, program writes report how they failed in `unknownWrites`, and a tool's `isError` text now reaches agents up to 4,096 UTF-8 bytes. Program errors and timeouts arrive as typed failures before MCP clients give up on the request. A QuickJS crash that could cost a run its answer, or the next request, is fixed.
14
+
15
+ **Discovery:** Served instructions and tool descriptions are shorter, and connector guides now hold only provider-specific facts. Search ranks partial matches by the rarity of the query words they cover, so a hatch's common verb no longer hides a named tool. A connector that does not answer in time keeps its tools in discovery from its last complete catalog, marked `stale: true`. The `execute_code` inventory can show each connector's configured description as a short purpose.
16
+
17
+ Deployments without these overrides, native write names, longer client timeouts or the Eve helper need no configuration changes.
18
+
19
+ ### Added
20
+
21
+ - The `execute_code` connector inventory now adds each connector's configured description as a one-line purpose, capped at 60 UTF-8 bytes. Purposes are the first detail dropped under the inventory's 1,024-byte cap, and they never appear in the server instructions.
22
+ - Add an eval-only Eve runner using local ChatGPT subscription models, the Connecta Eve helper, deferred tool traces, and the existing task graders on Node and Workers, across every eval surface, with a configurable connection name and description.
23
+ - Teach Eve agents how to reach Connecta tools: `connectaInstructions` now places an Eve guide before the server's text, with a `connection` option for connections not named `connecta`. `connectaConnection`'s `description` is optional and warns when it carries no domain information, both factories accept `headers`, and the helper warns on release skew. Discovery and `initialize` advertise caller-scoped endpoint facts in `capabilities.extensions["dev.connecta/endpoint"]`.
24
+ - Writable `/mcp` and `/mcp/<pool>` endpoints now list a seventh meta tool, `execute_read_code`, annotated read-only, so hosts that gate on annotations need no write approval for read-only programs. It has the same guest API, sandbox and connector inventory as `execute_code`, and refuses every call whose effective or source class is write before dispatch, with the next step and zero writes, whether or not the program catches the refusal. A tool set to `"read-repeatable"` runs there, since the deployment accepted that hosts may repeat it. Discovery inside it lists writes with `callable: false`. Handles from direct reads and read programs page from it. Read-only endpoints, `/agent/<pool>` and native pools are unchanged. `execute: { readRoute: false }` turns it off. Activity records source `execute_read_code`. A read program runs unprompted on hosts that auto-approve reads, and a read tool that takes a URL or query can carry data out in its arguments: see the read programs section in `documentation/meta-tools.md`.
25
+ - GitHub's reviewed read allowlist now covers check runs, check suites, check-run annotations and commit statuses. These reads require the App to hold Checks read or Commit statuses read; an App without them fails only those reads, with a typed permission failure, until the permission is granted.
26
+ - Add twelve hard-discovery eval tasks over a shared 110-tool catalog, with no-model controls on every arm and target.
27
+ - Add eval arms for host-native search and five meta-tools, deterministic catalog padding, and first-request and discovery efficiency telemetry, per-trial distractor reporting, and an explicit list of tasks the no-code arm cannot be compared on.
28
+ - Add `connecta.result(handleOrId, { all: true })`, which reads a retained result whole as `{ data, format }` for one host call up to a read ceiling of 1.5 MiB (scaled with `cpuTimeMs` on QuickJS, and declared by the new optional `Executor.maxReadBytes`), and throws a catchable `result_too_large` above it. A read's finished transfer now gives its stash back, up to three per run within a one-second budget, leaving the run's unsealed ledger first. Handles within the ceiling now say to read whole. Migration: an oversized program return's `nextAction` now reads whole and marks where to reduce, instead of returning a 3,829-byte page, so code that parses that action's output as a page should read `connecta.result` with `{ offset }` instead.
29
+ - `classification` accepts a new value, `"read-repeatable"`, the migration path for a deployment that overrode a vendor-declared write to `"read"` on purpose. The deployment has checked the tool and accepts that hosts may repeat it, so it serves as a read on every route: read annotations, read-only endpoints, `call_tool`, programs and its plain native name. A host's resend after a connection failure can reach the vendor twice. Connecta still sends it at most once and never replays it after an authorization failure. Plain `"read"` is unchanged: it does not apply over a declared write, so a vendor that later declares one fails safe.
30
+ - Add eval-only native and native-without-code pool arms for Worker comparisons, with native call evidence and surface controls.
31
+
32
+ ### Changed
33
+
34
+ - Failure messages now give the exact next step in one read. An auth error that carries a handoff says so in `nextAction` (`{ url, purpose }`, or `{ action: "contact_operator", purpose }` when there is no link) instead of sending the agent to `authorize_connector` for the same thing, and its `instructions` address the person opening the link; `authorizationUrl`, `operatorUrl`, `credential` and `instructions` keep their meaning, and anything branching on `nextAction.tool === "authorize_connector"` must also accept the new shapes. A program that finishes with nothing to read gets a `note`, which after a write says not to rerun it; an unawaited read in a program that finished gets the await hint there too. A truncated direct read offers a `connecta.result` loop over the retained result in one program when that fits the host-call budget, and repeats the read otherwise; a paged handle offers that loop on the same terms, counting the calls its program has left, and otherwise bounds the pages per program. Budget, write-outcome, read-only, unknown-tool, empty-search, program-error and direct-call-shape messages name what to do next; `invalid_args` with complete findings no longer sends the agent to search first, and its example uses a named placeholder rather than an empty string. The `usage` paging example no longer sets `maxBytes`, which could exhaust the host-call budget.
35
+ - **Breaking:** Shrink every maintained connector guide to provider-specific facts. Generic paging, `fields`, withheld-value and write-once rules now live in the `cursor`, `page` and `fields` schemas and the `write_outcome_unknown` error; a hatch page that cannot continue adds an incomplete-result warning; and a read-only connection is no longer served its guide's write rules. A `skills` fetch now returns its text once, in `content`, as documented, so a client reading `structuredContent.text` from that fetch must read `content`; the tool advertises no `outputSchema`.
36
+ - **Breaking:** `@zackbart/connecta-eve` now requires exactly Eve `1.0.0-rc.4` as its peer. Update the app to `eve@1.0.0-rc.4`, reinstall, rebuild and test before deploying; helper authoring is unchanged. Eve's own changes reach the app: a deployment from before rc.3 cannot take over sessions rc.3 or later wrote, so plan rollbacks around that; every agent gains a default `edit_file` tool, so gate it wherever `write_file` is gated; and builds now reject `nitro/storage`, `nitro/kv`, `nitro/database` and asset imports that leave the authored package. The helper's README lists them with the release notes.
37
+ - **Breaking:** Answer before MCP clients give up, so an agent gets a typed `timeout` or `write_outcome_unknown` with `hostCalls` and `writes` rather than a transport `The operation timed out`. Claude Code and Codex both give one request 60 s by default. `execute.watchdogMs` now defaults to 55 s (was 120 s), counts from the request's arrival so queueing spends it, and caps direct `call_tool`/`call_destructive_tool` deadlines, including an explicit `timeoutMs` or `calls.defaultTimeoutMs`. `workerExecutor()` now defaults to a 50 s `timeout` (was 60 s) and a 40 s queue deadline (was 60 s); `quickJsExecutor()` keeps 30 s. A run cut short by the ceiling rather than wedged fails as a `timeout` at stage `response ceiling`, retryable unless a write in it landed. Any program error is now non-retryable and drops `retryAfterMs` once a write in the run has landed, because running the program again would repeat it. A run or direct call with no time left is refused unsent, and a call whose time runs out during catalog resolution, admission or auth is refused unsent by a check immediately before dispatch. On Node, the ceiling also counts the request's upload. Storage after a run stops waiting 2 s past the ceiling at most, so a stalled store costs an oversized return its paging, not its answer.
38
+
39
+ Migration: if your clients allow longer requests (`MCP_TOOL_TIMEOUT`, a server's `timeout`, `tool_timeout_sec`) and your programs or direct calls need the time, set `execute.watchdogMs` a margin below that limit, then raise `workerExecutor({ timeout })` or `quickJsExecutor({ timeoutMs })` below it. Construction warns when an executor's deadline is not below `execute.watchdogMs`.
40
+ - End program reads just before the executor's run deadline, so a slow read becomes a catchable, retryable `timeout` and `Promise.allSettled` keeps every result that finished. Executors may publish `deadlineMs`; `quickJsExecutor()` and `workerExecutor()` do, and `execute_code` states it as `<n>s/run`. Writes stay exempt so a drained write's outcome is still learned, and a write that reaches its gate after reads have stopped is refused unsent.
41
+ - Remove the contributor policy limiting Opus agents.
42
+ - A direct read over its inline cap (24,000 bytes unless a connector overrides it) on a surface with `execute_code` can now answer with a sample instead of its first bytes: for rows, the row count, keys and whole first rows as valid JSON in the result's own shape; for text, the line count, head and tail. Its notice says which rows or bytes it holds and that the rest are not shown, and carries `resultId`, the original `totalBytes`, a `nextAction` program over the retained result, and the fallback when that program is refused. A program's value and the paged handle are unchanged. Results at or under the cap return whole, as before. Migration: a client that parses a truncated direct result should not assume the body after the notice is a prefix of the result: `nextOffset` is 0 and the body is a separate view, showing less than first bytes did. Writes, including a source write a deployment overrode to read, value mode, several or non-text blocks, native pools with `executeCode: false`, results with numbers `JSON.parse` would change, and any shape the rule does not cover keep their first bytes. A source write overridden to read now also says its write already ran and is no longer told to repeat the read inside a program. A `fields` miss warning is shorter: `fields/0 matched nothing after filtering, at segment 1; declared there: id, name.`, with at most 160 characters of names, and a later miss at the same place says `same as fields/0`.
43
+ - A program's retained results are now unreadable from other requests until they pass the run's complete secret set. At its end a run waits for its host calls and credential reads to finish within the response's answer-by time, then publishes each entry that passes. A run pays no extra storage, and reads stored bytes back only for entries the set grew past. Stored bytes are never rewritten, so an offset issued during the run stays valid. An entry that matched, or could not be checked or written, stays unreadable: paging it from a later request answers `result_not_retained` with `retryable: false`, a read told to rerun and a write told not to repeat, and the run's response carries a `note` saying so. Paging inside the run is unchanged, and direct-call stashes keep passing their own request's set when stored.
44
+ - `tools/list` now advertises open output schemas for `execute_code`, `search_tools` and `authorize_connector`: required keys and top-level types, with every other field open. Served results are unchanged, and the server still validates each successful one against the full contract before sending it. A client that read the closed schema or `$defs.failure` from `tools/list` finds the failure and handoff shape in `documentation/meta-tools.md`.
45
+ - **Breaking:** A deployment `classification` override to `"read"` no longer applies to a tool whose source explicitly declares a write (`readOnlyHint: false`, `destructiveHint: true`, or contradictory hints). Such a tool stays a write on every surface and is never served read-annotated, because a host may resend a read-annotated call after a connection failure, which duplicated physical writes. Where the source is silent, `"read"` still serves as a read, and setting it asserts the call is safe to repeat.
46
+
47
+ **Migration:** if you overrode a vendor-declared write to `"read"`, an operator warning now names that override once per catalog change. Its calls prompt on hosts that prompt, and a direct `call_tool` is refused with the next step, naming `call_destructive_tool`. Native pools list it as destructive. Read-only endpoints and connectors omit and refuse it, so a read-only pool loses it. Remove the override, or, if you have checked the tool and a repeat is harmless, change it to `"read-repeatable"`. `"write"` and `"hidden"` overrides are unchanged.
48
+ - Shorten the MCP instructions and tool descriptions served to agents. Meta instructions now open with the caller's admitted connector ids; `skills` and `connecta.skill` return a local guide's body without frontmatter; the connector inventory marks required guides with `*` and no longer names optional ones.
49
+
50
+ ### Fixed
51
+
52
+ - Raise the root bundle cap so main passes after two changes that each fit it alone.
53
+ - Stop the required CI gate failing when the jobs API trails completed matrix legs.
54
+ - Keep the required CI gate waiting while the jobs API still reports finished legs as queued.
55
+ - A declined or cancelled URL elicitation now returns the verified connection handoff, so an agent on a client that refuses prompts automatically can give the user a working `/connect` link. The refusal text no longer implies that a person saw the prompt.
56
+ - Stop eval graders failing correct refusals, handoffs and QuickBooks and CCB routes, and passing a handoff link altered inside a Markdown destination; make the QuickBooks journeys passable by following their prompts; harden Worker restarts against port collisions.
57
+ - Stop build hooks rewriting unchanged generated sources, so they cannot reload a smoke under Wrangler, and report a Worker eval reload as an infrastructure error instead of a lost connection or host authorization failure. Freeze one verified Worker graph per eval batch, refusing a batch or comparison whose served sources differ from its label or its other arm.
58
+ - A `force: true` connection flow now resets once across all of its links. URL elicitation `accept` does not mean a browser opened the link, so every link stays live: the first verified visit performs the reset and later visits continue it, without invalidating the first consent. A visit during a running start returns 409 without spending its link and says to start a new connection request if it persists, and a failed start lets the flow's next visit begin again. A declined or cancelled prompt's link now works alongside the refusal's link instead of being retired. An unforced flow's unspent links are no longer retired at completion, and a refusal whose link could not be issued now tells the agent how to get a new one. A link from an earlier version that names a predecessor link is refused and replaced by a new link.
59
+ - **Breaking:** GitHub answers a resource HTTP 403 as `provider_permission_denied` instead of `auth_required`, without a credential handoff, naming the repository and any permission or scope GitHub reports. The message states possible causes without claiming the credential works or a grant is missing. Rate-limit 403s stay `rate_limited`, a 401 stays `auth_required`, and SAML enforcement is stated without its link. A refused token mint reports the typed gap only when GitHub's installation record lacks the requested permission; otherwise a 422 is `connector_call_failed` with vendor text withheld, and a mint 403 keeps its causes open. Repositories or owners outside the App's installation report `provider_permission_denied`. A 403 on the App's own installation list stays `auth_required`. Programs that branched on `auth_required` for these failures must branch on `provider_permission_denied`, and these failures no longer carry a credential handoff.
60
+ - **Breaking:** A native pool's tool name now binds its classification, so a host resending a call it cached as read-only cannot write. Writes are named `<connector>__WRITE__<tool>`, or a hashed form of it; every read keeps the name it had, and a name stays stable until the tool's classification changes. A read name whose tool is now a write is refused before dispatch with `destructive_tool_requires_approval`, and `nextAction.tool` names the write. To migrate, take native names from `tools/list`, and change anything hardcoding a native write's name, such as host approval rules, allowlists, prompts and recipes, to the `__WRITE__` form. A host's cached list needs a refresh before it can call a write. `nativeToolName` now requires a classification. Addresses (`connector.tool`), programs and meta pools are unchanged.
61
+ - Program writes no longer hide how they failed or leave altered. `write_outcome_unknown` now leads with the tool's words when a tool answered `isError`, and carries `unknownWrites` with each write's address, code and redacted message, plus `toolAnswered: true`; the outcome stays unknown and nothing is sent again. A tool's `isError` text now reaches every agent surface up to 4,096 UTF-8 bytes instead of 512, cut only between tokens and marked, so an approval link or request id arrives usable. A program's report is redacted against the run's final credentials before any cut and bounded per message, in total, and by `execute.maxResultChars`. A program write whose arguments hold a credential the request already sent is refused before dispatch with `invalid_args`, rather than sent with `[redacted]` in place of the match, which could first appear on a program's second call. Program arguments no longer match a credential's public prefix or an identifier sent beside it, so reads and writes naming them leave as written. Maintained providers now declare a header public from the vendor's contract, and only Cloudflare's Global API Key `X-Auth-Email` and Overflow's `x-client-id` are declared: the whole value stays withheld from output, but its fragments no longer match, so such a connection's domain survives in results. Every other header and query parameter stays fully protected whatever its name. Connecta's clips of tool errors, program failures and logs now end between tokens. Operator logs name a tool's own failure `DownstreamToolError` rather than `Error`.
62
+ - Keep a QuickJS child alive when a program's memory grows during a job pass. A bug in `quickjs-emscripten-core` 0.32.0 aborted the child while freeing a finished run's runtime, which cost the run its answer, or the next request when a host call was still open. If freeing a runtime ever fails again, the run keeps its answer, a warning is logged and the child is replaced before it can take another run.
63
+ - Stop a hatch's operation vocabulary hiding partial matches from named tools, and rank on what search shows. Search descriptions are now the first complete sentence with `descriptionTruncated: true` when cut, `connecta.describe` returns full text by default within a 32 KiB budget per call, a description's tail can no longer make a tool a complete match, titles are indexed, and a few abbreviations such as `env`/`environment` match. Partial matches score by the squared share of the query's rarity they cover, so rare words in a hatch's description outrank a common verb in another tool's name, and GitHub's hatch operations now count in main search as other hatches' already do.
64
+ - Keep a slow connector's tools in discovery. When a connector does not answer an unscoped search or native `tools/list` in time, its last complete catalog, up to 24 hours past expiry, is listed and reported with `stale: true` and `ageMs` in `catalogErrors`. Calls still resolve the live catalog. An auth or permission refusal lists nothing and retires the catalog of the credential that attempt sent, in every isolate, until a listing begun after the refusal succeeds. A custom connector refused after its credential changed retires all of that connector's catalogs, since the credential it sent cannot be known. A downstream's retry delay is kept in `retryAfterMs`, and `connecta doctor` fails with "Native discovery degraded" when a native pool lists a connector stale. Cached and authored catalogs no longer wait for a probe slot, a timed-out probe refreshes its catalog in the runtime's tail, and a publish deletes the catalog revision it supersedes.
65
+
66
+ ## 0.41.0 — 2026-11-03
67
+
68
+ This release fixes a production outage on Workers, tightens auth and network defaults, and makes warm requests cheaper. Three changes can stop an existing deployment at startup or at validation, so check them before upgrading.
69
+
70
+ **Upgrade if you run Workers on `@cloudflare/codemode` 0.5.5 or later.** On Connecta 0.40.0, those codemode releases make every `execute_code` host call fail with "The code execution finished before this tool call settled". `workerExecutor()` now works with them. Deployments that pinned codemode to 0.5.4 to avoid this can drop the pin after upgrading; deployments that stay on 0.40.0 must keep it.
71
+
72
+ **Breaking:** `clerkAuth` now requires a resolved `publicUrl`, set on `clerkAuth({ publicUrl })` or `ConnectaConfig.publicUrl`, and no longer falls back to the request origin. A configured vault must have `seal` and `open` when any connector has `startAuth`; use `encryptedCredentialVault(storage, key)` or add both methods, and existing plaintext grants are sealed on first read. Pool descriptions are limited to 500 UTF-16 code units after trimming. The public activity kind union adds `operation`, which affects exhaustive consumers of activity records.
73
+
74
+ **Security:** learned OAuth URLs on non-public addresses are refused, including transitional IPv6 forms carrying non-public IPv4 addresses, learned Node URL connections are pinned to validated DNS answers, and the Node adapter answers `400` to a malformed `Host`, `X-Forwarded-Proto` or request target before reading the body. Pending OAuth flow records store less: with a sealing vault the consent URL, binding and PKCE verifier are sealed together, and with no vault the record keeps no consent URL, so a repeated start mints fresh consent. Sealed consents pending from 0.40.0 still complete; a legacy no-vault consent restarts fresh.
75
+
76
+ **Performance:** warm catalog resolution locates manifests, negotiation and generation in one bounded read. Lexical indexes are reused across requests in an isolate, result-stash writes are batched, and last-seen observation skips redundant reads in its sampling window. Local-only skill catalogs now advertise a five-minute private TTL. Node gains a cap on request body bytes held in flight together, set with `listen(..., { maxInFlightBodyBytes })`. It defaults to the larger of 64 MiB and `maxBodyBytes`, and an explicit value must be at least `maxBodyBytes`; past it, requests get `503` with `Retry-After`.
77
+
78
+ **Reliability and agent guidance:** a rejected Cloudflare Access identity lookup retries once within its original deadline and records payload-free diagnostics. API OAuth status now comes from typed call outcomes fenced by grant revision. Endpoint instructions lead with pool scope and the guide pointer and state the effective write policy, native tools point at connector guides, skills have readable names with legacy aliases, and downstream skill text is labeled with its third-party origin. Typed failures carry recovery steps across host, availability, utility and result-handle errors.
79
+
80
+ **Opt-in, ignore unless you want them:** `activityHistory({ operations: true })` for payload-free operation history; `timingTraceSampleRate` for sampled payload-free MCP timing logs; QuickBooks `update_unsent_invoice`, enabled with `readOnly: false`, for sparse `DocNumber` updates on observed unsent US-company USD invoices, an inference from Intuit's documented autosend prerequisites and not a vendor no-email guarantee; and, in `@zackbart/connecta-eve`, a caller or turn `when` predicate on `connectaConnection` and `connectaInstructions`, plus `connectaSkill({ name, description, ... })` for deferred connector instructions as Eve skills.
81
+
82
+ The downstream Skills extension remains Beta, and unknown Skills revisions are now rejected.
83
+
84
+ ### Added
85
+
86
+ - Add opt-in sparse DocNumber updates for observed unsent US-company USD QuickBooks invoices, refusing unknown delivery state, enabled payments and stale versions.
87
+ - **Breaking:** Opt in to payload-free operation history with activityHistory({ operations: true }). Search, describe, result, skill and whole-program rows use conservative scope and owner disclosure, remain detached from responses, and do not inflate call or friction counts. The public activity kind union adds operation, which may require updates to exhaustive consumers. Typed operation failures retain their checked codes, and activity reuses existing page byte measurements without serializing operation returns.
88
+ - The Eve helper accepts a caller/turn `when` predicate for instructions and connections. False or a policy failure omits the value before fetching guidance or selecting a pool, including when guidance is cached.
89
+ - `listen(..., { maxInFlightBodyBytes })` bounds the request body bytes all in-flight requests hold together on Node. It defaults to 64 MiB, or `maxBodyBytes` when that is larger. Past it a request is answered `503` with `Retry-After` and `Connection: close`. `maxBodyBytes` and its `413` are unchanged, and Workers are unaffected.
90
+ - Point guided native tool descriptions at the connector guide through the serving skills route.
91
+ - Add eval-only guidance delivery interventions for pointer, summary, inline convention and instruction-led fetch comparisons, with deterministic arm controls and guide-fetch metrics. Inline conventions use the advertised guideSummary field, and wire controls list tools first so the SDK validates every arm's output schema.
92
+ - Offer opt-in deferred connector instructions as Eve skills with caller selection before fetching. Network, timeout and malformed-response failures omit the skill while preserving turn cancellation. Successful content is reused only while Eve retains the endpoint/guide/turn memo; failed later turns serve no stale guide.
93
+ - Opt-in sampled payload-free MCP timing logs with request-read cache outcomes and D1 trip/statement totals. Workers durations are I/O-spanning elapsed observations, not CPU attribution; CPU-only catalog stages report counts and decoded bytes. Trace finalization survives throwing downstream cleanup.
94
+
95
+ ### Changed
96
+
97
+ - **Breaking:** `createConnecta` throws when `clerkAuth` has no resolved `publicUrl`: neither `clerkAuth({ publicUrl })` nor `ConnectaConfig.publicUrl`. Clerk's token `resource` and session `azp` checks no longer fall back to the request origin. To migrate, set either option to the URL clients reach, or name the loopback origin locally, such as `http://localhost:8787`. A `clerkAuth` `publicUrl` that is not an absolute http(s) URL without credentials also throws. The unset-`publicUrl` startup warning now also fires with inbound auth and describes every URL that follows the request origin.
98
+ - **Breaking:** `createConnecta` throws when a configured vault lacks `seal` or `open` and any connector has `startAuth`, replacing a startup warning that left downstream OAuth tokens, client registrations and PKCE verifiers as plaintext beside encrypted credentials. To migrate, use `encryptedCredentialVault(storage, key)` or add both methods to the custom vault; grants already stored as plaintext are sealed on first read. `encryptedCredentialVault`, deployments with no vault, and non-sealing vaults with no OAuth connector are unaffected.
99
+ - Skip redundant last-seen observation reads during the one-minute sampling window while continuing fresh token admission.
100
+ - Raise the root bundle cap to 422,500 B gzip and `examples/worker` to 806,500 B gzip for this release's measured growth, ending the batch's temporary headroom.
101
+ - Locate catalog manifests alongside negotiation and generation in one bounded SQL read, retaining complete chunk verification and a separate fresh acceptance fence.
102
+
103
+ Keep locator layout metadata and key construction in the shared key builders. Prove expired generation/manifest handling and literal punctuation against both SQL drivers, and pin measured combined warm agent read counts after admission observation sampling.
104
+ - Run CI static/package build phases and two isolated Node test shards in parallel, checking complete shard coverage and every matrix leg's completion at the aggregate gate.
105
+ - Prefix downstream skill text with its third-party connector origin on tool and program surfaces while preserving native manifest bytes.
106
+ - Reuse bounded immutable lexical tokens and postings across complete request catalogs while applying visibility on each request.
107
+ - Advertise a five-minute private TTL for local-only skill catalogs and reads; downstream-enabled caller views retain zero TTL.
108
+ - Use pinned native TypeScript for core and operator UI no-emit feedback, with explicit shared result types accepted by both compilers. Keep TypeScript 6 for AST analysis, emitted builds, declarations and installed consumers.
109
+ - Verify sealed pending OAuth consents from 0.40.0 through callback validation, binding, claim, exchange and token persistence. Cover fresh-start recovery for legacy no-vault consents whose discovery URLs include userinfo, queries or fragments.
110
+ - **Breaking:** Put pool scope and the guide pointer before routing rules in meta and native endpoint instructions, preserving unscoped instructions.
111
+
112
+ Pool descriptions are limited to 500 UTF-16 code units after trimming so accepted text fits the instruction budget and first 512 units in every mode. This tightens validation: previously accepted descriptions with at most 500 Unicode code points but more than 500 UTF-16 units (for example, 500 emoji) now fail construction. Shorten those descriptions before upgrading; required routing rules remain intact.
113
+ - Keep server orchestration and Effect out of the QuickJS child startup graph.
114
+ - Use readable skill slugs with legacy aliases, actionable provider summaries and advisory construction warnings for weak guide summaries.
115
+
116
+ Generated readable names resolve on every skill surface; canonical identities take priority over legacy aliases, ambiguous aliases fail explicitly, and generated names avoid configured connector IDs.
117
+ - Batch SQL result-stash trailing chunks before separate header publication, reducing storage trips while preserving expiry and uncertain-write accounting.
118
+ - Strengthen stash fault probes for delayed SQL batch commits and transactional expiry sweeps.
119
+
120
+ ### Fixed
121
+
122
+ - Clarify that the redaction guarantee excludes occurrences made entirely of reserved replacement text, including runs across the seam between adjacent markers. Settling behavior is unchanged.
123
+ - QuickBooks write evals now explicitly grade the destination and final answer evidence, completing the selftest check classification without weakening outcome checks.
124
+ - Let `eval:selftest`'s D1 latency control accept a timer that fires just under its delay, so the full selftest no longer fails on a 49.8 ms round trip.
125
+ - QuickBooks eval setup now probes personal credential isolation through a program and checks read-only pools using the tools the selected eval surface serves, so code-only selftests retain their safety checks without calling hidden meta-tools.
126
+ - Attribute eval fake dispatches through a per-server transport marker, retaining unrelated loopback probes separately from vendor ledgers.
127
+ - Probe exact-once eval latency on both deployment targets, disabled fault membership, stopped listening sockets, and each D1 round trip. Count D1 batch waits instead of imposing a load-sensitive upper timing bound.
128
+ - Retry a rejected Cloudflare Access identity lookup once within its original deadline and record checked, payload-free lookup failure diagnostics. Identity failures continue to refuse admission.
129
+ - Restrict native eval workerd TCP connections with fail-closed Linux Landlock port rules and raw-socket controls for disallowed ports and the known allowed-port address limit. Add a model-free check of all eligible Worker references. Document the remaining address and UDP limits.
130
+ - Report API OAuth status from typed real-call outcomes fenced by the token grant's revision. Consent and refresh replace their observation atomically, and passive status reads do not write grant state. Later invocations can recover from transient API refusals; Connect starts fresh consent for a refused grant. Maintained provider middleware records initial and replayed outcomes, and unchanged observations do not rewrite grants. Normal Connect preserves concurrent recovery, and oversized 401 responses record refusal without reading their bodies, including after replay.
131
+ - `workerExecutor()` works with `@cloudflare/codemode` 0.5.5 and later. Those releases close a call gate when the SDK's `execute()` settles, and Connecta's inert loader settled it before the real program ran, so every `execute_code` host call failed with "The code execution finished before this tool call settled". The gate now stays open for the whole native run and closes when it ends or its lease is released. Development and CI use codemode 0.5.6.
132
+ - State effective write policy in endpoint instructions and connector guides, and explain that read-only refusals have no write route.
133
+ - Supply schema recovery for typed argument failures without validation details and explicit safe recovery limits for missing resources, unavailable connectors and uncertain writes.
134
+ - Reuse one observation age for stored connector details and their overlays, keeping both fields consistent across a clock rollover.
135
+ - Corrected the Cloudflare Access `identityLookup.inFlight` documentation: it counts lookup attempts still pending, so a hung lookup counts itself, and a rejected lookup or a deadline during retry backoff does not.
136
+ - Prevent eval startup port reservation races, retry child bind collisions before trials start, and protect a checkout's build with crash-released OS locks while allowing concurrent eval readers. Report failed cross-connector reference reads before attempting the answer post.
137
+
138
+ Restore the fake vendor transport before deployment startup yields, preserving containment and physical-write evidence across interleaved trials.
139
+
140
+ Use descriptor-mode locking on Linux and macOS, retaining the lease when the wrapper dies while its command runs. Keep explicit Wrangler inspector/proxy ports for Landlock and retry native startup collisions with fresh ports. Document the lock's reader/writer fairness limit.
141
+ - Retain remote MCP cleanup settlement across owner and connector close, including abandoned leases, so bounded session termination remains owned by runtime deferred work. Await explicit warmup termination in OAuth rotation tests, including cleanup gated before fetch accounting.
142
+ - `eval:selftest` exits after its last check instead of idling on the egress control's unclosed UDP probe socket.
143
+ - Reject unknown downstream Skills revisions and record the extension’s Beta lifecycle.
144
+ - Complete typed host, availability, utility and result-handle recovery, with continuation actions taking precedence over generic retry advice.
145
+
146
+ ### Security
147
+
148
+ - Pending downstream OAuth flow records store less. With a sealing vault, the consent URL, server and client binding, and PKCE verifier are sealed together. With no vault, the record stores no consent URL and its binding keeps no URL userinfo, query or fragment, so a repeated non-forced start mints a fresh consent instead of reusing the pending URL. `documentation/auth.md` states the narrower no-vault guarantee. Sealed consents already in flight across the upgrade still complete. A no-vault consent whose discovery URL holds userinfo, a query or a fragment fails cleanly and needs a fresh start.
149
+ - Learned OAuth URLs naming reserved or transitional addresses are refused: IPv4 special-purpose ranges (`192.0.0/24`, `198.18/15`, multicast, reserved and broadcast among them), IPv6 outside global unicast, and IPv4 carried in compatible, translated, NAT64 or 6to4 form, judged by the address it carries. A public address in those forms stays public. A `publicUrl` on such an address no longer offers a self-hosted client metadata document.
150
+ - The Node adapter answers `400` for a `Host` that is not a host with an optional port, an `X-Forwarded-Proto` other than `http` or `https`, or a request target that does not start with `/`, before reading any body. No such value can change the path or host routes see. A malformed value used to fail as a `500` or be parsed into another URL.
151
+ - Pin learned Node URL connections to validated DNS addresses while preserving TLS hostname verification.
152
+
5
153
  ## 0.40.0 — 2026-11-02
6
154
 
7
155
  This release makes warm Connecta calls on Workers cheaper in storage and closes a credential gap on OAuth remote MCP connectors. Deployments need no configuration change or migration, unless they use the Eve helper.
package/bin/connecta.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { copyFile, cp, lstat, mkdtemp, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
4
4
  import { CONNECTA_VERSION } from "./version.mjs";
5
- import { META_TOOL_NAMES } from "./meta-tool-names.mjs";
5
+ import { META_TOOL_NAMES, READ_PROGRAM_TOOL } from "./meta-tool-names.mjs";
6
6
  import { basename, dirname, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
@@ -245,6 +245,8 @@ async function doctor() {
245
245
  let negotiatedVersion;
246
246
  let native = false;
247
247
  let executeCode = true;
248
+ let toolCount = META_TOOL_NAMES.length;
249
+ let readRoute = false;
248
250
  try {
249
251
  await client.connect(transport);
250
252
  negotiatedVersion = client.getNegotiatedProtocolVersion();
@@ -257,15 +259,28 @@ async function doctor() {
257
259
  throw new Error(
258
260
  `Native catalogs unavailable: ${surface.catalogErrors.join(", ")}. Inspect connector status and authentication.`,
259
261
  );
262
+ // Tools are listed, from an older catalog, so nothing above notices that the connector did not answer.
263
+ if (native && surface.staleCatalogs?.length)
264
+ throw new Error(
265
+ `Native discovery degraded: ${surface.staleCatalogs.join(", ")} did not answer and listed from an older catalog. Inspect connector status.`,
266
+ );
267
+ // A writable human endpoint also lists the read program route, unless the deployment turned it off.
268
+ // An agent endpoint never does, so seven tools there is a wrong surface.
269
+ readRoute = !native && !agent && actual.includes(READ_PROGRAM_TOOL);
260
270
  const expected = native
261
271
  ? ["authorize_connector", "skills", executeCode ? "execute_code" : "read_result"].sort()
262
- : [...META_TOOL_NAMES].sort();
272
+ : [...META_TOOL_NAMES, ...(readRoute ? [READ_PROGRAM_TOOL] : [])].sort();
273
+ toolCount = expected.length;
263
274
  if (
264
275
  native
265
276
  ? expected.some((name) => !actual.includes(name)) ||
266
- ["search_tools", "call_tool", "call_destructive_tool", executeCode ? "read_result" : "execute_code"].some(
267
- (name) => actual.includes(name),
268
- )
277
+ [
278
+ "search_tools",
279
+ "call_tool",
280
+ "call_destructive_tool",
281
+ READ_PROGRAM_TOOL,
282
+ executeCode ? "read_result" : "execute_code",
283
+ ].some((name) => actual.includes(name))
269
284
  : JSON.stringify(actual) !== JSON.stringify(expected)
270
285
  ) {
271
286
  throw new Error(
@@ -274,20 +289,14 @@ async function doctor() {
274
289
  );
275
290
  }
276
291
 
277
- if (executeCode) {
278
- const executed = await client.callTool(
279
- {
280
- name: "execute_code",
281
- arguments: { code: "async () => 42" },
282
- },
283
- undefined,
284
- { timeout: DOCTOR_TIMEOUT_MS },
285
- );
292
+ for (const name of executeCode ? ["execute_code", ...(readRoute ? [READ_PROGRAM_TOOL] : [])] : []) {
293
+ const executed = await client.callTool({ name, arguments: { code: "async () => 42" } }, undefined, {
294
+ timeout: DOCTOR_TIMEOUT_MS,
295
+ });
286
296
  const executionResult = executed.structuredContent ?? JSON.parse(executed.content?.[0]?.text ?? "null");
287
297
  if (executed.isError || executionResult?.result !== 42) {
288
298
  throw new Error(
289
- `${executorName ? `${executorName} execution` : "execute_code"} check ` +
290
- `failed: ${JSON.stringify(executed)}`,
299
+ `${executorName ? `${executorName} execution` : name} check ` + `failed: ${JSON.stringify(executed)}`,
291
300
  );
292
301
  }
293
302
  }
@@ -298,7 +307,7 @@ async function doctor() {
298
307
  console.log(
299
308
  `Connecta doctor passed: ${health.connectors} connector(s), ` +
300
309
  `${executeCode ? (executorName ? `${executorName} executed` : "code executed") : "code disabled"}, ` +
301
- `${native ? "native tool surface" : `prescribed ${META_TOOL_NAMES.length}-tool surface`}, MCP ${negotiatedVersion}, package ${CONNECTA_VERSION}.`,
310
+ `${native ? "native tool surface" : `prescribed ${toolCount}-tool surface`}, MCP ${negotiatedVersion}, package ${CONNECTA_VERSION}.`,
302
311
  );
303
312
  }
304
313
 
@@ -1,2 +1,4 @@
1
- /** The complete upward meta-tool set, shared by discovery checks and doctor. */
1
+ /** The upward meta-tool set every meta endpoint lists, shared by discovery checks and doctor. */
2
2
  export declare const META_TOOL_NAMES: readonly string[];
3
+ /** A writable `/mcp` or `/mcp/<pool>` endpoint also lists this, unless `execute.readRoute` is false. */
4
+ export declare const READ_PROGRAM_TOOL: string;
@@ -1,4 +1,4 @@
1
- /** The complete upward meta-tool set, shared by discovery checks and doctor. */
1
+ /** The upward meta-tool set every meta endpoint lists, shared by discovery checks and doctor. */
2
2
  export const META_TOOL_NAMES = Object.freeze([
3
3
  "authorize_connector",
4
4
  "call_destructive_tool",
@@ -7,3 +7,6 @@ export const META_TOOL_NAMES = Object.freeze([
7
7
  "search_tools",
8
8
  "skills",
9
9
  ]);
10
+
11
+ /** A writable `/mcp` or `/mcp/<pool>` endpoint also lists this, unless `execute.readRoute` is false. */
12
+ export const READ_PROGRAM_TOOL = "execute_read_code";
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.40.0";
3
+ export const CONNECTA_VERSION = "0.42.0";
@@ -5,7 +5,7 @@ import type { Logger } from "./types.js";
5
5
  export declare function activityClientFact(value: unknown, field: "name" | "version"): string | undefined;
6
6
  /** Stored package versions use a bounded release-version grammar, including prereleases. */
7
7
  export declare function activityPackageVersion(value: unknown): string | undefined;
8
- export type ActivityCallSource = "catalog_refresh" | "native" | "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
8
+ export type ActivityCallSource = "catalog_refresh" | "native" | "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "execute_read_code" | "resume_execution";
9
9
  export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled" | "paused" | "approved";
10
10
  /** Read-only history: how much one `resume_execution` approved (#672). */
11
11
  export type ActivityApproval = "call" | "tool";
@@ -27,7 +27,7 @@ export interface ActivityActor {
27
27
  namespace?: string;
28
28
  }
29
29
  /**
30
- * Privacy-minimal history of one resolved downstream connector call.
30
+ * Privacy-minimal history of a resolved call, catalog change or opt-in operation.
31
31
  *
32
32
  * Arguments, results, generated code, search text, and raw errors are excluded
33
33
  * by construction. Deployments that need a safe human summary can add a
@@ -39,6 +39,19 @@ export interface ActivityCatalogChange {
39
39
  removedTools: number;
40
40
  changedTools: number;
41
41
  }
42
+ export type ActivityOperationName = "search" | "describe" | "result" | "skill" | "program";
43
+ export interface ActivityOperation {
44
+ name: ActivityOperationName;
45
+ /** Conservative endpoint scope, never caller-supplied addresses. */
46
+ connectors: string[];
47
+ personal: boolean;
48
+ hostCalls?: {
49
+ attempted: number;
50
+ admitted: number;
51
+ succeeded: number;
52
+ failed: number;
53
+ };
54
+ }
42
55
  /** Integer metadata is checked at every write and read boundary. */
43
56
  export declare function activityCount(value: unknown): number | undefined;
44
57
  export declare function activityBehaviorFacts(event: {
@@ -46,16 +59,18 @@ export declare function activityBehaviorFacts(event: {
46
59
  resultBytes?: unknown;
47
60
  kind?: unknown;
48
61
  drift?: unknown;
62
+ operation?: unknown;
49
63
  pool?: unknown;
50
64
  actorBasis?: unknown;
51
- }): Pick<ToolCallActivityEvent, "classification" | "resultBytes" | "kind" | "drift" | "pool" | "actorBasis">;
65
+ }): Pick<ToolCallActivityEvent, "classification" | "resultBytes" | "kind" | "drift" | "operation" | "pool" | "actorBasis">;
52
66
  export interface ToolCallActivityEvent {
53
67
  schemaVersion: 1;
54
68
  id: string;
55
69
  occurredAt: string;
56
70
  requestId: string;
57
71
  /** Absent on legacy tool rows. Catalog changes use the same paging envelope. */
58
- kind?: "catalog_drift";
72
+ kind?: "catalog_drift" | "operation";
73
+ operation?: ActivityOperation | undefined;
59
74
  drift?: ActivityCatalogChange;
60
75
  /** The final registry verdict captured when resolution succeeded. */
61
76
  classification?: "read" | "write";
@@ -162,6 +177,7 @@ export interface CatalogDriftActivityContext {
162
177
  }
163
178
  /** Request-scoped context shared by direct, batch, and code-mode call paths. */
164
179
  export interface ActivityRequestContext {
180
+ recordOperation?: typeof recordOperationActivity | undefined;
165
181
  recordTool?: typeof recordToolActivity | undefined;
166
182
  sink: ActivitySink;
167
183
  actor: ActivityActor;
@@ -181,7 +197,7 @@ export interface ActivityRequestContext {
181
197
  defer?: (promise: Promise<unknown>) => void;
182
198
  logger: Logger;
183
199
  }
184
- export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "friction" | "classification" | "resultBytes" | "kind" | "drift"> & {
200
+ export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "friction" | "classification" | "resultBytes" | "kind" | "drift" | "operation"> & {
185
201
  /**
186
202
  * A code connecta assigns. Rows written by older deployments may hold
187
203
  * others, so the stored event keeps `string`; a new row never does.
@@ -204,8 +220,19 @@ export declare function recordCatalogChangeActivity(context: CatalogDriftActivit
204
220
  privateCatalog?: boolean;
205
221
  }, request?: ActivityRequestContext): void;
206
222
  export interface ActivityHistoryOptions {
223
+ /** Opt in to one detached storage write per meta operation and program. */
224
+ operations?: boolean;
207
225
  store: ActivityStore;
208
226
  deploymentId?: string;
209
227
  readGate?: ActivityReadGate;
210
228
  }
211
229
  export declare function activityHistory(options: ActivityHistoryOptions): ActivityModule;
230
+ /** Operation records reuse the detached sink and accept no payload fields. */
231
+ export declare function recordOperationActivity(context: ActivityRequestContext, input: {
232
+ operation: ActivityOperation;
233
+ source?: "execute_read_code" | undefined;
234
+ durationMs: number;
235
+ resultBytes?: number | undefined;
236
+ outcome: ActivityOutcome;
237
+ errorCode?: ClassificationCode | undefined;
238
+ }): void;
package/dist/activity.js CHANGED
@@ -1,7 +1,8 @@
1
+ import { checkedActivityOperation } from "./operation-activity.js";
1
2
  import { CONNECTA_VERSION } from "./version.js";
2
3
  import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
3
4
  import { routeActivity } from "./routes/activity.js";
4
- import { boundedEchoText, classificationCode } from "./errors.js";
5
+ import { clampedRecordName, classificationCode } from "./errors.js";
5
6
  import { failureRecord, logFailure } from "./operator-record.js";
6
7
  import { keepErrorName } from "./redaction-identity.js";
7
8
  /**
@@ -82,6 +83,7 @@ export function activityBehaviorFacts(event) {
82
83
  ...(classification !== undefined ? { classification } : {}),
83
84
  ...(resultBytes !== undefined ? { resultBytes } : {}),
84
85
  ...(drift ? { kind: "catalog_drift", drift } : {}),
86
+ ...(event.kind === "operation" ? { kind: "operation", operation: checkedActivityOperation(event.operation) } : {}),
85
87
  ...(pool !== undefined ? { pool } : {}),
86
88
  };
87
89
  }
@@ -106,7 +108,7 @@ export function recordToolActivity(context, input) {
106
108
  // Checked here as well as by type: a handler can put any string in a
107
109
  // `ConnectorCallError`'s code, and a downstream's own code is not recorded.
108
110
  const errorCode = classificationCode(input.errorCode);
109
- const friction = input.friction ?? agentFrictionForCode(errorCode);
111
+ const friction = input.kind === "operation" ? undefined : (input.friction ?? agentFrictionForCode(errorCode));
110
112
  // Re-check client grammars at the record boundary; never spread the envelope.
111
113
  const clientName = activityClientFact(context.clientInfo?.name, "name");
112
114
  const clientVersion = activityClientFact(context.clientInfo?.version, "version");
@@ -121,9 +123,9 @@ export function recordToolActivity(context, input) {
121
123
  pool: context.pool,
122
124
  actorBasis: input.privateCatalog || (input.personal && context.principalActor) ? "principal" : undefined,
123
125
  }),
124
- connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
125
- toolName: boundedEchoText(input.toolName, MAX_ACTIVITY_NAME_BYTES),
126
- address: boundedEchoText(input.address, MAX_ACTIVITY_ADDRESS_BYTES),
126
+ connectorId: clampedRecordName(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
127
+ toolName: clampedRecordName(input.toolName, MAX_ACTIVITY_NAME_BYTES),
128
+ address: clampedRecordName(input.address, MAX_ACTIVITY_ADDRESS_BYTES),
127
129
  source: input.source,
128
130
  outcome: input.outcome,
129
131
  durationMs: Math.max(0, Math.trunc(input.durationMs)),
@@ -176,16 +178,40 @@ export function recordCatalogChangeActivity(context, input, request) {
176
178
  }
177
179
  /** Attach payload-free history without changing tool results on store failure. */
178
180
  /** The closed options activityHistory() accepts; see `assertKnownOptions`. */
179
- const ACTIVITY_HISTORY_OPTIONS = optionsOf()(keys("store", "deploymentId", "readGate"));
181
+ const ACTIVITY_HISTORY_OPTIONS = optionsOf()(keys("store", "deploymentId", "readGate", "operations"));
180
182
  export function activityHistory(options) {
181
183
  options = assertKnownOptions(options, "activityHistory()", ACTIVITY_HISTORY_OPTIONS);
182
184
  if (!options || typeof options.store?.record !== "function") {
183
185
  throw new Error("activityHistory.store must implement record(event)");
184
186
  }
187
+ if (options.operations !== undefined && typeof options.operations !== "boolean")
188
+ throw new Error("activityHistory.operations must be a boolean");
185
189
  return {
186
190
  ...options,
191
+ ...(options.operations ? { recordOperation: recordOperationActivity } : {}),
187
192
  handle: routeActivity,
188
193
  recordTool: recordToolActivity,
189
194
  recordChange: recordCatalogChangeActivity,
190
195
  };
191
196
  }
197
+ /** Operation records reuse the detached sink and accept no payload fields. */
198
+ export function recordOperationActivity(context, input) {
199
+ const operation = checkedActivityOperation(input.operation);
200
+ if (!operation)
201
+ return;
202
+ const connectorId = operation.connectors.length === 1 ? operation.connectors[0] : "$operation";
203
+ recordToolActivity(context, {
204
+ kind: "operation",
205
+ operation,
206
+ connectorId,
207
+ toolName: operation.name,
208
+ address: `${connectorId}.${operation.name}`,
209
+ source: input.source ?? "execute_code",
210
+ outcome: input.outcome,
211
+ durationMs: input.durationMs,
212
+ ...(input.resultBytes !== undefined ? { resultBytes: input.resultBytes } : {}),
213
+ attempts: 1,
214
+ ...(input.errorCode ? { errorCode: input.errorCode } : {}),
215
+ ...(operation.personal ? { privateCatalog: true } : {}),
216
+ });
217
+ }
@@ -51,3 +51,18 @@ export function advertisedSchema(schema) {
51
51
  },
52
52
  };
53
53
  }
54
+ /**
55
+ * An output schema that advertises less than it enforces. The SDK validates each
56
+ * non-error result through `~standard.validate`, so the server keeps the full contract and
57
+ * a client's copy states only what it can use: required keys and top-level types,
58
+ * with every other field open.
59
+ */
60
+ export function openOutputSchema(validator, advertised) {
61
+ const json = deepFreeze(advertised);
62
+ return {
63
+ "~standard": {
64
+ ...validator["~standard"],
65
+ jsonSchema: { input: () => json, output: () => json },
66
+ },
67
+ };
68
+ }
@@ -5,8 +5,13 @@ import { isAgentName } from "./agent-identity.js";
5
5
  import { agentObservationKeys } from "./storage/keys.js";
6
6
  import { Storage } from "./runtime/services.js";
7
7
  import { storageCompareAndSet, storageGet } from "./runtime/storage.js";
8
+ import { physicalStorageAddress } from "./runtime/request-reads.js";
8
9
  const OBSERVATION_TIMEOUT_MS = 500;
9
10
  const OBSERVATION_FRESHNESS_MS = 60_000;
11
+ // Only scalar telemetry deadlines survive requests. Weak physical-store ownership
12
+ // and a bounded map prevent agent/config churn from retaining unbounded state.
13
+ const observedUntil = new WeakMap();
14
+ const MAX_OBSERVED_AGENTS = 256;
10
15
  function timestamp(raw) {
11
16
  if (!raw || raw.length !== 24)
12
17
  return null;
@@ -26,14 +31,31 @@ export function observeAgentAdmission(storage, name, at, timeoutMs) {
26
31
  return Effect.void;
27
32
  return Effect.gen(function* () {
28
33
  const key = agentObservationKeys.agent(name);
34
+ const address = physicalStorageAddress(storage, key) ?? { store: storage, key };
35
+ const now = Date.parse(at);
36
+ let observations = observedUntil.get(address.store);
37
+ if (!observations)
38
+ observedUntil.set(address.store, (observations = new Map()));
39
+ if ((observations.get(address.key) ?? -Infinity) > now)
40
+ return;
41
+ const remember = (seen) => {
42
+ const until = Math.min(seen + OBSERVATION_FRESHNESS_MS, now + OBSERVATION_FRESHNESS_MS);
43
+ if (!observations.has(address.key) && observations.size >= MAX_OBSERVED_AGENTS)
44
+ observations.delete(observations.keys().next().value);
45
+ observations.set(address.key, Math.max(observations.get(address.key) ?? -Infinity, until));
46
+ };
29
47
  for (let attempt = 0; attempt < 2; attempt++) {
30
48
  const previous = yield* storageGet(key);
31
49
  const seen = timestamp(previous);
32
50
  // Future and recently sampled observations both need no write.
33
- if (seen && Date.parse(at) - Date.parse(seen) < OBSERVATION_FRESHNESS_MS)
51
+ if (seen && now - Date.parse(seen) < OBSERVATION_FRESHNESS_MS) {
52
+ remember(Date.parse(seen));
34
53
  return;
35
- if (yield* storageCompareAndSet(key, previous, at))
54
+ }
55
+ if (yield* storageCompareAndSet(key, previous, at)) {
56
+ remember(now);
36
57
  return;
58
+ }
37
59
  }
38
60
  }).pipe(Effect.provideService(Storage, storage), Effect.catchDefect((error) => Effect.fail(error)), Effect.catch(() => Effect.void), Effect.timeoutOption(Duration.millis(Math.min(timeoutMs, OBSERVATION_TIMEOUT_MS))), Effect.asVoid);
39
61
  }