@zackbart/connecta 0.27.0 → 0.28.1

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 (73) hide show
  1. package/AGENTS.md +6 -6
  2. package/CHANGELOG.md +229 -0
  3. package/README.md +23 -24
  4. package/bin/connecta.mjs +1 -5
  5. package/dist/activity.d.ts +5 -4
  6. package/dist/activity.js +1 -10
  7. package/dist/artifacts/connector.js +1 -1
  8. package/dist/artifacts/json.js +10 -0
  9. package/dist/artifacts/operations.js +1 -1
  10. package/dist/artifacts/validate.js +1 -2
  11. package/dist/auth/downstream-oauth.js +147 -28
  12. package/dist/auth/static-oauth.js +504 -0
  13. package/dist/catalog-service.js +1 -1
  14. package/dist/connectors/api-connector.d.ts +168 -0
  15. package/dist/connectors/api-connector.js +127 -0
  16. package/dist/connectors/api.d.ts +8 -71
  17. package/dist/connectors/api.js +10 -99
  18. package/dist/connectors/guarded-fetch.js +37 -3
  19. package/dist/connectors/remote-mcp.js +7 -32
  20. package/dist/execute.js +92 -368
  21. package/dist/executors/quickjs.js +1 -1
  22. package/dist/exempt-writes.js +162 -0
  23. package/dist/index.d.ts +13 -32
  24. package/dist/index.js +14 -47
  25. package/dist/invocation.js +6 -19
  26. package/dist/meta-tools.js +1 -2
  27. package/dist/oauth-partition.js +17 -0
  28. package/dist/operator-ui/generated.js +1 -1
  29. package/dist/operator-ui/model.d.ts +3 -3
  30. package/dist/operator-ui/view.js +6 -4
  31. package/dist/providers/basecamp.d.ts +65 -0
  32. package/dist/providers/basecamp.js +445 -0
  33. package/dist/providers/breeze.d.ts +34 -0
  34. package/dist/providers/breeze.js +1402 -0
  35. package/dist/providers/ccb.d.ts +103 -0
  36. package/dist/providers/ccb.js +1570 -0
  37. package/dist/providers/cloudflare.js +1 -1
  38. package/dist/providers/linear.js +1 -1
  39. package/dist/providers/mixpanel.js +1 -1
  40. package/dist/providers/notion.js +1 -1
  41. package/dist/providers/overflow.d.ts +60 -0
  42. package/dist/providers/overflow.js +1572 -0
  43. package/dist/providers/planning-center.d.ts +59 -0
  44. package/dist/providers/planning-center.js +2039 -0
  45. package/dist/providers/revenuecat.js +1 -1
  46. package/dist/providers/stripe.js +1 -1
  47. package/dist/providers/tithely.d.ts +49 -0
  48. package/dist/providers/tithely.js +1034 -0
  49. package/dist/providers/vercel.js +1 -1
  50. package/dist/routes/mcp.js +5 -21
  51. package/dist/routes/oauth.js +29 -3
  52. package/dist/routes/shared.d.ts +1 -6
  53. package/dist/server.js +0 -4
  54. package/dist/skills.js +8 -23
  55. package/dist/tool-safety.d.ts +1 -1
  56. package/dist/tool-safety.js +1 -1
  57. package/dist/types.d.ts +2 -2
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/documentation/architecture.md +15 -17
  61. package/documentation/auth.md +120 -6
  62. package/documentation/code-mode.md +91 -194
  63. package/documentation/meta-tools.md +38 -41
  64. package/ethos.md +8 -9
  65. package/examples/worker/README.md +10 -14
  66. package/examples/worker/src/d1-activity-row.ts +4 -1
  67. package/examples/worker/src/index.ts +1 -9
  68. package/package.json +26 -2
  69. package/templates/node/AGENTS.md +4 -3
  70. package/templates/node/README.md +3 -4
  71. package/templates/node/package.json +1 -1
  72. package/dist/resumable.js +0 -1323
  73. package/dist/run-journal.js +0 -347
package/AGENTS.md CHANGED
@@ -4,13 +4,13 @@ This is the canonical instruction file for coding agents. `CLAUDE.md` is a
4
4
  symlink to this file so every agent works from the same conventions.
5
5
 
