@zackbart/connecta 0.25.0 → 0.26.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 (100) hide show
  1. package/AGENTS.md +6 -5
  2. package/CHANGELOG.md +387 -0
  3. package/README.md +32 -15
  4. package/SECURITY.md +6 -5
  5. package/bin/connecta.mjs +5 -1
  6. package/dist/activity.d.ts +10 -3
  7. package/dist/activity.js +10 -1
  8. package/dist/artifacts/connector.d.ts +55 -0
  9. package/dist/artifacts/connector.js +724 -0
  10. package/dist/artifacts/document.js +80 -0
  11. package/dist/artifacts/guide.js +140 -0
  12. package/dist/artifacts/html-scan.js +329 -0
  13. package/dist/artifacts/json.js +68 -0
  14. package/dist/artifacts/kv-store.d.ts +20 -0
  15. package/dist/artifacts/kv-store.js +140 -0
  16. package/dist/artifacts/markdown.js +478 -0
  17. package/dist/artifacts/module.d.ts +37 -0
  18. package/dist/artifacts/module.js +60 -0
  19. package/dist/artifacts/operations.d.ts +195 -0
  20. package/dist/artifacts/operations.js +944 -0
  21. package/dist/artifacts/refresh.d.ts +53 -0
  22. package/dist/artifacts/refresh.js +235 -0
  23. package/dist/artifacts/routes.js +217 -0
  24. package/dist/artifacts/types.d.ts +214 -0
  25. package/dist/artifacts/types.js +22 -0
  26. package/dist/artifacts/validate.d.ts +62 -0
  27. package/dist/artifacts/validate.js +653 -0
  28. package/dist/artifacts.d.ts +6 -0
  29. package/dist/artifacts.js +9 -0
  30. package/dist/auth/downstream-oauth.js +397 -48
  31. package/dist/auth/oauth-start-reset.js +20 -0
  32. package/dist/branding.d.ts +87 -0
  33. package/dist/branding.js +170 -1
  34. package/dist/catalog-service.js +39 -29
  35. package/dist/catalog.js +42 -8
  36. package/dist/connector-access.d.ts +18 -4
  37. package/dist/connector-access.js +65 -20
  38. package/dist/connector-caller.d.ts +8 -0
  39. package/dist/connector-caller.js +19 -0
  40. package/dist/connectors/remote-mcp.js +48 -9
  41. package/dist/errors.d.ts +16 -1
  42. package/dist/errors.js +28 -0
  43. package/dist/execute.js +431 -75
  44. package/dist/executor-admission.d.ts +11 -1
  45. package/dist/executor-admission.js +79 -14
  46. package/dist/executors/quickjs.js +4 -2
  47. package/dist/index.d.ts +62 -6
  48. package/dist/index.js +230 -6
  49. package/dist/invocation.js +56 -10
  50. package/dist/meta-tools.js +7 -2
  51. package/dist/module-contracts.d.ts +21 -1
  52. package/dist/oauth-callback-outcome.js +43 -18
  53. package/dist/operator-ui/generated.js +2 -2
  54. package/dist/operator-ui/model.d.ts +62 -5
  55. package/dist/operator-ui/view.js +372 -28
  56. package/dist/page-styles.js +7 -0
  57. package/dist/program-source.js +116 -0
  58. package/dist/registry.d.ts +42 -6
  59. package/dist/registry.js +164 -60
  60. package/dist/resumable.js +1291 -0
  61. package/dist/routes/activity.js +6 -69
  62. package/dist/routes/actor-labels.js +114 -0
  63. package/dist/routes/credentials.js +27 -5
  64. package/dist/routes/mcp.js +120 -59
  65. package/dist/routes/oauth-management.js +87 -20
  66. package/dist/routes/oauth.js +54 -87
  67. package/dist/routes/operator.js +1 -0
  68. package/dist/routes/shared.d.ts +23 -1
  69. package/dist/routes/shared.js +20 -2
  70. package/dist/routes/ui-data.js +2 -1
  71. package/dist/routes/ui.js +19 -4
  72. package/dist/run-journal.js +347 -0
  73. package/dist/runtime/shared-read.js +106 -0
  74. package/dist/server.js +30 -2
  75. package/dist/skills.js +30 -11
  76. package/dist/timeout.js +26 -0
  77. package/dist/tool-safety.d.ts +35 -0
  78. package/dist/tool-safety.js +31 -0
  79. package/dist/types.d.ts +23 -0
  80. package/dist/ui.d.ts +17 -5
  81. package/dist/ui.js +51 -68
  82. package/dist/version.d.ts +1 -1
  83. package/dist/version.js +1 -1
  84. package/documentation/architecture.md +214 -27
  85. package/documentation/auth.md +186 -2
  86. package/documentation/code-mode.md +236 -63
  87. package/documentation/meta-tools.md +55 -29
  88. package/ethos.md +12 -10
  89. package/examples/worker/AGENTS.md +6 -0
  90. package/examples/worker/README.md +174 -10
  91. package/examples/worker/src/d1-activity-row.ts +14 -2
  92. package/examples/worker/src/d1-activity.ts +4 -2
  93. package/examples/worker/src/index.ts +36 -1
  94. package/examples/worker/src/r2-artifact-blobs.ts +29 -0
  95. package/examples/worker/wrangler.jsonc +5 -1
  96. package/package.json +6 -2
  97. package/templates/node/AGENTS.md +9 -2
  98. package/templates/node/README.md +31 -3
  99. package/templates/node/package.json +1 -1
  100. package/templates/node/src/index.ts +27 -0
