@zackbart/connecta 0.25.0 → 0.26.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.
- package/AGENTS.md +6 -5
- package/CHANGELOG.md +271 -0
- package/README.md +32 -15
- package/SECURITY.md +6 -5
- package/bin/connecta.mjs +5 -1
- package/dist/activity.d.ts +10 -3
- package/dist/activity.js +10 -1
- package/dist/artifacts/connector.d.ts +55 -0
- package/dist/artifacts/connector.js +724 -0
- package/dist/artifacts/document.js +76 -0
- package/dist/artifacts/guide.js +140 -0
- package/dist/artifacts/html-scan.js +329 -0
- package/dist/artifacts/json.js +68 -0
- package/dist/artifacts/kv-store.d.ts +20 -0
- package/dist/artifacts/kv-store.js +140 -0
- package/dist/artifacts/markdown.js +449 -0
- package/dist/artifacts/module.d.ts +37 -0
- package/dist/artifacts/module.js +60 -0
- package/dist/artifacts/operations.d.ts +195 -0
- package/dist/artifacts/operations.js +944 -0
- package/dist/artifacts/refresh.d.ts +53 -0
- package/dist/artifacts/refresh.js +235 -0
- package/dist/artifacts/routes.js +194 -0
- package/dist/artifacts/types.d.ts +214 -0
- package/dist/artifacts/types.js +22 -0
- package/dist/artifacts/validate.d.ts +62 -0
- package/dist/artifacts/validate.js +652 -0
- package/dist/artifacts.d.ts +6 -0
- package/dist/artifacts.js +9 -0
- package/dist/catalog-service.js +39 -29
- package/dist/catalog.js +42 -8
- package/dist/connector-access.d.ts +18 -4
- package/dist/connector-access.js +65 -20
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +19 -0
- package/dist/connectors/remote-mcp.js +16 -2
- package/dist/errors.d.ts +16 -1
- package/dist/errors.js +28 -0
- package/dist/execute.js +427 -70
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +79 -14
- package/dist/executors/quickjs.js +4 -2
- package/dist/index.d.ts +62 -6
- package/dist/index.js +230 -6
- package/dist/invocation.js +56 -10
- package/dist/meta-tools.js +7 -2
- package/dist/module-contracts.d.ts +21 -1
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +62 -5
- package/dist/operator-ui/view.js +94 -5
- package/dist/program-source.js +116 -0
- package/dist/registry.d.ts +42 -6
- package/dist/registry.js +148 -47
- package/dist/resumable.js +1291 -0
- package/dist/routes/activity.js +6 -69
- package/dist/routes/actor-labels.js +114 -0
- package/dist/routes/credentials.js +27 -5
- package/dist/routes/mcp.js +116 -59
- package/dist/routes/oauth-management.js +22 -9
- package/dist/routes/operator.js +1 -0
- package/dist/routes/shared.d.ts +23 -1
- package/dist/routes/shared.js +20 -2
- package/dist/routes/ui-data.js +2 -1
- package/dist/routes/ui.js +13 -4
- package/dist/run-journal.js +347 -0
- package/dist/runtime/shared-read.js +106 -0
- package/dist/server.js +26 -0
- package/dist/skills.js +27 -8
- package/dist/timeout.js +26 -0
- package/dist/tool-safety.d.ts +35 -0
- package/dist/tool-safety.js +31 -0
- package/dist/types.d.ts +18 -0
- package/dist/ui.d.ts +12 -5
- package/dist/ui.js +32 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +196 -19
- package/documentation/auth.md +83 -2
- package/documentation/code-mode.md +221 -48
- package/documentation/meta-tools.md +44 -25
- package/ethos.md +12 -10
- package/examples/worker/AGENTS.md +6 -0
- package/examples/worker/README.md +174 -10
- package/examples/worker/src/d1-activity-row.ts +14 -2
- package/examples/worker/src/d1-activity.ts +4 -2
- package/examples/worker/src/index.ts +36 -1
- package/examples/worker/src/r2-artifact-blobs.ts +29 -0
- package/examples/worker/wrangler.jsonc +5 -1
- package/package.json +6 -2
- package/templates/node/AGENTS.md +9 -2
- package/templates/node/README.md +31 -3
- package/templates/node/package.json +1 -1
- 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
|
|
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.
|
|
11
|
-
|
|
12
|
-
and until
|
|
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
|
-
|
|
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,277 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 0.26.0 — 2026-09-25
|
|
8
|
+
|
|
9
|
+
Programs can now write with host approval. When a program reaches a tool that
|
|
10
|
+
is not explicitly read-only, it pauses before the call and returns the exact
|
|
11
|
+
address, arguments, and a token. The new eighth MCP tool,
|
|
12
|
+
`resume_execution`, repeats that call to show the host the real write and
|
|
13
|
+
replays the program from its journal without resending an uncertain outcome.
|
|
14
|
+
`tools/list` now has eight tools on every deployment, so client allowlists and
|
|
15
|
+
`connecta doctor` installations pinned to 0.25 need updating. Resumable
|
|
16
|
+
writes turn on by default when storage supports `compareAndSet` (memory, file,
|
|
17
|
+
or the Worker example's D1 adapter); a program write that previously failed
|
|
18
|
+
now pauses. Cloudflare KV cannot make that claim atomically, so resumable
|
|
19
|
+
writes stay off there. Set `execute.resumableWrites: false` to silence its
|
|
20
|
+
startup warning. Program clocks and randomness are pinned for replay.
|
|
21
|
+
|
|
22
|
+
Artifacts arrive as an optional module with versioned JSON documents, a
|
|
23
|
+
sandboxed viewer and library, and scheduled refresh of one data document from
|
|
24
|
+
a read-only program. A deployment supplies a Node timer or Worker cron;
|
|
25
|
+
connecta starts no background job. Stored pages use the operator sign-in,
|
|
26
|
+
and a dedicated artifact origin may require signing in again because browser
|
|
27
|
+
storage belongs to one origin. Page scripts run with an opaque origin and no
|
|
28
|
+
network by default; only explicitly allowlisted script, style, and font
|
|
29
|
+
origins may load. Artifact version and configuration writes are approval-exempt inside
|
|
30
|
+
programs by default; deployment config can restore the approval prompt.
|
|
31
|
+
`run_refresh` requires host approval and is excluded from that exemption.
|
|
32
|
+
Deployments that omit the module need no
|
|
33
|
+
artifact storage, R2 bucket, cron, browser binding, or second domain.
|
|
34
|
+
|
|
35
|
+
Before upgrading a Worker deployment that uses D1 activity, add the
|
|
36
|
+
`approval` column shown below. Every admitted `/mcp` request, on Node and
|
|
37
|
+
Workers, has a five-minute total lifetime by default; set
|
|
38
|
+
`admission.requests.maxDurationMs` above any longer authorized request. The
|
|
39
|
+
Worker example also enables `enable_request_signal` for live disconnects.
|
|
40
|
+
To add artifacts to that example, use CAS-capable D1
|
|
41
|
+
storage, optionally R2 for bodies, and explicitly configure any CDN origins.
|
|
42
|
+
The Node template now pins 0.26.0 and shows the optional artifact timer.
|
|
43
|
+
`ConnectorCallErrorCode` gains `conflict` for stale artifact edits, while an
|
|
44
|
+
identity can opt into reviewed exact tool grants that remain visible only while
|
|
45
|
+
the loaded catalog classifies them read-only. The operator UI no longer
|
|
46
|
+
returns downstream OAuth or credential-test text in notices; callers that
|
|
47
|
+
read those route messages must use the fixed outcome and consult server logs
|
|
48
|
+
for diagnostics.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **Guarded read-only identity grants (#601).** A deployment may put
|
|
53
|
+
`{ tool: "connector.tool", requireReadOnly: true }` in
|
|
54
|
+
`identity.connectorAccess`. The reviewed exact address stays visible only
|
|
55
|
+
while its loaded catalog explicitly says read-only without a contradictory
|
|
56
|
+
destructive hint. A reclassified tool disappears from discovery and every
|
|
57
|
+
call path, including `call_destructive_tool` and approval-exempt programs;
|
|
58
|
+
new names are never added automatically. Existing string grants keep their
|
|
59
|
+
additive meaning, and a pool can still only narrow the identity's view.
|
|
60
|
+
A stale catalog or a dishonest downstream annotation remains a trust limit.
|
|
61
|
+
|
|
62
|
+
- **Resumable writes and `resume_execution` (#565).** A program's call to a
|
|
63
|
+
tool that is not explicitly read-only pauses the run before anything is
|
|
64
|
+
sent and returns `{ paused: { address, args, token, expiresAt, nextAction,
|
|
65
|
+
hint } }`. `resume_execution` — always listed, annotated destructive — must
|
|
66
|
+
repeat the address and args exactly (`approval_mismatch` otherwise, before
|
|
67
|
+
anything is claimed); it claims the run by compare-and-set and replays the
|
|
68
|
+
program from a journal in the caller's result storage, answering every
|
|
69
|
+
earlier host call from its record — no catalog, permit, connector, or
|
|
70
|
+
activity — and sending the approved write once. `approval: "tool"` approves
|
|
71
|
+
the rest of the run's calls to that tool. Each live write is marked on the
|
|
72
|
+
run's header before it is sent, so racing or crashed resumes never send it
|
|
73
|
+
twice. A write sent but never answered — or answered with anything short
|
|
74
|
+
of a refusal or the downstream tool's own error — stops the run as
|
|
75
|
+
`write_outcome_unknown`, even beside a concurrent pause, and is never sent
|
|
76
|
+
again; a program that stops matching its journal fails
|
|
77
|
+
`execution_diverged`; a resumed play that sent writes and then ended without
|
|
78
|
+
pausing (a sandbox failure, a lapsed claim) fails `execution_interrupted`
|
|
79
|
+
rather than replaying reads it never journaled. Every error from a resumed
|
|
80
|
+
play carries `writes` counts, and once a write landed or may have, its
|
|
81
|
+
advice is to check those before re-running rather than a plain re-run; a
|
|
82
|
+
run that sent writes keeps those counts past its deadline. Every paused-run
|
|
83
|
+
storage call has a deadline (the host-call deadline, at least 5 s), so
|
|
84
|
+
storage that stops answering fails the run rather than holding it. A paused
|
|
85
|
+
run survives a restart and expires `execute.pausedRunTtlSeconds` (default 1,800) after its
|
|
86
|
+
first pause; `execute.maxWrites` (default 10) caps a run's writes on top of
|
|
87
|
+
the host-call budget. `execute.resumableWrites` defaults to whether the
|
|
88
|
+
storage has `compareAndSet`; `true` without it refuses to construct.
|
|
89
|
+
`/health` reports `resumableWrites`, and `connecta doctor` expects the eight
|
|
90
|
+
tools and says when resumable writes are off. See code mode "Pausing and
|
|
91
|
+
resuming" (`W1`–`W11`, `X12`).
|
|
92
|
+
- **Config approval exemptions (#566).** `execute.approval` maps connector ids
|
|
93
|
+
and `connector.tool` addresses to `"never"` or `"ask"`: a program calls an
|
|
94
|
+
exempt write without pausing, while it still spends the write budget, is
|
|
95
|
+
journaled, and records activity. The address wins over the connector entry,
|
|
96
|
+
which wins over a connector's own `approval: "never"` default — reserved for
|
|
97
|
+
connectors connecta ships, such as the artifacts connector — so
|
|
98
|
+
`"ask"` switches such a default off. It works with resumable writes off
|
|
99
|
+
too, where an exempt write the program leaves unawaited still finishes
|
|
100
|
+
before the run ends, and one with an unknown outcome is the result. Exempt is never read-only: discovery keeps the tool approval-required
|
|
101
|
+
and marks its row `approval: "exempt"`, `call_tool` still refuses it, and no
|
|
102
|
+
downstream annotation can grant it. An unknown connector id, or an `api()`
|
|
103
|
+
address its tools do not include, refuses to construct. The operator UI's
|
|
104
|
+
per-tool badge gains its third state, "exempt from approval".
|
|
105
|
+
- **Activity for pauses and approvals.** `ActivityOutcome` gains `paused` and
|
|
106
|
+
`approved`, both with zero attempts; `ActivityCallSource` gains
|
|
107
|
+
`resume_execution`; `ToolCallActivityEvent` gains an optional `approval`
|
|
108
|
+
(`"call"` or `"tool"`), set only on an approval. All three are enums — the
|
|
109
|
+
approved arguments are never recorded. The Worker example's D1 activity
|
|
110
|
+
store writes an `approval` column; add it with
|
|
111
|
+
`ALTER TABLE tool_call_activity ADD COLUMN approval TEXT;` before deploying
|
|
112
|
+
the updated adapter. The operator activity page labels both outcomes and
|
|
113
|
+
says what an approval covered.
|
|
114
|
+
- **Artifact storage and validation, at `@zackbart/connecta/artifacts`
|
|
115
|
+
(#562).** `kvArtifactStore(storage, { blobs?, prefix? })` keeps artifacts
|
|
116
|
+
in any `KVStorage` with `compareAndSet` and `list` — one head record per
|
|
117
|
+
artifact, swapped by compare-and-set, with every earlier version immutable
|
|
118
|
+
beside it and bodies content-addressed — and refuses Cloudflare Workers KV
|
|
119
|
+
at construction, because a write that cannot compare-and-set its head can
|
|
120
|
+
lose a teammate's edit. `validateArtifact({ kind, source, documents? })` is
|
|
121
|
+
the static check every save runs: one `id="artifact-root"`, no frames,
|
|
122
|
+
plugins, forms, or `<base>`, external scripts only from explicitly
|
|
123
|
+
allowlisted origins, and no relative or unapproved network resource URLs,
|
|
124
|
+
with a line number and a fix in every message. The Worker example gains
|
|
125
|
+
`r2-artifact-blobs.ts` for keeping bodies
|
|
126
|
+
in R2 beside a D1 store. The subpath adds no dependency and no Effect.
|
|
127
|
+
- **The built-in artifacts connector (#562, #564).** `createConnecta({ artifacts:
|
|
128
|
+
artifacts({ store }) })` appends an `artifacts` connector with five reads —
|
|
129
|
+
`list_artifacts`, `get_artifact`, `get_document`, `validate_artifact`, and
|
|
130
|
+
`get_refresh` — and nine writes: `create_artifact`, `update_artifact`, `patch_artifact` (exact
|
|
131
|
+
find/replace, every edit matching once or nothing changes),
|
|
132
|
+
`set_documents` (plural and atomic), `rollback_artifact`,
|
|
133
|
+
`archive_artifact`, `restore_artifact`, `set_refresh`, and `run_refresh`.
|
|
134
|
+
Version-changing writes name their expected base, and every write records
|
|
135
|
+
its actor from the request's authorization. The connector skips approval, except for `run_refresh`,
|
|
136
|
+
inside `execute_code` through its own `approval:
|
|
137
|
+
"never"` — `execute.approval: { artifacts: "ask" }` turns that off — while
|
|
138
|
+
still spending the write budget and recording activity; `call_tool` still
|
|
139
|
+
refuses it. The connector serves a publishing guide as
|
|
140
|
+
`connector:artifacts`, required before the first write. An optional
|
|
141
|
+
`renderCheck` hook receives the exact frame document and CSP a viewer will
|
|
142
|
+
load and can refuse a write; without one, static validation is the floor.
|
|
143
|
+
The module needs `publicUrl`, and a configured connector named `artifacts`
|
|
144
|
+
refuses to construct.
|
|
145
|
+
- **Artifact library and sandboxed viewer (#563).** `/artifacts` lists pages;
|
|
146
|
+
`/artifacts/<id>` opens one after the same inbound sign-in as the operator
|
|
147
|
+
UI. A viewer snapshot pins exact view and document versions. Page HTML runs
|
|
148
|
+
in an opaque-origin frame with no fetch or worker access. A deployment may
|
|
149
|
+
allow exact CDN origins for scripts, styles, and fonts; none are allowed by
|
|
150
|
+
default. With `artifactOrigin`, the main host redirects artifact paths and
|
|
151
|
+
the dedicated host serves no MCP or operator routes. A browser bearer stored
|
|
152
|
+
on the main origin must be entered again on the artifact origin; the redirect
|
|
153
|
+
carries no credential.
|
|
154
|
+
- **Scheduled artifact refresh (#564).** `set_refresh` attaches a versioned
|
|
155
|
+
program for one data document on a manual, daily, or weekly schedule;
|
|
156
|
+
`run_refresh` triggers it now, and `get_refresh` reports freshness and a
|
|
157
|
+
bounded run history. The module's `runDue()` is called by the deployment's
|
|
158
|
+
Node timer or Worker cron, never by a core background loop. Runs use only
|
|
159
|
+
shared connectors' explicitly read-only tools within the setter's current
|
|
160
|
+
identity and pool grants, and stop if publishing permission is revoked.
|
|
161
|
+
Connector approval exemptions cannot permit a scheduled write. Runs claim
|
|
162
|
+
the artifact by compare-and-set and validate returned JSON, including any
|
|
163
|
+
configured render check, before publishing a new document version. A failure keeps the last good value and marks the page
|
|
164
|
+
stale without rendering raw failure text in the library or viewer. Each tick
|
|
165
|
+
starts at most ten due pages; the Node template and Worker example show the
|
|
166
|
+
optional wiring.
|
|
167
|
+
- **`conflict` (#562).** A new `ConnectorCallErrorCode` for a write whose base
|
|
168
|
+
someone else already moved past, never retryable as-is, with an optional
|
|
169
|
+
bounded `current` map (at most 20 whole-number entries) on
|
|
170
|
+
`ConnectorCallError` and `CallErrorDetails` saying where things stand. A
|
|
171
|
+
program reads it as `err.details.current`; a top-level call returns it in
|
|
172
|
+
the structured error. A write refused with `conflict` inside a program is a
|
|
173
|
+
known failure (`W9`), not an unknown outcome.
|
|
174
|
+
|
|
175
|
+
### Changed
|
|
176
|
+
|
|
177
|
+
- **`execute_code` asks for a zero-argument program (#602).** Its guidance now
|
|
178
|
+
shows `async () => { ... }`, with `connecta` supplied as the sandbox global.
|
|
179
|
+
Passing `connecta` as an arrow parameter shadows that global and fails; the
|
|
180
|
+
accepted source forms and execution rules have not changed.
|
|
181
|
+
- **Worker resource setup has an optional Alchemy guide (#567).** It covers
|
|
182
|
+
account resources and adoption while keeping the Worker script, domains,
|
|
183
|
+
Browser Rendering, and cron deployment with Wrangler until Alchemy's
|
|
184
|
+
script-upload path is shown to preserve `ctx.access`. Alchemy is not a
|
|
185
|
+
connecta dependency or a second deployment template.
|
|
186
|
+
- **`execute_code` source intake (#576).** Programs over 64 KiB of UTF-8
|
|
187
|
+
source fail before executor admission. The host recovers one fenced async
|
|
188
|
+
arrow even when prose surrounds it, a default-exported arrow, or a named
|
|
189
|
+
async function declaration, so Node and Workers receive the same program.
|
|
190
|
+
- **Eight tools, and instructions that follow the mode.** `tools/list` adds
|
|
191
|
+
`resume_execution` everywhere. With resumable writes on, the MCP
|
|
192
|
+
instructions say programs may write and pause, the `execute_code`
|
|
193
|
+
description advertises the write budget and drops `safety: "readOnly"` from
|
|
194
|
+
its search example (a program looking for a write would not find it), and
|
|
195
|
+
the `usage` skill gains the pause, resume, and unknown-outcome guidance;
|
|
196
|
+
without them, the instructions and description read as they did in 0.25.
|
|
197
|
+
- **The operator UI's "needs approval" badge reads "asks for approval"**, and
|
|
198
|
+
the tool legend says a program pauses for `resume_execution` while a direct
|
|
199
|
+
call crosses `call_destructive_tool`.
|
|
200
|
+
- **A program's clock and randomness are pinned (code mode `P6`).**
|
|
201
|
+
`Date.now()`, `new Date()`, and `Date()` inside `execute_code` return the
|
|
202
|
+
instant the run started and do not advance, and `Math.random()` is an sfc32
|
|
203
|
+
stream seeded per run from the host's CSPRNG; on a Dynamic Worker,
|
|
204
|
+
`crypto.getRandomValues`, `crypto.randomUUID`, and `performance.now()` follow
|
|
205
|
+
the same pin. The replacements are locked like `Error`. Resumable writes
|
|
206
|
+
(#565) replay a paused program from the top, and a program that branched on
|
|
207
|
+
a moving clock or an unseeded draw could not be replayed; pinning every run
|
|
208
|
+
means none behaves differently for having paused. A program that timed its
|
|
209
|
+
own work now measures zero — `diagnostics: true` measures from the host.
|
|
210
|
+
|
|
211
|
+
### Fixed
|
|
212
|
+
|
|
213
|
+
- **Request admission recovers after missing Worker cleanup (#595).** An
|
|
214
|
+
admitted `/mcp` request now has a configurable total lifetime, five minutes
|
|
215
|
+
by default, covering auth, tools, and response delivery. Its own timer aborts
|
|
216
|
+
live work, including connector calls. If workerd ends it without running
|
|
217
|
+
JavaScript cleanup, the next request or a queued request's timer reclaims
|
|
218
|
+
only the expired permit. An orphaned queued request cannot strand its next
|
|
219
|
+
permit indefinitely, and late cleanup cannot release a successor's permit.
|
|
220
|
+
The Worker example enables `enable_request_signal` for prompt live disconnect
|
|
221
|
+
cleanup.
|
|
222
|
+
|
|
223
|
+
- **Legacy MCP handshake close (#572).** A client that leaves while the older
|
|
224
|
+
protocol handshake is still initializing no longer leaves the SDK's
|
|
225
|
+
`notifications/initialized` promise rejected without a handler. Successful
|
|
226
|
+
handshakes still send the notification.
|
|
227
|
+
|
|
228
|
+
- **Operator notices no longer render downstream error text**
|
|
229
|
+
([#568](https://github.com/zackbart/connecta/issues/568)). The OAuth
|
|
230
|
+
connect/disconnect notice and the credential Test result showed whatever the
|
|
231
|
+
server sent back, and that text can come from a token endpoint's error body or
|
|
232
|
+
a provider's refusal, either of which can quote the secret it was sent. Each
|
|
233
|
+
notice now says a fixed sentence chosen by outcome, with the fix prompt for
|
|
234
|
+
that outcome, and the page never renders a route's words there even if an
|
|
235
|
+
older server sends them. The routes log the downstream's text instead — a
|
|
236
|
+
failed OAuth action or credential test at `warn`, a passing test's message at
|
|
237
|
+
`info` — and never record it in activity. A `CredentialTestResult.message` is
|
|
238
|
+
now log-only, and a Test refused because nothing usable is stored carries the
|
|
239
|
+
same `credential_required` or `credential_mismatch` problem the Connections
|
|
240
|
+
page shows.
|
|
241
|
+
|
|
242
|
+
- **Compact schemas group an array's union element.** The `compact` format,
|
|
243
|
+
the default for `search_tools` and `connecta.search`, rendered an array of a
|
|
244
|
+
union as `{ op: "replace" } | { op: "append" }[]`, which reads as one
|
|
245
|
+
operation or an array of the other; Linear `save_issue`'s `patch` was one
|
|
246
|
+
such array. A union, intersection, enum, or type list used as an array
|
|
247
|
+
element, a tuple's rest included, now renders parenthesized, as
|
|
248
|
+
`({ op: "replace" } | { op: "append" })[]`, matching the `typescript`
|
|
249
|
+
format. The renderer records which shapes it rendered at operator level
|
|
250
|
+
rather than rescanning its text, so a pipe in a literal, a constraint, or
|
|
251
|
+
property prose adds no parentheses, and every other shape renders byte for
|
|
252
|
+
byte as before (#569).
|
|
253
|
+
|
|
254
|
+
- **A catalog refresh whose connector ignores abort held every later reader.**
|
|
255
|
+
A refresh flight lived until its connector settled, so a `listTools` that
|
|
256
|
+
ignored its signal kept the flight open until the catalog was invalidated,
|
|
257
|
+
and every reader that joined it timed out in turn while the connector stayed
|
|
258
|
+
unlisted. A flight is now bounded by its owner's deadline, or by the default
|
|
259
|
+
30-second probe timeout for an owner with none. Past it, readers still
|
|
260
|
+
waiting and readers arriving later make a fresh attempt, and a late result
|
|
261
|
+
from the abandoned flight is neither cached nor persisted, so it cannot
|
|
262
|
+
overwrite the fresh one. Abandonment logs a warning
|
|
263
|
+
([#570](https://github.com/zackbart/connecta/issues/570)).
|
|
264
|
+
- **A joined catalog read ended on its starter's deadline.** Readers of one
|
|
265
|
+
catalog in a request share one read, and that read ran under whichever
|
|
266
|
+
reader started it. A short-deadline `connecta.call` that started it failed a
|
|
267
|
+
concurrent `connecta.search` with the call's timeout, and a search cancelled
|
|
268
|
+
by its client cancelled a read others were still waiting on. The shared read
|
|
269
|
+
now carries its own signal and the probe timeout. Each reader waits under its
|
|
270
|
+
own deadline and cancellation, one that leaves fails alone, and the read is
|
|
271
|
+
cancelled only when every reader has gone. It is still one downstream read
|
|
272
|
+
per connector per request. The registry's cross-request refresh no longer
|
|
273
|
+
hands its owner's cancellation to joiners, which make a fresh attempt instead
|
|
274
|
+
([#571](https://github.com/zackbart/connecta/issues/571)).
|
|
275
|
+
|
|
5
276
|
## 0.25.0 — 2026-09-23
|
|
6
277
|
|
|
7
278
|
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
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
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,
|
|
32
|
-
Sandbox["execute_code<br/>the agent's program runs here<br/>
|
|
33
|
-
Explicit["call_destructive_tool<br/>
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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`,
|
|
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
|
|
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.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
|
: "."),
|
package/dist/activity.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Connector } from "../types.js";
|
|
2
|
+
import { type ArtifactOperations } from "./operations.js";
|
|
3
|
+
import { type ArtifactRefreshService } from "./refresh.js";
|
|
4
|
+
import type { ArtifactAllowlist, ArtifactIssue, ArtifactKind, ArtifactLimits } from "./types.js";
|
|
5
|
+
/** What a deployment's render check is handed: exactly what the viewer loads. */
|
|
6
|
+
export interface ArtifactRenderCheckInput {
|
|
7
|
+
/** The complete frame document, data injected. */
|
|
8
|
+
document: string;
|
|
9
|
+
/** The Content-Security-Policy the viewer serves it under. */
|
|
10
|
+
csp: string;
|
|
11
|
+
kind: ArtifactKind;
|
|
12
|
+
/** Aborted when the check's 20-second deadline or the call's own passes. */
|
|
13
|
+
signal: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
export type ArtifactRenderCheckResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
warnings?: string[];
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
errors: string[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A real render check — a headless browser, say — run on `validate_artifact`
|
|
24
|
+
* and on every write that changes what a page renders. It must answer within
|
|
25
|
+
* 20 seconds; a throw or a timeout fails the write as retryable `unavailable`
|
|
26
|
+
* and saves nothing.
|
|
27
|
+
*/
|
|
28
|
+
export type ArtifactRenderCheck = (input: ArtifactRenderCheckInput) => Promise<ArtifactRenderCheckResult>;
|
|
29
|
+
type RenderVerdict = {
|
|
30
|
+
ok: true;
|
|
31
|
+
warnings: ArtifactIssue[];
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
errors: ArtifactIssue[];
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
unavailable: string;
|
|
38
|
+
};
|
|
39
|
+
/** Run a hook under its 20-second deadline and the call's own signal. Never throws. */
|
|
40
|
+
export declare function runRenderCheck(hook: ArtifactRenderCheck, input: {
|
|
41
|
+
document: string;
|
|
42
|
+
csp: string;
|
|
43
|
+
kind: ArtifactKind;
|
|
44
|
+
}, callSignal: AbortSignal | undefined): Promise<RenderVerdict>;
|
|
45
|
+
export interface ArtifactsConnectorOptions {
|
|
46
|
+
operations: ArtifactOperations;
|
|
47
|
+
refresh: ArtifactRefreshService;
|
|
48
|
+
allowlist: ArtifactAllowlist;
|
|
49
|
+
limits: ArtifactLimits;
|
|
50
|
+
renderCheck?: ArtifactRenderCheck;
|
|
51
|
+
/** Where pages are served, when not the deployment's own origin. */
|
|
52
|
+
origin?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function artifactsConnector(options: ArtifactsConnectorOptions): Connector;
|
|
55
|
+
export {};
|