6
6
  A single MCP endpoint aggregating downstream connectors (remote MCP servers and
7
- plain HTTP APIs) behind eight meta-tools, `execute_code` among them. Every
7
+ plain HTTP APIs) behind seven meta-tools, `execute_code` among them. Every
8
8
  deployment configures an executor, and agents reach connectors by writing
9
9
  JavaScript against it. One fetch-native core on Effect behind a Promise API,
10
- running on both Node and Cloudflare Workers. A program's write pauses until
11
- `resume_execution` repeats it. One `planned` decision changes that picture — an
12
- artifacts module — and until it ships, the code and the guides describe what
13
- exists.
10
+ running on both Node and Cloudflare Workers. A program runs read-only and
11
+ config-exempt tools; every other write is its own `call_destructive_tool` call,
12
+ so the host's permission prompt is the only approval. The code and the guides
13
+ describe what exists.
14
14
 
15
15
  - **[`ethos.md`](./ethos.md) is the constitution.** It states what connecta is
16
16
  and isn't, and its decisions table carries a verdict for every shape already
@@ -74,7 +74,7 @@ pins the generated deployment to the CLI package's exact version, restores the
74
74
  template `.gitignore` and the `CLAUDE.md` symlink, and refuses to merge into an
75
75
  existing path.
76
76
  `connecta doctor` verifies a running deployment's health, executor, and exact
77
- eight-tool surface. The template carries its own `Dockerfile` and
77
+ seven-tool surface. The template carries its own `Dockerfile` and
78
78
  `docker-compose.yml`, so the generated project is the container: setup changes
79
79
  must keep the root README, the template (source, container files, and README),
80
80
  and the `scripts/check-package.mjs` smoke — which builds and runs that
