@zackbart/connecta 0.29.0 → 0.30.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 (69) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +123 -134
  3. package/bin/connecta.mjs +13 -2
  4. package/bin/version.mjs +1 -1
  5. package/dist/argument-redaction.js +230 -0
  6. package/dist/auth-elicitation.js +14 -9
  7. package/dist/authorization-handoff.js +111 -0
  8. package/dist/catalog-cache.js +2 -2
  9. package/dist/catalog-service.js +103 -63
  10. package/dist/config.d.ts +2 -0
  11. package/dist/config.js +12 -7
  12. package/dist/connectors/guarded-fetch.js +3 -3
  13. package/dist/connectors/negotiation-cache.js +1 -1
  14. package/dist/downstream-input-context.js +9 -0
  15. package/dist/downstream-input.js +13 -11
  16. package/dist/errors.d.ts +36 -12
  17. package/dist/errors.js +19 -9
  18. package/dist/execute.js +375 -43
  19. package/dist/executor-result.js +11 -9
  20. package/dist/executors/quickjs-child.js +12 -5
  21. package/dist/executors/quickjs-protocol.js +5 -6
  22. package/dist/executors/quickjs-runtime.js +6 -1
  23. package/dist/executors/quickjs.js +20 -4
  24. package/dist/guest-runtime.js +52 -2
  25. package/dist/guest-types.d.ts +47 -5
  26. package/dist/index.d.ts +1 -1
  27. package/dist/invocation.js +40 -14
  28. package/dist/meta-output.js +0 -15
  29. package/dist/meta-tools.js +166 -95
  30. package/dist/oauth-handoff.js +88 -11
  31. package/dist/program-result.js +5 -0
  32. package/dist/program-writes.js +5 -1
  33. package/dist/provider.d.ts +1 -1
  34. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  35. package/dist/providers/_shared/request-token.js +83 -0
  36. package/dist/providers/infisical/index.d.ts +7 -0
  37. package/dist/providers/infisical/index.js +713 -0
  38. package/dist/providers/infisical/skill.generated.js +8 -0
  39. package/dist/registry.d.ts +8 -6
  40. package/dist/registry.js +134 -61
  41. package/dist/resource-read.js +4 -1
  42. package/dist/routes/mcp.js +11 -2
  43. package/dist/sent-secrets.d.ts +54 -13
  44. package/dist/sent-secrets.js +770 -231
  45. package/dist/skills.js +26 -16
  46. package/dist/sqlite.js +21 -6
  47. package/dist/storage/keys.js +20 -5
  48. package/dist/storage/memory.js +9 -4
  49. package/dist/storage/sql.js +18 -9
  50. package/dist/types.d.ts +17 -3
  51. package/dist/usage-guide.js +49 -40
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/worker.js +2 -1
  55. package/documentation/README.md +63 -0
  56. package/documentation/architecture.md +121 -89
  57. package/documentation/auth.md +138 -5
  58. package/documentation/code-mode.md +224 -107
  59. package/documentation/deploying.md +146 -0
  60. package/documentation/integrating.md +159 -0
  61. package/documentation/meta-tools.md +135 -30
  62. package/documentation/operating.md +135 -0
  63. package/documentation/operator-tests.md +4 -0
  64. package/documentation/operator-ui.md +132 -0
  65. package/documentation/provider-migration-0.29.md +5 -0
  66. package/examples/worker/README.md +7 -0
  67. package/package.json +7 -1
  68. package/templates/node/README.md +7 -0
  69. package/templates/node/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,73 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.30.0 — 2026-10-09