package/AGENTS.md CHANGED
@@ -4,12 +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 seven meta-tools, `execute_code` among them. Every
7
+ plain HTTP APIs) behind eight 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. Two `planned` decisions change
11
- that picture — resumable writes with an eighth tool, and an artifacts module —
12
- and until they ship, the code and the guides describe what exists.
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.
13
14
 
14
15
  - **[`ethos.md`](./ethos.md) is the constitution.** It states what connecta is
15
16
  and isn't, and its decisions table carries a verdict for every shape already
@@ -73,7 +74,7 @@ pins the generated deployment to the CLI package's exact version, restores the
73
74
  template `.gitignore` and the `CLAUDE.md` symlink, and refuses to merge into an
74
75
  existing path.
75
76
  `connecta doctor` verifies a running deployment's health, executor, and exact
76
- seven-tool surface. The template carries its own `Dockerfile` and
77
+ eight-tool surface. The template carries its own `Dockerfile` and
77
78
  `docker-compose.yml`, so the generated project is the container: setup changes
78
79
  must keep the root README, the template (source, container files, and README),
79
80
  and the `scripts/check-package.mjs` smoke — which builds and runs that
package/CHANGELOG.md CHANGED
@@ -2,6 +2,393 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.26.1 — 2026-09-26
8
+
9
+ This patch fixes slow OAuth restarts, unbounded downstream authorization waits,
10
+ and operator pages that signed people out after a temporary load failure.
11
+ Connect now opens consent in one click, and browser-facing pages share the
12
+ configured theme. Agent guidance preserves one-time write results and keeps
13
+ an authorized batch in one resumable program. Approval defaults and the eight
14
+ MCP tools are unchanged. No configuration or storage migration is required;
15
+ deployments without the operator UI can ignore the page changes. OAuth starts
16
+ abort downstream work after 30 seconds, but uncancellable storage resets and
17
+ cleanup must finish before the response. Restart reuses a matching client
18
+ registration; a revoked client may still fail at consent or callback and need
19
+ another restart. The Node template now pins 0.26.1.
20
+
21
+ ### Added
22
+
23
+ - **Continue or restart an operator OAuth start.** `POST /ui/oauth/<id>`
24
+ takes `?mode=continue` or `?mode=restart`. `restart`, still the default
25
+ when `mode` is absent, creates a new epoch and clears the grant and
26
+ discovery while retaining a matching issuer-bound client registration. `continue` hands back the pending
27
+ authorization URL when it was written in the last ten minutes. Otherwise it
28
+ starts a flow in the current epoch, reusing a stored registration when one
29
+ exists. A first connection still registers dynamically. A disconnected
30
+ connector resets first. A response with a URL now carries `reused`. A continue that reused a
31
+ URL or found the connection healthy leaves the cached catalog alone. Any
32
+ other `mode` is a 400. After a `publicUrl` change, continue keeps a client
33
+ registered for the old callback, which the authorization server refuses;
34
+ restart recovers. `ConnectorStatus` gains the optional
35
+ `authorizationReused`, set by `remoteMcp()`'s `startAuth`.
36
+
37
+ ### Changed
38
+
39
+ - **Pending authorization URLs expire for reuse.** A non-forced `startAuth`,
40
+ including `authorize_connector` without `force`, reissues a pending URL only
41
+ when it is under ten minutes old. The pending URL's envelope now records its
42
+ write time, which older readers ignore. A URL written before this release,
43
+ or under a pre-epoch generation, starts a fresh flow instead.
44
+ - **OAuth cleanup deletes run concurrently.** A restart's epoch cleanup,
45
+ `clearPending`, and `invalidateCredentials("all")` keep up to six deletes
46
+ in flight, the Workers limit on simultaneous connections. Every delete is
47
+ still attempted, and a falsy rejection now counts as a failure. A retired
48
+ epoch's manifest is still removed only after all of its values. Catalog
49
+ invalidation deletes the root and its chunks together under the registry's
50
+ chunk I/O bound, so a failed root delete no longer skips the chunks.
51
+
52
+ - Every page connecta renders for a person now shares one token layer and one
53
+ layout: the operator shell, the OAuth callback, a browser's 404, and the
54
+ artifact frame follow `branding.theme` and light or dark. The OAuth callback
55
+ names the outcome in its heading with a status mark, names the connector by
56
+ title only after the state check, uses `branding.productName` in its copy,
57
+ and folds the agent fix prompt under "Details for the operator". Refusals
58
+ stay byte-identical across connectors and paths.
59
+ - A request whose `Accept` names `text/html` gets a themed 404 page; every
60
+ other client keeps the plain `Not Found` body.
61
+ - Markdown artifacts take the deployment's resolved scheme and tokens instead
62
+ of the OS palette, and drop an opening `# Heading` that only repeats the
63
+ title the viewer already shows. The frame waits on the viewer's surface with
64
+ a loading line, and says so if the page never arrives.
65
+ - Without the operator UI, callback and 404 pages no longer link the default
66
+ `/favicon.svg`, which only the UI serves.
67
+ - The operator UI starts OAuth in one click. Connect opens the provider's
68
+ page in a new tab straight away and asks the server to continue a pending
69
+ authorization rather than restart it; only Reconnect on a healthy
70
+ connection restarts, behind an in-page confirm. A tab returning to the page
71
+ quietly re-reads the status of connectors waiting on authorization.
72
+ - Operator UI notices appear in the card that caused them, in fixed
73
+ sentences rather than the route's error text; problems fixed by authorizing
74
+ or adding a credential are warnings rather than errors, with one primary
75
+ action per row. Activity and artifact states, the confirm dialogs, the
76
+ mobile masthead, and the vocabulary shown to people (outcomes, actors,
77
+ timestamps) now read the same across pages and use `branding.productName`.
78
+
79
+ ### Fixed
80
+
81
+ - Operator OAuth starts now abort downstream discovery and registration after
82
+ 30 seconds or browser cancellation, with a fixed timeout response. Every
83
+ reset already started by the request, including issuer-mismatch recovery,
84
+ drains before catalog invalidation and response so a delayed generation
85
+ write cannot replace a later flow. These uncancellable storage waits can
86
+ exceed the deadline. Disconnect still finishes after browser cancellation.
87
+ - Forced OAuth restarts reuse registrations bound to the issuer, redirect URI,
88
+ client metadata, connector settings, and owner partition. Credentials are
89
+ re-sealed for the replacement epoch, and fresh discovery detects issuer
90
+ changes. Disconnect and issuer-mismatch recovery discard the registration.
91
+ The SDK cannot detect a revoked client while constructing a consent URL;
92
+ a callback refusal clears it so the next restart can register again.
93
+ - Agent sampling advice now applies to reads. For a one-time write, agents
94
+ reduce the full result in the program or page a direct-call result through
95
+ `get_result`, without repeating the write to recover discarded output.
96
+ - Authorized batches stay in one resumable program. Guidance explains how
97
+ tool-scoped approval covers repeated calls to one address while other
98
+ approval-required writes retain their own approvals. The call-scoped
99
+ default and host enforcement are unchanged.
100
+ - **A restart's cleanup no longer grows with every earlier restart.** Each
101
+ OAuth restart re-deleted every epoch the connector had ever retired, one key
102
+ at a time, and restart 1,001 failed forever with a full cleanup backlog. A
103
+ restart now deletes the epoch it retires, retries any of the eight most
104
+ recent epochs whose cleanup failed (their manifest outlives their values),
105
+ and sweeps at most 16 epochs retired more than 24 hours ago, dropping each
106
+ from the lineage only when all of its keys are gone. A Disconnect that
107
+ reported a failed cleanup still deletes the old grant when retried. The
108
+ cleanup work stays bounded independently of earlier resets. Residue a late
109
+ writer leaves in a younger epoch stays unreadable behind the fence until the
110
+ sweep reaches it. A late writer whose cleanup fails in an epoch already
111
+ listed now restarts that epoch's grace. The accepted assumption is that no
112
+ request holds a retired epoch for a day. The lineage records retirement
113
+ times in a sibling record older releases ignore, and its cap rises to 5,000
114
+ epochs. A connector stuck at the old 1,000 wall restarts the day it
115
+ upgrades. Rolling back is clean unless a lineage has grown past 1,000. See
116
+ [auth](./documentation/auth.md#starting-restarting-and-retiring-an-oauth-epoch).
117
+ - A failed or unreachable `/ui/data` no longer signs the operator out. Only a
118
+ 401 or 403 on that read returns to the token gate; anything else keeps the
119
+ page and offers Retry. A connector whose details fail to load says whether
120
+ the session, the browser's connection, or the downstream service is at
121
+ fault, and only the last offers the fix prompt.
122
+
123
+ ## 0.26.0 — 2026-09-25
124
+
125
+ Programs can now write with host approval. When a program reaches a tool that
126
+ is not explicitly read-only, it pauses before the call and returns the exact
127
+ address, arguments, and a token. The new eighth MCP tool,
128
+ `resume_execution`, repeats that call to show the host the real write and
129
+ replays the program from its journal without resending an uncertain outcome.
130
+ `tools/list` now has eight tools on every deployment, so client allowlists and
131
+ `connecta doctor` installations pinned to 0.25 need updating. Resumable
132
+ writes turn on by default when storage supports `compareAndSet` (memory, file,
133
+ or the Worker example's D1 adapter); a program write that previously failed
134
+ now pauses. Cloudflare KV cannot make that claim atomically, so resumable
135
+ writes stay off there. Set `execute.resumableWrites: false` to silence its
136
+ startup warning. Program clocks and randomness are pinned for replay.
137
+
138
+ Artifacts arrive as an optional module with versioned JSON documents, a
139
+ sandboxed viewer and library, and scheduled refresh of one data document from
140
+ a read-only program. A deployment supplies a Node timer or Worker cron;
141
+ connecta starts no background job. Stored pages use the operator sign-in,
142
+ and a dedicated artifact origin may require signing in again because browser
143
+ storage belongs to one origin. Page scripts run with an opaque origin and no
144
+ network by default; only explicitly allowlisted script, style, and font
145
+ origins may load. Artifact version and configuration writes are approval-exempt inside
146
+ programs by default; deployment config can restore the approval prompt.
147
+ `run_refresh` requires host approval and is excluded from that exemption.
148
+ Deployments that omit the module need no
149
+ artifact storage, R2 bucket, cron, browser binding, or second domain.
150
+
151
+ Before upgrading a Worker deployment that uses D1 activity, add the
152
+ `approval` column shown below. Every admitted `/mcp` request, on Node and
153
+ Workers, has a five-minute total lifetime by default; set
154
+ `admission.requests.maxDurationMs` above any longer authorized request. The
155
+ Worker example also enables `enable_request_signal` for live disconnects.
156
+ To add artifacts to that example, use CAS-capable D1
157
+ storage, optionally R2 for bodies, and explicitly configure any CDN origins.
158
+ The Node template now pins 0.26.0 and shows the optional artifact timer.
159
+ `ConnectorCallErrorCode` gains `conflict` for stale artifact edits, while an
160
+ identity can opt into reviewed exact tool grants that remain visible only while
161
+ the loaded catalog classifies them read-only. The operator UI no longer
162
+ returns downstream OAuth or credential-test text in notices; callers that
163
+ read those route messages must use the fixed outcome and consult server logs
164
+ for diagnostics.
165
+
166
+ ### Added
167
+
168
+ - **Guarded read-only identity grants (#601).** A deployment may put
169
+ `{ tool: "connector.tool", requireReadOnly: true }` in
170
+ `identity.connectorAccess`. The reviewed exact address stays visible only
171
+ while its loaded catalog explicitly says read-only without a contradictory
172
+ destructive hint. A reclassified tool disappears from discovery and every
173
+ call path, including `call_destructive_tool` and approval-exempt programs;
174
+ new names are never added automatically. Existing string grants keep their
175
+ additive meaning, and a pool can still only narrow the identity's view.
176
+ A stale catalog or a dishonest downstream annotation remains a trust limit.
177
+
178
+ - **Resumable writes and `resume_execution` (#565).** A program's call to a
179
+ tool that is not explicitly read-only pauses the run before anything is
180
+ sent and returns `{ paused: { address, args, token, expiresAt, nextAction,
181
+ hint } }`. `resume_execution` — always listed, annotated destructive — must
182
+ repeat the address and args exactly (`approval_mismatch` otherwise, before
183
+ anything is claimed); it claims the run by compare-and-set and replays the
184
+ program from a journal in the caller's result storage, answering every
185
+ earlier host call from its record — no catalog, permit, connector, or
186
+ activity — and sending the approved write once. `approval: "tool"` approves
187
+ the rest of the run's calls to that tool. Each live write is marked on the
188
+ run's header before it is sent, so racing or crashed resumes never send it
189
+ twice. A write sent but never answered — or answered with anything short
190
+ of a refusal or the downstream tool's own error — stops the run as
191
+ `write_outcome_unknown`, even beside a concurrent pause, and is never sent
192
+ again; a program that stops matching its journal fails
193
+ `execution_diverged`; a resumed play that sent writes and then ended without
194
+ pausing (a sandbox failure, a lapsed claim) fails `execution_interrupted`
195
+ rather than replaying reads it never journaled. Every error from a resumed
196
+ play carries `writes` counts, and once a write landed or may have, its
197
+ advice is to check those before re-running rather than a plain re-run; a
198
+ run that sent writes keeps those counts past its deadline. Every paused-run
199
+ storage call has a deadline (the host-call deadline, at least 5 s), so
200
+ storage that stops answering fails the run rather than holding it. A paused
201
+ run survives a restart and expires `execute.pausedRunTtlSeconds` (default 1,800) after its
202
+ first pause; `execute.maxWrites` (default 10) caps a run's writes on top of
203
+ the host-call budget. `execute.resumableWrites` defaults to whether the
204
+ storage has `compareAndSet`; `true` without it refuses to construct.
205
+ `/health` reports `resumableWrites`, and `connecta doctor` expects the eight
206
+ tools and says when resumable writes are off. See code mode "Pausing and
207
+ resuming" (`W1`–`W11`, `X12`).
208
+ - **Config approval exemptions (#566).** `execute.approval` maps connector ids
209
+ and `connector.tool` addresses to `"never"` or `"ask"`: a program calls an
210
+ exempt write without pausing, while it still spends the write budget, is
211
+ journaled, and records activity. The address wins over the connector entry,
212
+ which wins over a connector's own `approval: "never"` default — reserved for
213
+ connectors connecta ships, such as the artifacts connector — so
214
+ `"ask"` switches such a default off. It works with resumable writes off
215
+ too, where an exempt write the program leaves unawaited still finishes
216
+ before the run ends, and one with an unknown outcome is the result. Exempt is never read-only: discovery keeps the tool approval-required
217
+ and marks its row `approval: "exempt"`, `call_tool` still refuses it, and no
218
+ downstream annotation can grant it. An unknown connector id, or an `api()`
219
+ address its tools do not include, refuses to construct. The operator UI's
220
+ per-tool badge gains its third state, "exempt from approval".
221
+ - **Activity for pauses and approvals.** `ActivityOutcome` gains `paused` and
222
+ `approved`, both with zero attempts; `ActivityCallSource` gains
223
+ `resume_execution`; `ToolCallActivityEvent` gains an optional `approval`
224
+ (`"call"` or `"tool"`), set only on an approval. All three are enums — the
225
+ approved arguments are never recorded. The Worker example's D1 activity
226
+ store writes an `approval` column; add it with
227
+ `ALTER TABLE tool_call_activity ADD COLUMN approval TEXT;` before deploying
228
+ the updated adapter. The operator activity page labels both outcomes and
229
+ says what an approval covered.
230
+ - **Artifact storage and validation, at `@zackbart/connecta/artifacts`
231
+ (#562).** `kvArtifactStore(storage, { blobs?, prefix? })` keeps artifacts
232
+ in any `KVStorage` with `compareAndSet` and `list` — one head record per
233
+ artifact, swapped by compare-and-set, with every earlier version immutable
234
+ beside it and bodies content-addressed — and refuses Cloudflare Workers KV
235
+ at construction, because a write that cannot compare-and-set its head can
236
+ lose a teammate's edit. `validateArtifact({ kind, source, documents? })` is
237
+ the static check every save runs: one `id="artifact-root"`, no frames,
238
+ plugins, forms, or `<base>`, external scripts only from explicitly
239
+ allowlisted origins, and no relative or unapproved network resource URLs,
240
+ with a line number and a fix in every message. The Worker example gains
241
+ `r2-artifact-blobs.ts` for keeping bodies
242
+ in R2 beside a D1 store. The subpath adds no dependency and no Effect.
243
+ - **The built-in artifacts connector (#562, #564).** `createConnecta({ artifacts:
244
+ artifacts({ store }) })` appends an `artifacts` connector with five reads —
245
+ `list_artifacts`, `get_artifact`, `get_document`, `validate_artifact`, and
246
+ `get_refresh` — and nine writes: `create_artifact`, `update_artifact`, `patch_artifact` (exact
247
+ find/replace, every edit matching once or nothing changes),
248
+ `set_documents` (plural and atomic), `rollback_artifact`,
249
+ `archive_artifact`, `restore_artifact`, `set_refresh`, and `run_refresh`.
250
+ Version-changing writes name their expected base, and every write records
251
+ its actor from the request's authorization. The connector skips approval, except for `run_refresh`,
252
+ inside `execute_code` through its own `approval:
253
+ "never"` — `execute.approval: { artifacts: "ask" }` turns that off — while
254
+ still spending the write budget and recording activity; `call_tool` still
255
+ refuses it. The connector serves a publishing guide as
256
+ `connector:artifacts`, required before the first write. An optional
257
+ `renderCheck` hook receives the exact frame document and CSP a viewer will
258
+ load and can refuse a write; without one, static validation is the floor.
259
+ The module needs `publicUrl`, and a configured connector named `artifacts`
260
+ refuses to construct.
261
+ - **Artifact library and sandboxed viewer (#563).** `/artifacts` lists pages;
262
+ `/artifacts/<id>` opens one after the same inbound sign-in as the operator
263
+ UI. A viewer snapshot pins exact view and document versions. Page HTML runs
264
+ in an opaque-origin frame with no fetch or worker access. A deployment may
265
+ allow exact CDN origins for scripts, styles, and fonts; none are allowed by
266
+ default. With `artifactOrigin`, the main host redirects artifact paths and
267
+ the dedicated host serves no MCP or operator routes. A browser bearer stored
268
+ on the main origin must be entered again on the artifact origin; the redirect
269
+ carries no credential.
270
+ - **Scheduled artifact refresh (#564).** `set_refresh` attaches a versioned
271
+ program for one data document on a manual, daily, or weekly schedule;
272
+ `run_refresh` triggers it now, and `get_refresh` reports freshness and a
273
+ bounded run history. The module's `runDue()` is called by the deployment's
274
+ Node timer or Worker cron, never by a core background loop. Runs use only
275
+ shared connectors' explicitly read-only tools within the setter's current
276
+ identity and pool grants, and stop if publishing permission is revoked.
277
+ Connector approval exemptions cannot permit a scheduled write. Runs claim
278
+ the artifact by compare-and-set and validate returned JSON, including any
279
+ configured render check, before publishing a new document version. A failure keeps the last good value and marks the page
280
+ stale without rendering raw failure text in the library or viewer. Each tick
281
+ starts at most ten due pages; the Node template and Worker example show the
282
+ optional wiring.
283
+ - **`conflict` (#562).** A new `ConnectorCallErrorCode` for a write whose base
284
+ someone else already moved past, never retryable as-is, with an optional
285
+ bounded `current` map (at most 20 whole-number entries) on
286
+ `ConnectorCallError` and `CallErrorDetails` saying where things stand. A
287
+ program reads it as `err.details.current`; a top-level call returns it in
288
+ the structured error. A write refused with `conflict` inside a program is a
289
+ known failure (`W9`), not an unknown outcome.
290
+
291
+ ### Changed
292
+
293
+ - **`execute_code` asks for a zero-argument program (#602).** Its guidance now
294
+ shows `async () => { ... }`, with `connecta` supplied as the sandbox global.
295
+ Passing `connecta` as an arrow parameter shadows that global and fails; the
296
+ accepted source forms and execution rules have not changed.
297
+ - **Worker resource setup has an optional Alchemy guide (#567).** It covers
298
+ account resources and adoption while keeping the Worker script, domains,
299
+ Browser Rendering, and cron deployment with Wrangler until Alchemy's
300
+ script-upload path is shown to preserve `ctx.access`. Alchemy is not a
301
+ connecta dependency or a second deployment template.
302
+ - **`execute_code` source intake (#576).** Programs over 64 KiB of UTF-8
303
+ source fail before executor admission. The host recovers one fenced async
304
+ arrow even when prose surrounds it, a default-exported arrow, or a named
305
+ async function declaration, so Node and Workers receive the same program.
306
+ - **Eight tools, and instructions that follow the mode.** `tools/list` adds
307
+ `resume_execution` everywhere. With resumable writes on, the MCP
308
+ instructions say programs may write and pause, the `execute_code`
309
+ description advertises the write budget and drops `safety: "readOnly"` from
310
+ its search example (a program looking for a write would not find it), and
311
+ the `usage` skill gains the pause, resume, and unknown-outcome guidance;
312
+ without them, the instructions and description read as they did in 0.25.
313
+ - **The operator UI's "needs approval" badge reads "asks for approval"**, and
314
+ the tool legend says a program pauses for `resume_execution` while a direct
315
+ call crosses `call_destructive_tool`.
316
+ - **A program's clock and randomness are pinned (code mode `P6`).**
317
+ `Date.now()`, `new Date()`, and `Date()` inside `execute_code` return the
318
+ instant the run started and do not advance, and `Math.random()` is an sfc32
319
+ stream seeded per run from the host's CSPRNG; on a Dynamic Worker,
320
+ `crypto.getRandomValues`, `crypto.randomUUID`, and `performance.now()` follow
321
+ the same pin. The replacements are locked like `Error`. Resumable writes
322
+ (#565) replay a paused program from the top, and a program that branched on
323
+ a moving clock or an unseeded draw could not be replayed; pinning every run
324
+ means none behaves differently for having paused. A program that timed its
325
+ own work now measures zero — `diagnostics: true` measures from the host.
326
+
327
+ ### Fixed
328
+
329
+ - **Request admission recovers after missing Worker cleanup (#595).** An
330
+ admitted `/mcp` request now has a configurable total lifetime, five minutes
331
+ by default, covering auth, tools, and response delivery. Its own timer aborts
332
+ live work, including connector calls. If workerd ends it without running
333
+ JavaScript cleanup, the next request or a queued request's timer reclaims
334
+ only the expired permit. An orphaned queued request cannot strand its next
335
+ permit indefinitely, and late cleanup cannot release a successor's permit.
336
+ The Worker example enables `enable_request_signal` for prompt live disconnect
337
+ cleanup.
338
+
339
+ - **Legacy MCP handshake close (#572).** A client that leaves while the older
340
+ protocol handshake is still initializing no longer leaves the SDK's
341
+ `notifications/initialized` promise rejected without a handler. Successful
342
+ handshakes still send the notification.
343
+
344
+ - **Operator notices no longer render downstream error text**
345
+ ([#568](https://github.com/zackbart/connecta/issues/568)). The OAuth
346
+ connect/disconnect notice and the credential Test result showed whatever the
347
+ server sent back, and that text can come from a token endpoint's error body or
348
+ a provider's refusal, either of which can quote the secret it was sent. Each
349
+ notice now says a fixed sentence chosen by outcome, with the fix prompt for
350
+ that outcome, and the page never renders a route's words there even if an
351
+ older server sends them. The routes log the downstream's text instead — a
352
+ failed OAuth action or credential test at `warn`, a passing test's message at
353
+ `info` — and never record it in activity. A `CredentialTestResult.message` is
354
+ now log-only, and a Test refused because nothing usable is stored carries the
355
+ same `credential_required` or `credential_mismatch` problem the Connections
356
+ page shows.
357
+
358
+ - **Compact schemas group an array's union element.** The `compact` format,
359
+ the default for `search_tools` and `connecta.search`, rendered an array of a
360
+ union as `{ op: "replace" } | { op: "append" }[]`, which reads as one
361
+ operation or an array of the other; Linear `save_issue`'s `patch` was one
362
+ such array. A union, intersection, enum, or type list used as an array
363
+ element, a tuple's rest included, now renders parenthesized, as
364
+ `({ op: "replace" } | { op: "append" })[]`, matching the `typescript`
365
+ format. The renderer records which shapes it rendered at operator level
366
+ rather than rescanning its text, so a pipe in a literal, a constraint, or
367
+ property prose adds no parentheses, and every other shape renders byte for
368
+ byte as before (#569).
369
+
370
+ - **A catalog refresh whose connector ignores abort held every later reader.**
371
+ A refresh flight lived until its connector settled, so a `listTools` that
372
+ ignored its signal kept the flight open until the catalog was invalidated,
373
+ and every reader that joined it timed out in turn while the connector stayed
374
+ unlisted. A flight is now bounded by its owner's deadline, or by the default
375
+ 30-second probe timeout for an owner with none. Past it, readers still
376
+ waiting and readers arriving later make a fresh attempt, and a late result
377
+ from the abandoned flight is neither cached nor persisted, so it cannot
378
+ overwrite the fresh one. Abandonment logs a warning
379
+ ([#570](https://github.com/zackbart/connecta/issues/570)).
380
+ - **A joined catalog read ended on its starter's deadline.** Readers of one
381
+ catalog in a request share one read, and that read ran under whichever
382
+ reader started it. A short-deadline `connecta.call` that started it failed a
383
+ concurrent `connecta.search` with the call's timeout, and a search cancelled
384
+ by its client cancelled a read others were still waiting on. The shared read
385
+ now carries its own signal and the probe timeout. Each reader waits under its
386
+ own deadline and cancellation, one that leaves fails alone, and the read is
387
+ cancelled only when every reader has gone. It is still one downstream read
388
+ per connector per request. The registry's cross-request refresh no longer
389
+ hands its owner's cancellation to joiners, which make a fresh attempt instead
390
+ ([#571](https://github.com/zackbart/connecta/issues/571)).
391
+
5
392
  ## 0.25.0 — 2026-09-23
6
393
 
7
394
  The Effect core is the release. Admission, deadlines, invocation,
package/README.md CHANGED
@@ -10,16 +10,19 @@ 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 seven tools. Always seven,
13
+ 1. The agent talks to one endpoint, yours, and sees eight tools. Always eight,
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 agent wants to change something — create, update, delete — it
21
- cannot do that from a program. It makes one explicit call, and your MCP
22
- client can put that call in front of you first.
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`.
23
26
 
24
27
  Credentials never leave the server. The program never sees them, and neither
25
28
  does the agent.
@@ -28,9 +31,9 @@ does the agent.
28
31
  flowchart TB
29
32
  Client["Your MCP client<br/>Claude, Cursor, …"]
30
33
 
31
- subgraph Connecta["Connecta — one endpoint, seven tools, your credentials"]
32
- Sandbox["execute_code<br/>the agent's program runs here<br/>read-only tools only"]
33
- Explicit["call_destructive_tool<br/>one visible call per write<br/>your client can ask you first"]
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"]
34
37
  end
35
38
 
36
39
  Integrations["The integrations you chose<br/>Linear · Stripe · Notion · Vercel · your HTTP API · any MCP server"]
@@ -38,6 +41,7 @@ flowchart TB
38
41
  Client -->|"one connection"| Sandbox
39
42
  Client --> Explicit
40
43
  Sandbox -->|"reads"| Integrations
44
+ Sandbox -.->|"paused write"| Explicit
41
45
  Explicit -->|"writes"| Integrations
42
46
  ```
43
47
 
@@ -73,22 +77,35 @@ Fifty issues in, one small object out. Your context window notices.
73
77
  retain field names and broad types in bounded runtime memory, never scalar
74
78
  values, so later programs can project a remote MCP result its provider never
75
79
  documented.
76
- - **Keep writes deliberate.** Only tools marked read-only run in a program.
77
- Everything else is a separate, visible call your client can gate.
78
- - **Run it where you like.** Node, a Docker container, or a Cloudflare Worker,
79
- from the same small deployment file.
80
+ - **Keep large results usable.** Oversized direct calls return a bounded
81
+ preview with a handle for paging through the rest. For read-only work, the
82
+ notice also points the agent to reduce or search the result inside a
83
+ program. Discovery can show compact schemas, exact JSON Schema, or a
84
+ 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.
92
+ - **Run it on Node or Cloudflare Workers.** The core is shared; each deployment
93
+ supplies its platform's executor and storage. The Node template also runs
94
+ unchanged in Docker.
80
95
 
81
96
  Deployments explicitly compose optional features: `operatorUi()` from
82
97
  `@zackbart/connecta/ui`, `encryptedCredentialVault()` from `/credentials`,
83
- `activityHistory()` from `/activity`, and inbound authentication adapters from
84
- `/auth/*`. Omit a module and its implementation does no runtime work. Core
98
+ `activityHistory()` from `/activity`, `artifacts()` from `/artifacts`, and
99
+ inbound authentication adapters from `/auth/*`. Omit a module and its implementation does no runtime work. Core
85
100
  keeps connector discovery, execution, invocation, and enforcement together.
86
101
 
87
102
  The optional UI shows each person's connections and effective permissions.
88
103
  Authentication controls live inside each connection, with optional activity
89
104
  history. The configured connection list loads before downstream checks finish;
90
105
  a slow provider does not hold up the page. Connector selection and access rules
91
- remain in deployment code.
106
+ remain in deployment code. It also explains which tools can run in programs or
107
+ need approval, offers a fixed repair prompt for classified failures, and shows
108
+ client setup commands for the endpoints the signed-in person can use.
92
109
 
93
110
  One deployment may serve several authenticated people inside the same tenant.
94
111
  Cloudflare Access supplies Worker identity; Node can use Clerk or the optional
@@ -105,7 +122,7 @@ and why.
105
122
 
106
123
  Setup is written for an agent. Point yours at [`AGENTS.md`](./AGENTS.md) and
107
124
  ask it to set up a Connecta deployment; the
108
- [documentation](./documentation/) covers the architecture, the seven tools,
125
+ [documentation](./documentation/) covers the architecture, the eight tools,
109
126
  code mode, and inbound auth if you want to go deeper. When upgrading an
110
127
  existing deployment, each [changelog](./CHANGELOG.md) release opens with what
111
128
  breaks and what a deployment can ignore.
package/SECURITY.md CHANGED
@@ -16,8 +16,9 @@ CI blocks moderate, high, and critical production dependency advisories. Lower
16
16
  findings are reviewed for reachability and recorded when an upstream package has
17
17
  not yet released a compatible fix.
18
18
 
19
- The MCP TypeScript SDK 2.x packages are exact-pinned. The split client and
20
- server packages no longer install Hono or `@hono/node-server`, so the temporary
21
- root-project override used with SDK 1.x is gone. A dependency bump must pass
22
- both `npm run release:check` and the Node/Workers runtime suites before the pin
23
- moves.
19
+ The MCP TypeScript SDK 2.x packages and Effect are exact-pinned. Effect is the
20
+ runtime core, and its unstable modules can break in a minor release, so its pin
21
+ moves in a dedicated upgrade. The split client and server packages no longer
22
+ install Hono or `@hono/node-server`, so the temporary root-project override
23
+ used with SDK 1.x is gone. A dependency bump must pass `npm run release:check`
24
+ and the Node/Workers runtime suites before a pin moves.
package/bin/connecta.mjs CHANGED
@@ -249,6 +249,7 @@ async function doctor() {
249
249
  "call_tool",
250
250
  "execute_code",
251
251
  "get_result",
252
+ "resume_execution",
252
253
  "search_tools",
253
254
  "skills",
254
255
  ];
@@ -301,7 +302,10 @@ async function doctor() {
301
302
  console.log(
302
303
  `Connecta doctor passed: ${health.connectors} connector(s), ` +
303
304
  `${executorName ? `${executorName} executed` : "code executed"}, ` +
304
- "prescribed seven-tool surface" +
305
+ "prescribed eight-tool surface" +
306
+ // resume_execution is listed either way; whether programs can write is
307
+ // what an operator cannot tell from the tool list, so say when not.
308
+ (health.resumableWrites === false ? ", resumable writes off" : "") +
305
309
  (drifted.length > 0
306
310
  ? `, catalog drift on ${drifted.length} connector(s).`
307
311
  : "."),
@@ -1,7 +1,9 @@
1
1
  import type { ActivityModule } from "./module-contracts.js";
2
2
  import type { CatalogDriftCounts, Logger } from "./types.js";
3
- export type ActivityCallSource = "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code";
4
- export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled";
3
+ export type ActivityCallSource = "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
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. */
6
+ export type ActivityApproval = "call" | "tool";
5
7
  export type AgentFriction = "tool_not_found" | "schema_retry" | "destructive_reroute" | "auth_required" | "result_too_large";
6
8
  export { agentFrictionForCode } from "./activity-friction.js";
7
9
  /**
@@ -47,6 +49,11 @@ export interface ToolCallActivityEvent {
47
49
  * agent while remaining an `outcome: "success"` call with no error code.
48
50
  */
49
51
  friction?: AgentFriction;
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.
55
+ */
56
+ approval?: ActivityApproval;
50
57
  serverName: string;
51
58
  serverVersion: string;
52
59
  deploymentId?: string;
@@ -142,7 +149,7 @@ export interface ActivityRequestContext {
142
149
  defer?: (promise: Promise<unknown>) => void;
143
150
  logger: Logger;
144
151
  }
145
- export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "errorCode" | "friction">;
152
+ export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "errorCode" | "friction" | "approval">;
146
153
  /**
147
154
  * Best-effort by design: activity storage can never change a tool result.
148
155
  * Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
package/dist/activity.js CHANGED
@@ -49,9 +49,18 @@ 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
- attempts: Math.max(1, Math.trunc(input.attempts)),
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)),
53
58
  ...(input.errorCode ? { errorCode: input.errorCode } : {}),
54
59
  ...(friction ? { friction } : {}),
60
+ ...(input.outcome === "approved" &&
61
+ (input.approval === "call" || input.approval === "tool")
62
+ ? { approval: input.approval }
63
+ : {}),
55
64
  serverName: context.serverInfo.name,
56
65
  serverVersion: context.serverInfo.version,
57
66
  ...(context.deploymentId