package/CHANGELOG.md CHANGED
@@ -2,6 +2,235 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.28.1 — 2026-10-05
6
+
7
+ This patch adds six maintained connections for church operations and the
8
+ downstream OAuth they needed. Planning Center, Overflow, Tithe.ly, Breeze ChMS,
9
+ and Church Community Builder (Pushpay ChMS) are hand-written `api()` surfaces
10
+ over their whole APIs, with every money-moving write behind approval; Basecamp
11
+ proxies 37signals' hosted MCP server. Church Community Builder accepts only
12
+ OAuth, so `api()` connectors can now declare a static OAuth 2.0
13
+ authorization-code grant that reuses the `remoteMcp()` machinery. Each
14
+ connection is a new opt-in provider subpath and the OAuth grant is opt-in per
15
+ connector: existing imports, connector declarations, credentials, and
16
+ deployment behavior do not change. Two fixes a deployment may notice: a forced
17
+ OAuth restart no longer reuses a client registration the provider has deleted
18
+ ([#611](https://github.com/zackbart/connecta/issues/611)), and a failed OAuth
19
+ code exchange is now logged as fixed text rather than the provider's message.
20
+ Internally, the guarded transport can repeat a query key for an array value,
21
+ and `api()`'s builder moved into its own module so OAuth stays out of bundles
22
+ that never use it.
23
+
24
+ ### Added
25
+
26
+ - **Downstream OAuth on `api()` connectors.** A hand-written connector can
27
+ declare `oauth: { authorizationEndpoint, tokenEndpoint, clientId,
28
+ clientSecret?, tokenEndpointAuthMethod?, scope?, pkce?, authorizationParams?,
29
+ tokenRequestHeaders?, apiOrigins }` for a REST API that accepts only the authorization code grant,
30
+ such as Church Community Builder. Nothing is discovered or registered: the
31
+ endpoints and the provider-issued client are configuration, and the client
32
+ secret comes from the deployment's environment and is never stored. Handlers
33
+ call `ctx.oauth.fetch(url, init)`, which sends the calling owner's token as a
34
+ bearer to a declared origin only, answers a 401 with one coordinated refresh
35
+ and one replay, and otherwise fails `auth_required`. The grant is the
36
+ `remoteMcp()` grant underneath — the same sealed epochs, shared and personal
37
+ ownership, rotating-refresh coordination, dead-versus-outage verdicts,
38
+ `/oauth/callback/<id>`, `authorize_connector`, and operator Connect,
39
+ Restart, and Disconnect. `oauth` and `credential` are exclusive on one
40
+ connector. See [downstream OAuth on `api()`](./documentation/auth.md#downstream-oauth-on-api).
41
+ - **Church Community Builder (Pushpay ChMS) connection.**
42
+ `@zackbart/connecta/providers/ccb` exports `ccb(id, { purpose, environment,
43
+ mode, clientId, clientSecret, access?, scopes?, subdomain? })`, a
44
+ hand-written v2 REST connection over `api()`'s OAuth grant. `environment`
45
+ (`production` or `sandbox`) and `mode` (`system`, one church-wide grant an
46
+ administrator approves; `identity`, each person signing in as themselves
47
+ with a personal grant) are required, because CCB credentials do not cross
48
+ environments and the two modes reach different data. Twenty-four named reads
49
+ cover people and families, groups and participants, events and attendance,
50
+ forms and responses, processes and queues, scheduling, notes, and giving
51
+ metrics and pledges, with projections that drop allergies, giving
52
+ identifiers, and prayer requests unless `raw: true`. `ccb_api_get` and
53
+ `ccb_api_search` reach every GET and advanced search read-only, and
54
+ `ccb_api_mutate` ships only when a write scope is requested. A default
55
+ 60-per-minute admission window per endpoint transcribes CCB's documented
56
+ limit, and `npm run providers:check -- --provider ccb` compares the 41
57
+ touched endpoints with Pushpay's published OpenAPI document. Clients are
58
+ issued by hand through Pushpay's API access request form.
59
+ - **Maintained Planning Center connection.**
60
+ `@zackbart/connecta/providers/planning-center` exports `planningCenter(id,
61
+ options)`, a hand-written `api()` surface over Planning Center Online's REST
62
+ API with no SDK and no new dependency. Named tools cover the jobs church
63
+ staff run — People search, profiles, contact records, lists, workflows and
64
+ card actions, notes, custom fields, and form submissions; Services plans,
65
+ teams, schedules, songs, and scheduling; Groups memberships and events;
66
+ Check-Ins attendance; Calendar occurrences; Registrations signups; and Giving
67
+ donations, funds, batches, pledges, and recurring gifts as reads only — with
68
+ JSON:API flattened into lean records and `raw: true` beside them.
69
+ `pco_api_get` (read-only) and `pco_api_mutate` (always approval-gated) reach
70
+ every other endpoint, including every write that moves money. The credential
71
+ is a personal access token as two labeled fields, and the Test action names
72
+ the person and organization it acts as. Every request sends a reviewed
73
+ `X-PCO-API-Version` per product and a configurable `User-Agent`, and the
74
+ default call budget transcribes Planning Center's 100 requests per 20
75
+ seconds. `npm run providers:check -- --provider planning-center` compares
76
+ the touched endpoints with Planning Center's per-product OpenAPI documents
77
+ and reports a newly published version of any pinned product. Planning
78
+ Center's own hosted MCP server was considered and is not wrapped: its writes
79
+ stop at People.
80
+ - **Maintained Overflow connection.** `@zackbart/connecta/providers/overflow`
81
+ ships 16 projected reads over contributions, deposits and their line items
82
+ and totals, refunds, chargebacks, donors, recurring gifts, payment methods,
83
+ campaigns, locations, Tap events, and webhook delivery logs, plus a
84
+ read-only GET hatch and a destructive JSON-mutation hatch that carries every
85
+ Overflow write — charges, payment authorization, refunds, recurring-gift
86
+ changes, and donor edits — to the host's approval prompt. `environment`
87
+ (`"production"` or `"staging"`) is required with no default, the credential
88
+ is one operator slot with `clientId` and `apiKey` fields, list rows keep
89
+ donor contact details and webhook payloads out unless `raw: true`, and the
90
+ default admission budget transcribes Overflow's 120 requests per minute per
91
+ client. `npm run providers:check -- --provider overflow` compares all 38
92
+ operations with the OpenAPI document Overflow publishes on staging.
93
+ - A maintained Tithe.ly giving connection, `tithely(id, { purpose, environment })`
94
+ from `@zackbart/connecta/providers/tithely`: a hand-written `api()` surface
95
+ over Tithe.ly's v1 REST API. `environment` (`"live"` for tithe.ly, `"test"`
96
+ for tithelydev.com) is required with no default, because Tithe.ly's key pairs
97
+ are environment-specific and carry nothing that says which. The credential
98
+ slot takes the public and private keys as two fields, refuses an obviously
99
+ swapped pair without a request, and its Test action reports the organization
100
+ the keys reach; API access is by request through support@tithe.ly. Named
101
+ read-only tools cover organizations and their funds, donor accounts, charges
102
+ by organization or donor and created-date range, recurring gifts, and
103
+ payment-method metadata, with amounts as integer cents, dates as ISO 8601,
104
+ `hasMore` paging, and organization projections that omit payout bank and
105
+ legal-contact details. Every write moves money or donor payment state and is
106
+ reached only through the always-destructive, form-encoded
107
+ `tithely_api_mutate`; `tithely_api_get` reaches every v1 read. No call budget
108
+ is declared, since Tithe.ly publishes no rate limit.
109
+ `npm run providers:check -- --provider tithely` digests the OpenAPI snippet
110
+ each reference page embeds, for every documented v1 operation.
111
+ - A maintained Breeze ChMS connection (Tithely Church Management since
112
+ 2025-11-11) at `@zackbart/connecta/providers/breeze`:
113
+ `breeze(id, { subdomain, purpose })` binds one church's
114
+ `<subdomain>.breezechms.com` — validated as a single hostname label at
115
+ construction, so no argument can steer the API key elsewhere — with an
116
+ operator `Api-Key` slot tested against the account summary. Named reads cover
117
+ people, profile fields, tags, events, calendars, attendance, forms,
118
+ volunteers, the account log, and giving (`list_contributions`, `list_funds`,
119
+ which call endpoints Breeze stopped documenting in 2023 and still serves);
120
+ named writes add and update people, assign and unassign tags, and record a
121
+ check-in. Because Breeze sends writes as GET too, the escape hatches split by
122
+ endpoint rather than method: `breeze_api_get` admits only a reviewed
123
+ allowlist of read endpoints and their documented parameters, and the
124
+ destructive `breeze_api_mutate` reaches everything else. No admission budget
125
+ is declared, since Breeze documents no rate limit; drift is review-only,
126
+ since it publishes no machine-readable specification.
127
+ - **`basecamp()` — a maintained Basecamp hosted-MCP connection** at
128
+ `@zackbart/connecta/providers/basecamp`, proxying
129
+ `https://mcp.basecamp.com/mcp` — a server 37signals runs but has not
130
+ documented or announced. OAuth only, and `clientMetadataUrl` is required:
131
+ Basecamp restricts dynamic client registration for HTTPS callbacks, so a
132
+ deployment hosts a Client ID Metadata Document listing
133
+ `<publicUrl>/oauth/callback/<id>`, and construction throws without one. A
134
+ grant reaches exactly one Basecamp account, chosen at consent, so the guide
135
+ leads with that, names `get_me` to confirm it, and a second account is a
136
+ second connector. All 227 tools in the live catalog are classified (105
137
+ read-only, 34 additive, 88 destructive); the four verdicts that part from the
138
+ server's own annotations, and `create_stream_ticket`'s agreement with its
139
+ read-only claim, are argued beside their rows. No access mode ships: the MCP
140
+ client requests the scope Basecamp's challenge names, so a `read` option
141
+ would be a label rather than a limit. Personal access tokens are not offered,
142
+ because nothing shows this endpoint accepts one. No default `callAdmission`:
143
+ Basecamp's limits are dynamic, multi-tier, and metered at its own server's
144
+ IP. With no public setup page or inventory, `npm run providers:check` reads
145
+ the server's OAuth discovery instead — the resource names the endpoint and
146
+ the authorization server still accepts metadata-document clients — and
147
+ catalog drift is runtime-only.
148
+
149
+ ### Fixed
150
+
151
+ - **A forced OAuth restart can no longer wedge on a purged client
152
+ registration** ([#611](https://github.com/zackbart/connecta/issues/611)).
153
+ Restart already carried an issuer-bound dynamic registration into the new
154
+ epoch; it carried it unconditionally, and a provider that had forgotten the
155
+ client refuses it at consent without ever redirecting back, so every later
156
+ Restart reused the same dead client and only Disconnect escaped. A carried
157
+ client is now carried again only once a grant in its epoch proves the
158
+ provider still knows it, so two restarts in a row with no consent between
159
+ them register at most once more. A URL-based client (`clientMetadataUrl`)
160
+ and a client whose secret has expired are never carried. Continue no longer
161
+ hands back a pending consent URL whose client a callback's `invalid_client`
162
+ has dropped; it starts a flow that registers instead. Static `api()` OAuth
163
+ clients are configuration and unaffected.
164
+ - **A refresh rotation saved after its request was cancelled keeps its
165
+ issuer.** When the token endpoint answered after the owning request left,
166
+ connecta persisted the rotated tokens itself but unbound, and the next
167
+ issuer-aware read adopted them for whichever authorization server the
168
+ connector named by then — so a connector repointed at another server would
169
+ send the rotated refresh token there. The recovery save now stamps the issuer
170
+ the refresh answered to, as the SDK's own save does, and a repointed connector
171
+ fences the grant instead.
172
+ - **A failed OAuth code exchange no longer writes the provider's message to the
173
+ operator log.** The SDK quotes a token endpoint's `error_description` or raw
174
+ body, and a provider that echoes a `client_secret_post` request put the client
175
+ secret, code, and verifier in the log. The callback now logs fixed text and,
176
+ when the error carries one, a standard OAuth error code from an allowlist.
177
+
178
+ ## 0.28.0 — 2026-10-04
179
+
180
+ This minor release removes program pauses ([#672](https://github.com/zackbart/connecta/issues/672)).
181
+ `execute_code` programs run explicitly read-only tools and the writes
182
+ `execute.approval` exempts, and every other write is its own
183
+ `call_destructive_tool` call, so the host's permission prompt on that call is
184
+ the only approval there is. Hosts approve one tool call at a time, and under
185
+ always-allow or bypass modes the agent called `resume_execution` itself: each
186
+ pause cost a round trip and a full replay and protected nothing. What breaks:
187
+ `resume_execution` is gone, so the surface is seven tools again, and a host
188
+ allow-list or cached tool list naming it should drop it.
189
+ `execute.resumableWrites` and `execute.pausedRunTtlSeconds` now refuse to
190
+ construct with an error naming
191
+ the removal — delete them, including the Worker example's
192
+ `execute: { resumableWrites: false }` line. A program's non-exempt write now
193
+ fails `destructive_tool_requires_approval` with a `nextAction` for
194
+ `call_destructive_tool`, exactly as it did on deployments without resumable
195
+ writes, and a run never returns `{ paused: … }`. `Date.now()` and
196
+ `Math.random()` inside a program are the runtime's own again rather than pinned
197
+ per run. A deployment that sets neither option and whose programs only read
198
+ can ignore all of this: approval exemptions, `execute.maxWrites`, storage
199
+ contracts, and the D1 activity schema are unchanged, and any paused runs left
200
+ in storage expire on their own TTL. The Node template now pins 0.28.0.
201
+
202
+ ### Removed
203
+
204
+ - `resume_execution`, the run journal, deterministic replay, compare-and-set
205
+ run claims, and the pinned clock and randomness (code-mode `P6`) that
206
+ existed only so a replay could match. `W1`–`W8`, `W11`, `P6`, and `X12` are
207
+ retired clause ids.
208
+ - The pause-only error codes: `execution_paused`, `pending_write_too_large`,
209
+ `journal_too_large`, `execution_interrupted`, `execution_diverged`,
210
+ `execution_claim_lost`, `approval_mismatch`, `execution_token_stale`,
211
+ `execution_in_progress`, `execution_expired`, `execution_not_found`, and
212
+ `resumable_writes_unavailable`. `write_outcome_unknown` stays, for an exempt
213
+ write that was sent and never answered.
214
+ - `resumableWrites` from `/health`, and the "resumable writes off" note from
215
+ `connecta doctor`, which now verifies the exact seven-tool surface.
216
+ - The pause and resume agent eval tasks.
217
+
218
+ ### Changed
219
+
220
+ - The MCP instructions, the `execute_code` description, and the `usage` skill
221
+ describe the read-only program route: reads and config-exempt writes in a
222
+ program, every other write through `call_destructive_tool`. The description
223
+ states the exempt-write budget.
224
+ - `execute.maxWrites` now bounds only config-exempt writes, the one kind a
225
+ program can still send. Its default stays 10.
226
+ - Activity no longer emits `paused` or `approved` outcomes, the
227
+ `resume_execution` source, or an `approval` scope. The types keep them as
228
+ read-only history, the operator UI still renders older rows (a historical
229
+ pause now reads "Paused for approval"), and the D1 example still stores and
230
+ reads the `approval` column.
231
+ - QuickJS still runs its child with `TZ=UTC`, now for parity with a Dynamic
232
+ Worker's local time rather than for replay.
233
+
5
234
  ## 0.27.0 — 2026-10-01
6
235
 
7
236
  This minor release moves the runtime to stable Effect v4 and fixes execution,
package/README.md CHANGED
@@ -10,19 +10,18 @@ writing code instead of loading a thousand tool definitions.
10
10
  You ask your agent a question that touches a service — Linear, Stripe, an
11
11
  internal API, anything you have connected. Here is what happens:
12
12
 
13
- 1. The agent talks to one endpoint, yours, and sees eight tools. Always eight,
13
+ 1. The agent talks to one endpoint, yours, and sees seven tools. Always seven,
14
14
  no matter how many services sit behind it.
15
15
  2. It writes a short JavaScript program. Connecta runs it in a sandbox next to
16
16
  your integrations. The program can search for tools, call them, chain the
17
17
  calls, and shape the result.
18
18
  3. Only the answer comes back into the agent's context — not raw pages of
19
19
  API output.
20
- 4. If the program wants to change something — create, update, delete — it
21
- stops right there, before anything is sent, and hands the agent the exact
22
- write. The agent repeats it through `resume_execution`, which your MCP
23
- client can put in front of you first. Approve it and the program picks up
24
- where it stopped, without re-reading what it already read. A single write
25
- can also go straight through `call_destructive_tool`.
20
+ 4. If the agent wants to change something — create, update, delete — it
21
+ cannot do that from a program, unless your config exempts that tool. The
22
+ program refuses the write before anything is sent and hands back the exact
23
+ call; the agent makes it through `call_destructive_tool`, one visible call
24
+ your MCP client can put in front of you first.
26
25
 
27
26
  Credentials never leave the server. The program never sees them, and neither
28
27
  does the agent.
@@ -31,9 +30,9 @@ does the agent.
31
30
  flowchart TB
32
31
  Client["Your MCP client<br/>Claude, Cursor, …"]
33
32
 
34
- subgraph Connecta["Connecta — one endpoint, eight tools, your credentials"]
35
- Sandbox["execute_code<br/>the agent's program runs here<br/>reads run, writes pause"]
36
- Explicit["resume_execution · call_destructive_tool<br/>the exact write, visible<br/>your client can ask you first"]
33
+ subgraph Connecta["Connecta — one endpoint, seven tools, your credentials"]
34
+ Sandbox["execute_code<br/>the agent's program runs here<br/>read-only tools, plus writes<br/>your config exempts"]
35
+ Explicit["call_destructive_tool<br/>one visible call per write<br/>your client can ask you first"]
37
36
  end
38
37
 
39
38
  Integrations["The integrations you chose<br/>Linear · Stripe · Notion · Vercel · your HTTP API · any MCP server"]
@@ -41,7 +40,6 @@ flowchart TB
41
40
  Client -->|"one connection"| Sandbox
42
41
  Client --> Explicit
43
42
  Sandbox -->|"reads"| Integrations
44
- Sandbox -.->|"paused write"| Explicit
45
43
  Explicit -->|"writes"| Integrations
46
44
  ```
47
45
 
@@ -66,11 +64,14 @@ Fifty issues in, one small object out. Your context window notices.
66
64
  services in a config file; your client never changes.
67
65
  - **Wrap any HTTP API by hand.** A few lines per tool. No OpenAPI conversion —
68
66
  generated tool sprawl is the problem, not the fix.
69
- - **Use maintained connections** for Cloudflare, Linear, Mixpanel, Notion,
70
- RevenueCat, Stripe, and Vercel: known endpoints, auth defaults, and vetted
71
- read/write classifications, imported one at a time. Cloudflare, Notion, and
72
- Vercel each let the deployment choose their hand-written API interface or
73
- official hosted MCP.
67
+ - **Use maintained connections** for Basecamp, Breeze ChMS, Church Community
68
+ Builder, Cloudflare, Linear, Mixpanel, Notion, Overflow, Planning Center,
69
+ RevenueCat, Stripe, Tithe.ly, and Vercel: known endpoints, auth defaults, and
70
+ vetted read/write classifications, imported one at a time. Cloudflare,
71
+ Notion, and Vercel each let the deployment choose their hand-written API
72
+ interface or official hosted MCP. Planning Center, Overflow, Tithe.ly,
73
+ Breeze, and Church Community Builder are hand-written over their whole APIs,
74
+ with every money-moving write behind approval.
74
75
  - **Let the agent work in code.** Search, chain, filter, join, and reduce
75
76
  inside the sandbox instead of round-tripping every call through the model.
76
77
  - **Teach undeclared result shapes by using them.** Successful read-only calls
@@ -82,13 +83,11 @@ Fifty issues in, one small object out. Your context window notices.
82
83
  notice also points the agent to reduce or search the result inside a
83
84
  program. Discovery can show compact schemas, exact JSON Schema, or a
84
85
  TypeScript signature to read while writing JavaScript.
85
- - **Keep writes deliberate.** Only tools marked read-only run in a program
86
- unasked. Any other call pauses the program with the exact write, and it
87
- runs only once `resume_execution` repeats it — a visible call your client
88
- can gate. A paused run is a journal in storage, not a held program, so it
89
- survives a restart; it expires, and a write whose outcome is unknown is
90
- never sent twice. Config — and only config — can exempt a cheap,
91
- reversible write from asking, per tool or per connector.
86
+ - **Keep writes deliberate.** Only tools marked read-only run in a program.
87
+ Every other write is a separate, visible `call_destructive_tool` call your
88
+ client can gate, so the host's permission prompt is the one approval there
89
+ is. Config — and only config — can exempt a cheap, reversible write from
90
+ asking, per tool or per connector, and then a program may make it.
92
91
  - **Run it on Node or Cloudflare Workers.** The core is shared; each deployment
93
92
  supplies its platform's executor and storage. The Node template also runs
94
93
  unchanged in Docker.
@@ -122,7 +121,7 @@ and why.
122
121
 
123
122
  Setup is written for an agent. Point yours at [`AGENTS.md`](./AGENTS.md) and
124
123
  ask it to set up a Connecta deployment; the
125
- [documentation](./documentation/) covers the architecture, the eight tools,
124
+ [documentation](./documentation/) covers the architecture, the seven tools,
126
125
  code mode, and inbound auth if you want to go deeper. When upgrading an
127
126
  existing deployment, each [changelog](./CHANGELOG.md) release opens with what
128
127
  breaks and what a deployment can ignore.
package/bin/connecta.mjs CHANGED
@@ -257,7 +257,6 @@ async function doctor() {
257
257
  "call_tool",
258
258
  "execute_code",
259
259
  "get_result",
260
- "resume_execution",
261
260
  "search_tools",
262
261
  "skills",
263
262
  ];
@@ -310,10 +309,7 @@ async function doctor() {
310
309
  console.log(
311
310
  `Connecta doctor passed: ${health.connectors} connector(s), ` +
312
311
  `${executorName ? `${executorName} executed` : "code executed"}, ` +
313
- "prescribed eight-tool surface" +
314
- // resume_execution is listed either way; whether programs can write is
315
- // what an operator cannot tell from the tool list, so say when not.
316
- (health.resumableWrites === false ? ", resumable writes off" : "") +
312
+ "prescribed seven-tool surface" +
317
313
  (drifted.length > 0
318
314
  ? `, catalog drift on ${drifted.length} connector(s).`
319
315
  : "."),
@@ -2,7 +2,7 @@ import type { ActivityModule } from "./module-contracts.js";
2
2
  import type { CatalogDriftCounts, Logger } from "./types.js";
3
3
  export type ActivityCallSource = "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
4
4
  export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled" | "paused" | "approved";
5
- /** How much one `resume_execution` approved: this call, or this tool for the run. */
5
+ /** Read-only history: how much one `resume_execution` approved (#672). */
6
6
  export type ActivityApproval = "call" | "tool";
7
7
  export type AgentFriction = "tool_not_found" | "schema_retry" | "destructive_reroute" | "auth_required" | "result_too_large";
8
8
  export { agentFrictionForCode } from "./activity-friction.js";
@@ -50,8 +50,9 @@ export interface ToolCallActivityEvent {
50
50
  */
51
51
  friction?: AgentFriction;
52
52
  /**
53
- * Set only on an `approved` event: the scope the approval covered. An enum,
54
- * like everything else here — the approved arguments are never recorded.
53
+ * Read-only history: set only on an `approved` event written before issue
54
+ * #672, the scope the approval covered. An enum, like everything else
55
+ * here — the approved arguments were never recorded.
55
56
  */
56
57
  approval?: ActivityApproval;
57
58
  serverName: string;
@@ -149,7 +150,7 @@ export interface ActivityRequestContext {
149
150
  defer?: (promise: Promise<unknown>) => void;
150
151
  logger: Logger;
151
152
  }
152
- export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "errorCode" | "friction" | "approval">;
153
+ export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "errorCode" | "friction">;
153
154
  /**
154
155
  * Best-effort by design: activity storage can never change a tool result.
155
156
  * Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
package/dist/activity.js CHANGED
@@ -49,18 +49,9 @@ export function recordToolActivity(context, input) {
49
49
  source: input.source,
50
50
  outcome: input.outcome,
51
51
  durationMs: Math.max(0, Math.trunc(input.durationMs)),
52
- // A pause and an approval reach nothing downstream, so they are the two
53
- // events that honestly carry zero attempts. Every other outcome keeps its
54
- // floor of one, as it always has.
55
- attempts: input.outcome === "paused" || input.outcome === "approved"
56
- ? 0
57
- : Math.max(1, Math.trunc(input.attempts)),
52
+ attempts: Math.max(1, Math.trunc(input.attempts)),
58
53
  ...(input.errorCode ? { errorCode: input.errorCode } : {}),
59
54
  ...(friction ? { friction } : {}),
60
- ...(input.outcome === "approved" &&
61
- (input.approval === "call" || input.approval === "tool")
62
- ? { approval: input.approval }
63
- : {}),
64
55
  serverName: context.serverInfo.name,
65
56
  serverVersion: context.serverInfo.version,
66
57
  ...(context.deploymentId
@@ -6,7 +6,7 @@
6
6
  //
7
7
  // Who made each version comes from the caller core attached to the context,
8
8
  // never from arguments.
9
- import { api } from "../connectors/api.js";
9
+ import { apiConnector as api } from "../connectors/api-connector.js";
10
10
  import { callerOf } from "../connector-caller.js";
11
11
  import { ConnectorCallError } from "../errors.js";
12
12
  import { resolveTheme } from "../branding.js";
@@ -66,3 +66,13 @@ export function jsonProblem(value, maxDepth) {
66
66
  }
67
67
  return undefined;
68
68
  }
69
+ const encoder = new TextEncoder();
70
+ /** A string's size in UTF-8 bytes: what every artifact limit counts. */
71
+ export function utf8Bytes(text) {
72
+ return encoder.encode(text).byteLength;
73
+ }
74
+ /** SHA-256 as lowercase hex: the content address of a stored body. */
75
+ export async function sha256Hex(text) {
76
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(text));
77
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
78
+ }
@@ -14,8 +14,8 @@
14
14
  // Nothing here throws for a caller's mistake. Operations answer
15
15
  // `{ ok: false, code, message }`, and the connector turns that into its typed
16
16
  // error; a throw means storage failed.
17
- import { sha256Hex, utf8Bytes } from "../run-journal.js";
18
17
  import { buildFrameDocument, FRAME_OVERHEAD_BYTES } from "./document.js";
18
+ import { sha256Hex, utf8Bytes } from "./json.js";
19
19
  import { markdownPage, MarkdownNestingError } from "./markdown.js";
20
20
  import { ARTIFACT_ID, checkDocument, checkDocumentTotals, checkView, finish, validDocumentName, } from "./validate.js";
21
21
  /** Physical head moves a write absorbs before giving up as busy. */
@@ -7,9 +7,8 @@
7
7
  // finding names a line where there is one, quotes the source only through the
8
8
  // shared echo bound, and says what to do instead.
9
9
  import { boundedEchoText } from "../errors.js";
10
- import { utf8Bytes } from "../run-journal.js";
11
10
  import { lineLocator, scanHtml } from "./html-scan.js";
12
- import { jsonProblem, scriptSafeJson } from "./json.js";
11
+ import { jsonProblem, scriptSafeJson, utf8Bytes } from "./json.js";
13
12
  import { FRAME_OVERHEAD_BYTES } from "./document.js";
14
13
  import { markdownPage, MarkdownNestingError } from "./markdown.js";
15
14
  import { DEFAULT_ARTIFACT_ALLOWLIST, DEFAULT_ARTIFACT_LIMITS, } from "./types.js";