6
+
7
+ Connecta 0.30.0 makes large results, sign-in and rich output dependable for agents, on both direct calls and programs. The six-tool surface from 0.29.0 stays. We measured the alternative, folding everything into `execute_code`, against it on Sonnet 5.5 and GPT-6-Luna (#765). The code-only surface scored lower and made a duplicate billable write, so it was not adopted.
8
+
9
+ Default `call_tool` and `call_destructive_tool` results now put their data in `content` and no longer send `structuredContent` or advertise `outputSchema`. Clients that prefer structured content, Claude Code among them, previously saw only a `{"format":"json"}` stub. Read the format from `_meta["dev.connecta/format"]`. For a truncated result, parse the first line of the text as a JSON notice. `resultMode: "value"` keeps the full structured envelope.
10
+
11
+ Programs no longer lose large data. A `connecta.call` result over the executor's inline cap is rebuilt from stash pages up to 1 MiB, and larger results come back as a handle for `connecta.result`. A program that returns more than 24,000 characters gets a pageable `resultId` in place of a truncated preview. If that run made a write, the notice says the write already ran. Paging checks the caller's connector grants on every page. Programs can forward images and other rich content with `connecta.emit(result)` or `result.content`, within the existing byte and block limits.
12
+
13
+ Downstream-OAuth handoff links use compact, opaque tokens that agents copy intact; links issued before the upgrade keep working until they expire. Auth failures on direct calls, program calls and catalog search now carry the sign-in link or operator instructions themselves, so agents no longer need a separate `authorize_connector` step. URL elicitation is unchanged.
14
+
15
+ Values submitted in JSON-Schema `writeOnly` argument fields are now treated like credentials. They are redacted from downstream result text, error text, program output and stored pages, with bounded work and memory. Values under 8 characters are replaced only where a structured field equals them, and prose containing them is withheld. Credential redaction of very large or deeply nested results no longer risks exhausting memory.
16
+
17
+ Custom `KVStorage` adapters must honor the new `expiresAtMs` option on `set` and `compareAndSet` and declare `capabilities: { absoluteExpiry: true }`. Construction rejects adapters without that declaration. The built-in memory, SQLite and D1 drivers already comply. Stash capacity charges now use storage time and hold until orphaned chunks expire, so a skewed or failing host can no longer over-admit stashes. The new maintained Infisical provider lets deployments manage projects, folders and secrets with machine-identity Universal Auth and no hand-written connector.
18
+
19
+ ### Added
20
+
21
+ - Add a maintained Infisical REST provider for machine-identity Universal Auth, projects, folders, and secret reads and writes. Tokens stay in memory and refresh before expiry, lists omit values by default, writes omit values and expose pending approvals, and credential-free OpenAPI checks cover every endpoint. Value-free results, including imports and approvals, return only validated identifiers and omit comments, descriptions, free-form names, reminder notes, and arbitrary nested objects. Project slugs are withheld because Infisical can derive them from free-text project names. Write recovery echoes omit secret values, secret comments, and folder descriptions without changing dispatched arguments. Comments are available only from explicit value reads. `metadataOmitted` reports withheld or unavailable metadata. This protects against careless human data entry on an honest server; adversarial encodings by a malicious server are out of scope. Known, accepted residual risk: environment slugs are returned as required routing identifiers. Infisical's UI derives them from environment names by default, so secrets entered in those names can appear in returned slugs. Operators must not put secrets in environment names or slugs. Secrets placed in identifiers or key names are out of scope. Submitted-value matching and request-secret registration are removed, preserving later program arguments and repeated writes with the same value. Rejected tokens never automatically resend writes; reconcile before an explicit retry. Existing `clientId`/`clientSecret` credentials work when the connector id is preserved. Core uncertainty and retry envelopes omit nested and composed `writeOnly` fields, withhold argument echoes when sensitivity cannot be resolved, and mark partial or withheld echoes with `argsRedacted: true`. Retry using the original arguments. Guarded base-URL validation refuses invalid configuration without quoting its value.
22
+ - Add an eval-only code arm, explicit route/outcome grading, offline baseline regrading and a paired A/B report with a pre-registered correctness and write-safety decision rule. Both agent runners assert the selected tool list; Claude also supports Haiku 5.5.
23
+ - Forward MCP rich content from programs with `connecta.emit(result)` or `result.content`, preserving native blocks and metadata within the existing paging and emission limits. Snapshot retained native blocks within byte, node, and depth limits before paging can yield so later connector mutations cannot change their content or bypass emission limits. Page oversized structured data before inspecting native presentation, and refuse cyclic or over-budget presentation without retaining it.
24
+
25
+ ### Changed
26
+
27
+ - Auth failures on direct calls, program calls, and catalog search/describe now include the authorization URL or operator credential instructions that `authorize_connector` returns. Each call or program run reuses one handoff per connector, without forcing reauthorization. Programs can read typed recovery through `error.data` as well as `error.details`; URL elicitation is unchanged.
28
+ - Separate the human product overview from agent task documentation. Route endpoint operation, deployment, integration, and repository maintenance through an agent index, preserve technical contracts and existing section links, and move the operator UI contract into its own guide.
29
+
30
+ ### Fixed
31
+
32
+ - Read Vercel's complete public MCP inventory from its linked category references, with bounded credential-free fetching and explicit unavailable findings for incomplete documentation.
33
+
34
+ Reconcile category URLs discovered across the entire index with published counts and category tool headings before comparing drift. Unrecognized formatting reports unavailable instead of silently omitting tools, and unused unrelated reference definitions do not block the inventory.
35
+
36
+ Resolve relative category destinations before requiring a count on each category link, exclude CommonMark code examples, and require the tool's known category to reconcile before reporting a removal. Disappearing categories require review, while unrelated count prose does not block the check.
37
+ - **Breaking:** Book each result stash reservation once with a finite deadline of booking time
38
+ plus a 30-second write budget plus the chunk TTL, normally 15 minutes. Stop the
39
+ write loop on timeout and return no paging ID. Absolute chunk and ledger expiry
40
+ prevents late or ambiguous writes from extending that bound. Custom KVStorage
41
+ adapters must honor the new `expiresAtMs` option on set and compareAndSet and
42
+ declare `capabilities: { absoluteExpiry: true }`; construction rejects legacy
43
+ adapters without that explicit opt-in. Rejected write responses retain the
44
+ original reservation because their dispatched operation may commit later.
45
+ Settlement and cleanup shorten or remove charges when possible; after failure
46
+ or exhausted retries, capacity may be over-held until the booked deadline.
47
+ Bound each cleanup/release or settlement path to 15 seconds, including storage
48
+ I/O, and abandon further work when its budget expires so stalled operations
49
+ cannot hang the caller after the write timeout.
50
+ Remove durable completion receipts and their recovery path.
51
+ State-file import and `migrate-state` report database failures with fixed
52
+ step descriptions, preventing SQLite trigger errors from printing imported
53
+ values (#726).
54
+ - Serve identical eval code-arm usage guidance through text, structured content, resource aliases and guest skills. Require clean matching runtime and task/harness provenance before evaluating the registered A/B decision; explicit mismatch diagnostics mark decisions NON-COMPARABLE.
55
+ - Require strict per-trial model, CLI, task and deadline provenance before the eval A/B decision. Count timeout, interruption and runner errors as failures regardless of cached status, report failure kinds per arm, and derive native code-arm skill manifests from the exact served guide bytes.
56
+ - Clarify the cross-connector join eval to select the highest-MRR customer with an open bug, and accept value-mode retained-result notices in the result-paging grader while preserving same-ID paging and single-fetch checks.
57
+ - **Breaking:** Keep default direct-call data and truncation previews visible in clients that prefer structured content. Successful `call_tool` and `call_destructive_tool` results with `resultMode` omitted or set to `"mcp"` now omit `structuredContent`, including its former `format` and paging fields. These tools also stop advertising `outputSchema`.
58
+
59
+ Migrate clients to read result data from `content` and format from `_meta["dev.connecta/format"]` instead of `structuredContent.format`. For truncated default-mode results, parse the first line of the content text as a JSON notice to read `resultId`, `nextOffset`, and `nextAction`, then page with `connecta.result`. Clients that need a structured envelope can request `resultMode: "value"` and keep reading `structuredContent.format` and the paging notice in `structuredContent.data`. Remove assumptions that either direct-call tool advertises an `outputSchema`; use content handling or the value-mode envelope instead. Value-mode and error envelopes retain their complete JSON mirrors.
60
+ - Require storage-side proof of expiry before pruning stash capacity charges, so an ahead caller clock cannot admit another stash while charged chunks remain readable.
61
+ - Make stash charge deadline tests deterministic under runner load across memory, SQLite, and D1.
62
+ - Page oversized call results inside programs through the existing result stash. Reconstruct bounded values across executors and return pageable handles for larger results, keeping completed writes successful and avoiding duplicate downstream calls. Preserve U+FEFF at result and page starts in program reconstruction and direct paging. Report unpageable oversized raw text containing unpaired UTF-16 surrogates without silently replacing its code units.
63
+ - Issue compact downstream-OAuth handoff URLs with opaque, unpadded base64url tokens so agents can copy them intact. Preserve exact signature verification, all identity and lifetime checks, and pre-upgrade links until their original expiry. Authorization guidance now tells agents to copy the URL unchanged and request a fresh link when it is invalid or expired.
64
+ - Stash oversized execute_code returns behind a pageable resultId, preserving the full redacted value without repeating completed writes. Share direct-call expiry, authority checks, page caps, and capacity limits; keep bounded truncation when paging is unavailable. Skill and catalog reads retain their grant dependencies through subsequent return stashes, including caught resolution failures and recovery metadata. Local guides follow connector visibility under exact-tool grants; downstream skills and resources retain whole-connector enforcement. Write warnings reflect only writes completed during the current run.
65
+ - Count Claude eval runner cleanup after successful conversation completion as a normal exit in paired comparisons.
66
+ Accept numeric 143 or signal SIGTERM only after successful completion and a harness SIGTERM sent before any observed child exit.
67
+
68
+ ### Security
69
+
70
+ - Redact submitted `writeOnly` argument values, including property names inside private objects and their encoded forms, from downstream results, errors, resource reads, connector usage guides, downstream skill files, program returns, logs, and emits before they reach agents, guest programs, or result paging storage. JSON results stay valid, with matching numbers and booleans replaced by a placeholder string, and nested JSON strings are decoded and checked. Short private values redact only equal structured fields and withhold prose that contains them, so identifiers and approval state remain; empty strings are exempt. Matching costs amortized constant work per scanned character regardless of how many values are registered, plus one budgeted confirmation per length where a long value's prefix appears; the matcher's memory, including at most 16 Ki runs of waiting confirmations per scanned text, is bounded across every credential and private value, and text that needs more work or runs is withheld. Redaction walks results iteratively and copies only what changes, so an oversized or deeply nested downstream result can no longer exhaust the heap or stack before paging, and one bounded work budget per redaction withholds only the affected field.
71
+
5
72
  ## 0.29.0 — 2026-10-08
6
73
 
7
74
  Connecta 0.29.0 adopts the MCP 2026-07-28 request envelope, binding client capabilities and client identity to each request. `server/discover` advertises the extensions served and private cache hints. Connecta-owned access, pool and deadline refusals return JSON-RPC errors; admission codes move to `-33001` and `-33002`. Modern clients must preserve the protocol-version and declared argument headers. `tools.listChanged` is false, and `subscriptions/listen` remains unsupported.
package/README.md CHANGED
@@ -2,149 +2,144 @@
2
2
 
3
3
  ![A monochrome clay Connecta hub joining many tools](https://raw.githubusercontent.com/zackbart/connecta/main/assets/connecta-clay-hero.png)
4
4
 
5
- One MCP endpoint. The integrations you chose. Your agent reaches them by
6
- writing code instead of loading a thousand tool definitions.
5
+ One connection between your agent and the services you choose.
6
+
7
+ Connecta brings your MCP servers and HTTP APIs together behind one endpoint.
8
+ Connect your agent once, then choose which integrations it can use. You run
9
+ Connecta yourself, on your own machine or infrastructure.
7
10
 
8
11
  ## The mental model
9
12
 
10
- You ask your agent a question that touches a service — Linear, Stripe, an
11
- internal API, anything you have connected. Here is what happens:
13
+ Ask your agent to find open issues, compare them with customer feedback, or
14
+ prepare a report. Connecta gives it a small set of tools for finding and using
15
+ the services you connected, without loading every service's tool definitions
16
+ into the conversation.
17
+
18
+ The agent writes a short program that runs in a sandbox beside those
19
+ integrations. It can gather results, combine them, and return the parts that
20
+ answer your question. Fifty issues can become a short list grouped by owner
21
+ before the result reaches the conversation. The agent writes the code; you
22
+ ask for the work.
23
+
24
+ ```mermaid
25
+ flowchart LR
26
+ Agent["Your agent"] <-->|"One connection"| Connecta["Your Connecta deployment"]
27
+ Connecta <--> Work["Work tools<br/>Linear, GitHub, Notion"]
28
+ Connecta <--> Business["Business services<br/>Stripe, RevenueCat, Mixpanel"]
29
+ Connecta <--> Yours["Your other services<br/>Remote MCP servers and HTTP APIs"]
30
+
31
+ style Connecta fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
32
+ ```
33
+
34
+ You choose which services to connect and who can use them. Your agent keeps
35
+ the same connection as those services change.
12
36
 
13
- 1. The agent talks to one endpoint, yours, and sees six tools. Always six,
14
- no matter how many services sit behind it.
15
- 2. It writes a short JavaScript program. Connecta runs it in a sandbox next to
16
- your integrations. The program can search for tools, call them, chain the
17
- calls, and shape the result.
18
- 3. Only the answer comes back into the agent's context — not raw pages of
19
- API output.
20
- 4. If the agent wants to change something — create, update, delete — it
21
- can do that from a program in a `trusted` pool. A `read-only` pool
22
- routes each write through `call_destructive_tool`. The host controls approval; trusted endpoints annotate `execute_code` as a
23
- write. Pool trust defaults to `read-only`.
37
+ ## From a question to an answer
24
38
 
25
- Credentials never leave the server. The program never sees them, and neither
26
- does the agent.
39
+ Say you ask, "Which open issues affect our highest-value customers?" Your agent
40
+ can gather information from your issue tracker and customer records, combine
41
+ it inside Connecta, and bring back a focused answer.
27
42
 
28
43
  ```mermaid
29
44
  flowchart TB
30
- Client["Your MCP client<br/>Claude, Cursor, …"]
45
+ Question["You ask a question"] --> Plan["Your agent works out what to look up"]
46
+ Plan --> Gather["A program in Connecta gathers the relevant records"]
47
+ Gather --> Tracker["Open issues from your tracker"]
48
+ Gather --> Customers["Customer records from a connected service"]
49
+ Tracker --> Combine["The program matches, filters, and groups the results"]
50
+ Customers --> Combine
51
+ Combine --> Answer["Your agent explains the answer"]
52
+
53
+ style Combine fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
54
+ style Answer fill:#dcfce7,stroke:#15803d,color:#14532d
55
+ ```
31
56
 
32
- subgraph Connecta["Connecta — one endpoint, six tools, your credentials"]
33
- Sandbox["execute_code<br/>the agent's program runs here<br/>reads, plus writes<br/>in trusted pools"]
34
- Explicit["call_destructive_tool<br/>one visible call per write<br/>your client can ask you first"]
35
- end
57
+ The records are an example; the available information depends on the services
58
+ and access you have configured. Processing results inside Connecta keeps the
59
+ conversation focused on the answer.
36
60
 
37
- Integrations["The integrations you chose<br/>Linear · Stripe · Notion · Vercel · your HTTP API · any MCP server"]
61
+ ## What you can do with it
38
62
 
39
- Client -->|"one connection"| Sandbox
40
- Client --> Explicit
41
- Sandbox -->|"reads"| Integrations
42
- Explicit -->|"writes"| Integrations
43
- ```
63
+ Connect hosted MCP services, add an internal HTTP API, and let the agent work
64
+ across them in one task. Changing your connected services does not require
65
+ changing the agent's connection. Your deployment decides which services and
66
+ operations each caller can reach.
67
+
68
+ Connecta includes maintained connections for <!-- providers:start -->Basecamp, Breeze ChMS, Church Community Builder, Cloudflare, GitHub, Gmail, Google Docs, Google Drive, Google Forms, Google Sheets, Google Slides, Infisical, Linear, Mixpanel, Notion, Overflow, Planning Center, RevenueCat, Stripe, Tithe.ly, and Vercel<!-- providers:end -->.
69
+ Each connection has its own setup guide.
70
+ Notion, Vercel, and Cloudflare offer hosted MCP connections and selected HTTP
71
+ API capabilities. You can also connect other remote MCP servers or define the
72
+ operations you need from an HTTP API.
73
+
74
+ Google Workspace connections use the account your deployment assigns to each
75
+ signed-in person. A Workspace administrator authorizes them for your domain.
76
+ Gmail reads mail and writes drafts, but does not send mail. Google Drive can
77
+ read, write, and share files, but does not permanently delete them. Available
78
+ operations and account access depend on the integration and your deployment.
79
+
80
+ Large results need not fill the conversation. The agent can filter and
81
+ summarize them in its program, or read a large result in smaller pieces.
44
82
 
45
- This is the kind of thing the agent writes, not you:
46
-
47
- ```js
48
- async () => {
49
- const { data: { nodes } } = await connecta.call("tracker.list_issues", { state: "started" });
50
- const byOwner = {};
51
- for (const issue of nodes) {
52
- (byOwner[issue.assignee?.name ?? "unassigned"] ??= []).push(issue.identifier);
53
- }
54
- return byOwner;
55
- }
83
+ ## Writes, credentials, and access
84
+
85
+ Connecta distinguishes reads from writes. By default, programs can read, and
86
+ each change uses a separate visible tool call. Your agent's host controls
87
+ whether it asks for approval. You can explicitly allow programs to make
88
+ changes too; the host then sees the program tool as a write. This choice does
89
+ not give a caller access to additional services.
90
+
91
+ ```mermaid
92
+ flowchart TB
93
+ Change["Your agent wants to change something"] --> Choice{"Are programs allowed<br/>to make changes?"}
94
+ Choice -->|"Default: no"| Separate["A separate visible call for each change"]
95
+ Choice -->|"Explicit choice: yes"| Program["A program that can make changes"]
96
+ Separate --> Host["Your agent's host handles approval"]
97
+ Program --> Host
98
+ Host --> Access["Connecta checks the caller's access"]
99
+ Access --> Services["The connected service receives the change"]
100
+
101
+ style Host fill:#fef3c7,stroke:#b45309,color:#78350f
102
+ style Access fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
56
103
  ```
57
104
 
58
- Fifty issues in, one small object out. Your context window notices.
105
+ Connecta handles integration credentials on the server. Its sandbox does not
106
+ receive them. Optional encrypted storage protects saved credentials. Connections can use
107
+ shared credentials or a person's own account, with access rules set by the
108
+ deployment owner. Permission to use a connection is separate from permission
109
+ to manage its credentials.
59
110
 
60
- ## What you can do with it
111
+ People sign in through Clerk on Node or Cloudflare Access on Workers. Machine
112
+ clients use tokens issued by your Connecta deployment. A deployment can serve
113
+ several people within one tenant, each with the access you assign.
114
+
115
+ ## A place to see your connections
61
116
 
62
- - **Put every MCP server you use behind one connection.** Add or remove
63
- services in a config file; your client never changes.
64
- - **Wrap any HTTP API by hand.** A few lines per tool. No OpenAPI conversion —
65
- generated tool sprawl is the problem, not the fix.
66
- - **Use maintained connections** for <!-- providers:start -->Basecamp, Breeze ChMS, Church Community Builder, Cloudflare, GitHub, Gmail, Google Docs, Google Drive, Google Forms, Google Sheets, Google Slides, Linear, Mixpanel, Notion, Overflow, Planning Center, RevenueCat, Stripe, Tithe.ly, and Vercel<!-- providers:end -->: known endpoints, auth defaults, and
67
- vetted read/write classifications, imported one at a time.
68
- Cloudflare, Notion, and Vercel each let the deployment choose their
69
- hand-written API interface or official hosted MCP. Planning Center, Overflow,
70
- Tithe.ly, Breeze, and Church Community Builder are hand-written over their
71
- whole APIs, with every money-moving write behind approval. Gmail reads mail
72
- and writes drafts — never sends — and Google Drive reads, writes, and shares
73
- files — never deletes one for good — as each signed-in Workspace user through
74
- domain-wide delegation, with no per-user consent step; Google Docs reads and
75
- edits documents, and Google Sheets reads and writes spreadsheets, the same
76
- way. Google Slides reads, creates, edits, and comments on each user's decks the
77
- same way.
78
- Google Forms reads and edits forms and reads their responses the same way.
79
- - **Let the agent work in code.** Search, chain, filter, join, and reduce
80
- inside the sandbox instead of round-tripping every call through the model.
81
- - **Teach undeclared result shapes by using them.** Successful read-only calls
82
- retain field names and broad types in bounded runtime memory, never scalar
83
- values, so later programs can project a remote MCP result its provider never
84
- documented.
85
- - **Keep large results usable.** Oversized direct calls return a bounded
86
- preview with a handle for paging through the rest. For read-only work, the
87
- notice also points the agent to reduce or search the result inside a
88
- program. Discovery can show compact schemas, exact JSON Schema, or a
89
- TypeScript signature to read while writing JavaScript.
90
- - **Keep writes deliberate.** The registry classifies tools as reads or writes.
91
- Read-only pools route writes through `call_destructive_tool`; trusted pools
92
- let programs write and annotate `execute_code` as a write. The host controls
93
- approval. Exact deployment classification overrides beat provider review
94
- and downstream annotations.
95
- - **Run it on Node or Cloudflare Workers.** The core is shared; each deployment
96
- supplies its platform's executor and one store: a D1 database on Workers
97
- (`@zackbart/connecta/d1`), a SQLite file on Node (`@zackbart/connecta/sqlite`).
98
- The Node template also runs unchanged in Docker.
99
-
100
- Deployments explicitly compose optional features: `operatorUi()` from
101
- `@zackbart/connecta/ui`, `encryptedCredentialVault()` from `/credentials`,
102
- `activityHistory()` from `/activity`, and
103
- inbound authentication adapters from `/auth/*`. Omit a module and its implementation does no runtime work. Core
104
- keeps connector discovery, execution, invocation, and enforcement together.
105
- Both deployment shapes write configuration as `defineConfig((env) => …)`, with
106
- each optional module a type-checked expression the environment switches on.
107
- `createConnecta` validates it against one schema — an unknown option or an
108
- unusable value refuses to boot — and `connecta.describeConfig()` returns a
109
- secret-free snapshot of what the deployment runs with. With `operatorUi()` enabled,
110
- `GET /ui/api/config` returns that snapshot scoped to the caller, live connector
111
- facts, and the caller's grants and permissions. `CONNECTA_TOKEN=<bearer> connecta
112
- doctor --config --url https://connecta.example` prints only the scoped snapshot
113
- as JSON, using the same serializer. It does not run the diagnostic program.
114
-
115
- The optional UI has Overview, Connectors, Tools, Access, Activity and
116
- Config pages. Connector detail separates configuration, schemas, authentication,
117
- activity and diagnostics into tabs. Overview shows health, attention items and
118
- endpoint setup in a drawer; Config marks each snapshot value as default or
119
- configured. Catalog descriptions and schemas render as text. Activity filters
120
- stay in the URL and calls group by recorded request id. Authentication, history,
121
- and token controls use the signed-in person's existing permissions.
122
- Connector selection and access rules remain in deployment code.
123
-
124
- One deployment may serve several authenticated people inside the same tenant.
125
- Cloudflare Access supplies Worker identity; Node uses Clerk for human auth. Machine clients use connecta-issued `cta_`
126
- access tokens. Static bearer secrets are retired. Connecta owns no accounts or groups. Shared-credential administration and personal connection
127
- setup require separate explicit permissions, both denied by default. See
128
- [inbound auth](./documentation/auth.md#principals-visibility-and-operators).
129
- Clerk deployments enable `aud_claim_enabled` and use resource-bound JWT or
130
- opaque OAuth tokens by default. An explicit `allowedOAuthClientIds` list is a
131
- fallback for unbound tokens from dedicated clients. Clerk session
132
- tokens authenticate operator routes only; [Clerk auth](./documentation/auth.md#clerk-oauth-tokens-and-operator-sessions)
133
- describes endpoint audience validation and the configuration migration.
134
-
135
- Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
136
- service. The [principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md)
137
- state the goals and invariants; [decision records](https://github.com/zackbart/connecta/tree/main/decisions)
138
- explain past choices.
117
+ The optional operator UI shows connected services, available tools, access,
118
+ and configuration. Authorized people can connect accounts, manage credentials,
119
+ and issue client tokens. Optional activity history shows who called what,
120
+ when, and whether it worked, without recording arguments, results, or program
121
+ code. The UI's controls follow the signed-in person's permissions.
122
+
123
+ Your agent or developer changes the integrations and access rules in deployment
124
+ code. The UI helps you inspect them and manage authentication.
125
+
126
+ ## Where it runs
127
+
128
+ Run Connecta locally or on a server with Node, including in Docker, or host it
129
+ on Cloudflare Workers. Both offer the same agent tools. Running programs on
130
+ Workers requires Cloudflare's Workers Paid plan.
139
131
 
140
132
  ## Getting started
141
133
 
142
- Setup is written for an agent. Point yours at [`AGENTS.md`](https://github.com/zackbart/connecta/blob/main/AGENTS.md) and
143
- ask it to set up a Connecta deployment; the
144
- [documentation](./documentation/) covers the architecture, the six tools,
145
- code mode, and inbound auth if you want to go deeper. When upgrading an
146
- existing deployment, each [changelog](./CHANGELOG.md) release opens with what
147
- breaks and what a deployment can ignore.
134
+ Give your agent the [agent documentation](./documentation/README.md) and ask it
135
+ to set up Connecta with the integrations you want. Tell it where you want to
136
+ run it and who should have access. The setup guides cover Node, Docker, and
137
+ Cloudflare Workers, including connecting your agent client.
138
+
139
+ <a id="existing-client-tokens"></a>
140
+
141
+ For an existing deployment, ask your agent to follow the
142
+ [upgrade guidance](./documentation/deploying.md#upgrade-an-existing-deployment).
148
143
 
149
144
  ## Status
150
145
 
@@ -152,13 +147,7 @@ Built for its author's deployments first and published openly. Breaking
152
147
  changes are expected before 1.0. See the [changelog](./CHANGELOG.md) and
153
148
  [security policy](./SECURITY.md).
154
149
 
155
- ### Existing client tokens
156
-
157
- Upgrading from v0.23 does not require rotating managed `cta_…` tokens. Import
158
- `accessTokens` from `@zackbart/connecta/auth/access-tokens`, replace the old
159
- `accessTokens: true` with `accessTokens: accessTokens(storage)`, and keep the
160
- same persistent storage namespace and identity/tool/pool grant rules. Enable
161
- `identity.accessTokenManagement` only for the interactive operators who should
162
- manage tokens. Records written by v0.23 and later move with the store: a 0.28
163
- JSON state file migrates with `connecta migrate-state`, and a D1 table is
164
- read as is. See the package's `documentation/auth.md` for the migration.
150
+ The [principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md)
151
+ state the goals and tested rules. [Decision records](https://github.com/zackbart/connecta/tree/main/decisions)
152
+ explain past choices. [GitHub issues](https://github.com/zackbart/connecta/issues)
153
+ track planned work; a plan is not a shipped feature.
package/bin/connecta.mjs CHANGED
@@ -276,7 +276,13 @@ async function migrateState() {
276
276
  }
277
277
  const [statePath, databasePath] = args.map((arg) => resolve(process.cwd(), arg));
278
278
  const { importStateFile, openSqlite } = await import(pathToFileURL(join(packageRoot, "dist", "sqlite.js")).href);
279
- const database = openSqlite(databasePath);
279
+ let database;
280
+ try {
281
+ database = openSqlite(databasePath);
282
+ } catch {
283
+ throw new Error("State migration failed: could not open the SQLite database");
284
+ }
285
+ let closeFailed = false;
280
286
  try {
281
287
  const result = importStateFile(database, statePath);
282
288
  console.log(
@@ -284,8 +290,13 @@ async function migrateState() {
284
290
  ` (${result.kept} already present, ${result.expired} expired).`,
285
291
  );
286
292
  } finally {
287
- database.close();
293
+ try {
294
+ database.close();
295
+ } catch {
296
+ closeFailed = true;
297
+ }
288
298
  }
299
+ if (closeFailed) throw new Error("State migration failed: could not close the SQLite database");
289
300
  }
290
301
 
291
302
  try {
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.29.0";
3
+ export const CONNECTA_VERSION = "0.30.0";
@@ -0,0 +1,230 @@
1
+ const PUBLIC = {};
2
+ const OMIT = { omit: true };
3
+ const OMITTED = Symbol("omitted argument");
4
+ const MAX_DEPTH = 32;
5
+ const MAX_SCHEMAS = 2048;
6
+ function object(value) {
7
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+ function sensitive(plan) {
10
+ return Object.keys(plan).length > 0;
11
+ }
12
+ function equal(left, right) {
13
+ return JSON.stringify(left) === JSON.stringify(right);
14
+ }
15
+ function item(plan, index) {
16
+ return plan.prefix?.[index] ?? plan.items ?? PUBLIC;
17
+ }
18
+ /** Combine restrictions from references, siblings, and allOf without losing array indices. */
19
+ function merge(left, right) {
20
+ if (left.omit || right.omit)
21
+ return OMIT;
22
+ const names = new Set([...Object.keys(left.properties ?? {}), ...Object.keys(right.properties ?? {})]);
23
+ const properties = Object.fromEntries([...names]
24
+ .sort()
25
+ .map((name) => [
26
+ name,
27
+ merge(Object.hasOwn(left.properties ?? {}, name) ? left.properties[name] : PUBLIC, Object.hasOwn(right.properties ?? {}, name) ? right.properties[name] : PUBLIC),
28
+ ]));
29
+ const items = mergeItems(left.items ?? PUBLIC, right.items ?? PUBLIC);
30
+ const prefix = Array.from({ length: Math.max(left.prefix?.length ?? 0, right.prefix?.length ?? 0) }, (_, index) => mergeItems(item(left, index), item(right, index)));
31
+ // Equivalent plans compare equally even when one schema spells out public prefixes.
32
+ while (prefix.length && equal(prefix.at(-1), items))
33
+ prefix.pop();
34
+ return {
35
+ ...(names.size ? { properties } : {}),
36
+ ...(prefix.length ? { prefix } : {}),
37
+ ...(sensitive(items) ? { items } : {}),
38
+ };
39
+ }
40
+ function mergeItems(left, right) {
41
+ if (!sensitive(left))
42
+ return right;
43
+ if (!sensitive(right))
44
+ return left;
45
+ return merge(left, right);
46
+ }
47
+ /** Compile a bounded plan. Unknown sensitivity refuses the entire echo, never just a branch. */
48
+ function compile(root, unionAlternatives = false) {
49
+ let visited = 0;
50
+ const active = new Set();
51
+ const unresolved = () => {
52
+ throw new Error("Unresolved argument sensitivity");
53
+ };
54
+ function reference(ref) {
55
+ if (typeof ref !== "string" || !ref.startsWith("#"))
56
+ return unresolved();
57
+ const pointer = decodeURIComponent(ref.slice(1));
58
+ if (pointer === "")
59
+ return root;
60
+ if (!pointer.startsWith("/"))
61
+ return unresolved();
62
+ let target = root;
63
+ for (const encoded of pointer.slice(1).split("/")) {
64
+ if (/~[^01]|~$/.test(encoded))
65
+ return unresolved();
66
+ const key = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
67
+ if (target === null || typeof target !== "object" || !Object.hasOwn(target, key))
68
+ return unresolved();
69
+ target = target[key];
70
+ }
71
+ return target;
72
+ }
73
+ function walk(schema, depth) {
74
+ if (++visited > MAX_SCHEMAS || depth > MAX_DEPTH)
75
+ return unresolved();
76
+ if (typeof schema === "boolean")
77
+ return PUBLIC;
78
+ if (!object(schema) || active.has(schema))
79
+ return unresolved();
80
+ if (schema["writeOnly"] !== undefined && typeof schema["writeOnly"] !== "boolean")
81
+ return unresolved();
82
+ if (schema["writeOnly"] === true)
83
+ return OMIT;
84
+ if ((schema !== root && schema["$id"] !== undefined) ||
85
+ schema["$dynamicRef"] !== undefined ||
86
+ schema["$recursiveRef"] !== undefined)
87
+ return unresolved();
88
+ active.add(schema);
89
+ try {
90
+ let plan = PUBLIC;
91
+ const child = (value) => walk(value, depth + 1);
92
+ if (schema["$ref"] !== undefined)
93
+ plan = merge(plan, child(reference(schema["$ref"])));
94
+ if (schema["properties"] !== undefined) {
95
+ if (!object(schema["properties"]))
96
+ return unresolved();
97
+ const properties = Object.entries(schema["properties"])
98
+ .map(([name, value]) => [name, child(value)])
99
+ .filter(([, value]) => sensitive(value))
100
+ .sort(([a], [b]) => a.localeCompare(b));
101
+ plan = merge(plan, properties.length ? { properties: Object.fromEntries(properties) } : PUBLIC);
102
+ }
103
+ const prefixes = schema["prefixItems"] ?? (Array.isArray(schema["items"]) ? schema["items"] : undefined);
104
+ if (prefixes !== undefined && !Array.isArray(prefixes))
105
+ return unresolved();
106
+ if (schema["prefixItems"] !== undefined && Array.isArray(schema["items"]))
107
+ return unresolved();
108
+ const tail = Array.isArray(schema["items"]) ? schema["additionalItems"] : schema["items"];
109
+ const arrayPlan = {
110
+ ...(prefixes ? { prefix: prefixes.map(child) } : {}),
111
+ ...(tail !== undefined ? { items: child(tail) } : {}),
112
+ };
113
+ plan = merge(plan, arrayPlan);
114
+ for (const keyword of ["allOf", "oneOf", "anyOf"]) {
115
+ const branches = schema[keyword];
116
+ if (branches === undefined)
117
+ continue;
118
+ if (!Array.isArray(branches) || !branches.length)
119
+ return unresolved();
120
+ const plans = branches.map(child);
121
+ if (keyword === "allOf" || unionAlternatives)
122
+ for (const branch of plans)
123
+ plan = merge(plan, branch);
124
+ else {
125
+ if (plans.some((branch) => !equal(branch, plans[0])))
126
+ return unresolved();
127
+ plan = merge(plan, plans[0]);
128
+ }
129
+ }
130
+ // These applicators need matching/evaluation rules we do not implement.
131
+ // Public-only schemas are harmless; sensitivity or an unresolved reference fails closed.
132
+ for (const keyword of [
133
+ "additionalProperties",
134
+ "unevaluatedProperties",
135
+ "unevaluatedItems",
136
+ "contains",
137
+ "not",
138
+ "if",
139
+ "then",
140
+ "else",
141
+ ])
142
+ if (schema[keyword] !== undefined && sensitive(child(schema[keyword])))
143
+ return unresolved();
144
+ for (const keyword of ["patternProperties", "dependentSchemas", "dependencies"]) {
145
+ if (schema[keyword] === undefined)
146
+ continue;
147
+ if (!object(schema[keyword]))
148
+ return unresolved();
149
+ for (const value of Object.values(schema[keyword])) {
150
+ if (keyword === "dependencies" && Array.isArray(value))
151
+ continue;
152
+ if (sensitive(child(value)))
153
+ return unresolved();
154
+ }
155
+ }
156
+ return plan;
157
+ }
158
+ finally {
159
+ active.delete(schema);
160
+ }
161
+ }
162
+ return walk(root, 0);
163
+ }
164
+ /** Omit whole array values when private elements cannot be removed without shifting indices. */
165
+ function apply(value, plan, mark, depth = 0) {
166
+ if (!sensitive(plan))
167
+ return value;
168
+ if (depth > MAX_DEPTH)
169
+ throw new Error("Argument redaction depth exceeded");
170
+ if (plan.omit) {
171
+ mark(value);
172
+ return OMITTED;
173
+ }
174
+ if (Array.isArray(value)) {
175
+ const values = value.map((entry, index) => apply(entry, item(plan, index), mark, depth + 1));
176
+ return values.includes(OMITTED) ? OMITTED : values;
177
+ }
178
+ if (!object(value))
179
+ return value;
180
+ return Object.fromEntries(Object.entries(value).flatMap(([name, entry]) => {
181
+ const field = Object.hasOwn(plan.properties ?? {}, name) ? plan.properties[name] : PUBLIC;
182
+ const filtered = apply(entry, field, mark, depth + 1);
183
+ return filtered === OMITTED ? [] : [[name, filtered]];
184
+ }));
185
+ }
186
+ /** Output redaction protects the union of private fields across alternatives.
187
+ * Other sensitivity rules and unresolved-schema handling follow echo redaction. */
188
+ export function visitPrivateCallArguments(args, schema, visit) {
189
+ // Public-only schemas need no sensitivity compilation, regardless of size.
190
+ // Iterative traversal visits each object once, including definitions and cycles.
191
+ const pending = [schema];
192
+ const seen = new Set();
193
+ let privateSchema = false;
194
+ while (pending.length) {
195
+ const value = pending.pop();
196
+ if (value === null || typeof value !== "object" || seen.has(value))
197
+ continue;
198
+ seen.add(value);
199
+ if (Object.hasOwn(value, "writeOnly") && value["writeOnly"] === true) {
200
+ privateSchema = true;
201
+ break;
202
+ }
203
+ for (const entry of Object.values(value))
204
+ pending.push(entry);
205
+ }
206
+ if (!privateSchema)
207
+ return;
208
+ try {
209
+ apply(args, compile(schema, true), visit);
210
+ }
211
+ catch {
212
+ // When a private annotation exists but its location cannot be resolved,
213
+ // treat every submitted value as private, just as the echo is withheld.
214
+ visit(args);
215
+ }
216
+ }
217
+ /** Internal schema filter; the single echo helper owns snapshotting and byte budgeting. */
218
+ export function redactCallArguments(args, schema) {
219
+ try {
220
+ const plan = compile(schema);
221
+ let redacted = false;
222
+ const value = apply(args, plan, () => {
223
+ redacted = true;
224
+ });
225
+ return { ...(value !== OMITTED ? { value } : {}), redacted };
226
+ }
227
+ catch {
228
+ return { redacted: true };
229
+ }
230
+ }