@zackbart/connecta-eve 0.0.0 → 0.36.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/README.md CHANGED
@@ -1,5 +1,151 @@
1
- # Connecta Eve helper
1
+ # @zackbart/connecta-eve
2
2
 
3
- This placeholder establishes the package for trusted publishing. It contains no runtime code. Install version 0.35.0 or later for the supported helper.
3
+ Connect an Eve agent to a Connecta agent pool and load its endpoint instructions
4
+ once per turn. The helper runs in the Eve app; the Connecta server
5
+ runs separately. No `@zackbart/connecta` dependency is installed by this package.
4
6
 
5
- [Package guide](https://github.com/zackbart/connecta/tree/main/packages/connecta-eve).
7
+ Requires **Node 24+** and **Eve >=0.84.0 <0.85.0**. Development and consumer
8
+ checks use real Eve 0.84.0. The range covers its patch line; newer minor releases
9
+ need a compatibility check. Helper releases share Connecta's version and tag;
10
+ choose the helper version matching your server release.
11
+
12
+ ## Install and add two files
13
+
14
+ ```sh
15
+ npm install @zackbart/connecta-eve eve@0.84.0
16
+ ```
17
+
18
+ The server must declare your agent, admit it to the named pool and issue its
19
+ agent token. See Connecta's [agent token setup](https://github.com/zackbart/connecta/blob/main/documentation/auth.md#issue-rotate-or-revoke-an-agent-token).
20
+
21
+ Set these in the Eve app's environment:
22
+
23
+ ```sh
24
+ CONNECTA_URL=https://connecta.example.com
25
+ CONNECTA_ACCESS_TOKEN=cta_your_agent_token
26
+ ```
27
+
28
+ Create `agent/connections/connecta.ts`:
29
+
30
+ ```ts
31
+ import { connectaConnection } from "@zackbart/connecta-eve";
32
+
33
+ export default connectaConnection({
34
+ pool: "assistant",
35
+ description: "Shared CRM and tickets for the assistant.",
36
+ });
37
+ ```
38
+
39
+ Create `agent/instructions/connecta.ts`:
40
+
41
+ ```ts
42
+ import { connectaInstructions } from "@zackbart/connecta-eve";
43
+
44
+ export default connectaInstructions({ pool: "assistant" });
45
+ ```
46
+
47
+ Both factories accept `url` to override `CONNECTA_URL`. With `pool`, the URL is
48
+ `<url>/agent/<pool>`; without `pool`, supply the complete endpoint URL. Pool
49
+ names must match `[a-z0-9_-]+`. `connectaEndpoint({ url, pool })` returns the same
50
+ URL for other clients. Keep both files pointed at the same endpoint and token
51
+ source.
52
+
53
+ Auth is app-owned. The default token comes from `CONNECTA_ACCESS_TOKEN`, read
54
+ again for each request, so rotation requires no process restart. A `getToken`
55
+ callback can return a string or a promise of one; it overrides the environment.
56
+ No user's identity is forwarded.
57
+
58
+ ## Eve 0.84.0 dependency override
59
+
60
+ Eve 0.84.0 pins undici 8.10.2. Connecta's development/test override does not ship
61
+ with this helper, so the Eve app must apply its own override to 8.12.3 before
62
+ installing. For an npm app, add this to its `package.json`, then reinstall and
63
+ commit the lockfile:
64
+
65
+ ```json
66
+ { "overrides": { "eve": { "undici": "8.12.3" } } }
67
+ ```
68
+
69
+ For a pnpm app, add `overrides: { "eve@0.84.0>undici": "8.12.3" }` to its
70
+ `pnpm-workspace.yaml`. Check the resolved dependency graph and audit the app.
71
+ Remove this override after upgrading to an Eve release that pins a fixed version
72
+ and validating compatibility. The server's optional undici 6.29.1 peer is a
73
+ separate dependency and keeps its exact pin.
74
+
75
+ ## Approval and writes
76
+
77
+ The default policy is Eve's `never()`: authorized writes run unattended. Use a
78
+ read-only Connecta pool when the agent must never write. For an interactive
79
+ agent, add `gateWrites: true` to the connection to request user approval before
80
+ any tool whose `readOnlyHint` is not `true`, including writable `execute_code`.
81
+ `gateConnectaWrites` exports that policy for composition. An explicit Eve
82
+ `approval`, either a policy or an approval configuration, takes precedence over
83
+ `gateWrites`.
84
+
85
+ The gate uses the host's cached annotations. If a read becomes a write before
86
+ dispatch, Connecta applies its current classification; a writable pool can
87
+ execute the write without a fresh approval. A read-only pool refuses it.
88
+
89
+ ## Endpoint instructions
90
+
91
+ Instructions use `server/discover` (MCP 2026-07-28), falling back to legacy
92
+ `initialize` when discovery returns a response without guidance, including a
93
+ non-OK status or an unsupported-method response. Network errors and timeouts
94
+ never trigger initialize. Both JSON and SSE replies are accepted. Each request
95
+ has a five-second timeout. Successful guidance is cached for ten minutes per
96
+ resolver; `ttlMs` overrides that duration. Eve evaluates instructions before
97
+ every model call; the helper keeps one result per turn in Eve's memo, so a turn
98
+ sees the same guidance throughout and at most one refresh. A failed refresh
99
+ keeps the last copy, or adds nothing if no fetch has succeeded, and waits 30
100
+ seconds before retrying.
101
+ When Eve cancels an evaluation, the helper rethrows the cancellation instead of
102
+ counting it as a failure: nothing is kept for that turn and no wait starts.
103
+ Tools remain usable. Instructions do not grant access.
104
+
105
+ ## Vercel OIDC token source
106
+
107
+ This variant requires a **Connecta 0.35.0 or later** deployment with Vercel
108
+ workload admission configured for this agent. Workload admission was added in
109
+ [#975](https://github.com/zackbart/connecta/pull/975) and is available from 0.35.0.
110
+ Earlier releases require agent tokens.
111
+ Once configured, use Vercel's token resolver in **both** files:
112
+
113
+ ```ts
114
+ import { getVercelOidcToken } from "@vercel/oidc";
115
+ import { connectaConnection } from "@zackbart/connecta-eve";
116
+
117
+ export default connectaConnection({
118
+ pool: "assistant",
119
+ description: "Shared CRM and tickets for the assistant.",
120
+ getToken: getVercelOidcToken,
121
+ });
122
+ ```
123
+
124
+ ```ts
125
+ import { getVercelOidcToken } from "@vercel/oidc";
126
+ import { connectaInstructions } from "@zackbart/connecta-eve";
127
+
128
+ export default connectaInstructions({ pool: "assistant", getToken: getVercelOidcToken });
129
+ ```
130
+
131
+ Install `@vercel/oidc` in the Eve app when using this variant. The helper has no
132
+ dependency on it and never switches credential types automatically.
133
+
134
+ ## Contributor validation
135
+
136
+ In a source checkout with dependencies installed and both packages built, run
137
+ `pnpm --filter @zackbart/connecta-eve run probe:catalog --output /absolute/path/outside/checkout`
138
+ on Node 24. This boots real Eve 0.84.0 with a packed, installed helper against
139
+ seeded local catalogs of 40, 100, 250, 500, and 1,000 downstream tools on the
140
+ meta, native, and native-plus-code surfaces. Native listings include two
141
+ Connecta tools beyond the downstream count. A 1,001-tool native catalog must
142
+ refuse listing. `--counts 40,1000` selects a smaller matrix.
143
+
144
+ The probe uses Eve's public language-model interface with a deterministic local
145
+ transport, without model credentials or paid calls. It measures construction,
146
+ cold and warm discovery, schema and result bytes, call counts, concurrent-call
147
+ isolation, bounded-result paging, and process memory. Fresh child processes
148
+ isolate each matrix cell; Eve startup is separate from turn latency. Reports
149
+ retain actual Eve events and transport inputs. Token usage and model task
150
+ quality remain unmeasured; these results establish local runtime contracts,
151
+ not live Vercel workload admission or production model performance.
@@ -0,0 +1,55 @@
1
+ import { type McpClientConnectionDefinition } from "eve/connections";
2
+ import { type Approval, type ApprovalPolicy } from "eve/tools/approval";
3
+ /** Where an Eve agent reaches Connecta and how it proves who it is. */
4
+ export interface ConnectaEndpointOptions {
5
+ /**
6
+ * The deployment's base URL, or a full endpoint URL when `pool` is absent.
7
+ * Defaults to the `CONNECTA_URL` environment variable.
8
+ */
9
+ url?: string;
10
+ /** The agent's pool; the endpoint is `<url>/agent/<pool>`. */
11
+ pool?: string;
12
+ /**
13
+ * The agent's bearer token, resolved on every request so a rotation needs
14
+ * no restart. Defaults to the `CONNECTA_ACCESS_TOKEN` environment variable.
15
+ */
16
+ getToken?: () => string | Promise<string>;
17
+ }
18
+ export interface ConnectaConnectionOptions extends ConnectaEndpointOptions {
19
+ /** What the connection offers, for the model choosing which connection to search. */
20
+ description: string;
21
+ /** Eve's approval for every Connecta tool: a policy or configuration. Defaults to `never()`. */
22
+ approval?: Approval;
23
+ /**
24
+ * Ask a person before every tool not annotated read-only: each write tool
25
+ * on a native endpoint, and `execute_code` on a writable one. Ignored when
26
+ * `approval` is set.
27
+ */
28
+ gateWrites?: boolean;
29
+ }
30
+ export interface ConnectaInstructionsOptions extends ConnectaEndpointOptions {
31
+ /** How long fetched instructions are reused. Default ten minutes. */
32
+ ttlMs?: number;
33
+ /** Fetch implementation, for tests. */
34
+ fetch?: typeof fetch;
35
+ }
36
+ /** The endpoint URL these options name. */
37
+ export declare function connectaEndpoint(options?: ConnectaEndpointOptions): string;
38
+ /**
39
+ * Approve reads and ask a person about everything else, keyed on the
40
+ * annotations Connecta derives from its own classification.
41
+ */
42
+ export declare const gateConnectaWrites: ApprovalPolicy;
43
+ /**
44
+ * The Eve connection for a Connecta agent endpoint, with app-owned auth:
45
+ * one agent token, sent as a bearer and re-read on every request.
46
+ */
47
+ export declare function connectaConnection(options: ConnectaConnectionOptions): McpClientConnectionDefinition;
48
+ /**
49
+ * Connecta's instructions as Eve system instructions, refreshed once per turn.
50
+ * Eve does not pass an MCP server's `instructions` to the model, so this
51
+ * fetches them and caches each success; a failure keeps the last copy, or adds
52
+ * nothing, and the tools still work. Eve evaluates the resolver before every
53
+ * model call; a memo keyed on the turn keeps one result for the whole turn.
54
+ */
55
+ export declare function connectaInstructions(options?: ConnectaInstructionsOptions): import("eve/connections").DynamicSentinel<import("eve/instructions").DynamicInstructionsResult, import("eve/connections").DynamicContext>;
package/dist/index.js ADDED
@@ -0,0 +1,133 @@
1
+ import { defineMcpClientConnection } from "eve/connections";
2
+ import { defineDynamic, defineInstructions } from "eve/instructions";
3
+ import { never } from "eve/tools/approval";
4
+ function environment(name) {
5
+ const value = globalThis.process?.env?.[name];
6
+ return value?.trim() || undefined;
7
+ }
8
+ /** The endpoint URL these options name. */
9
+ export function connectaEndpoint(options = {}) {
10
+ const url = options.url ?? environment("CONNECTA_URL");
11
+ if (!url)
12
+ throw new Error("Connecta needs a url, or CONNECTA_URL in the environment.");
13
+ if (options.pool === undefined)
14
+ return url;
15
+ if (!/^[a-z0-9_-]+$/.test(options.pool))
16
+ throw new Error(`Connecta pool "${options.pool}" must match [a-z0-9_-]+.`);
17
+ return `${url.replace(/\/+$/, "")}/agent/${options.pool}`;
18
+ }
19
+ async function tokenFor(options) {
20
+ const token = options.getToken ? (await options.getToken()).trim() : environment("CONNECTA_ACCESS_TOKEN");
21
+ if (!token)
22
+ throw new Error("Connecta needs an agent token: getToken, or CONNECTA_ACCESS_TOKEN in the environment.");
23
+ return token;
24
+ }
25
+ /**
26
+ * Approve reads and ask a person about everything else, keyed on the
27
+ * annotations Connecta derives from its own classification.
28
+ */
29
+ export const gateConnectaWrites = ({ toolAnnotations }) => toolAnnotations?.readOnlyHint === true ? "not-applicable" : "user-approval";
30
+ /**
31
+ * The Eve connection for a Connecta agent endpoint, with app-owned auth:
32
+ * one agent token, sent as a bearer and re-read on every request.
33
+ */
34
+ export function connectaConnection(options) {
35
+ return defineMcpClientConnection({
36
+ url: connectaEndpoint(options),
37
+ description: options.description,
38
+ auth: {
39
+ principalType: "app",
40
+ getToken: async () => ({ token: await tokenFor(options) }),
41
+ },
42
+ approval: options.approval ?? (options.gateWrites ? gateConnectaWrites : never()),
43
+ });
44
+ }
45
+ /** Connecta's MCP `instructions` for one endpoint, or null when unavailable. */
46
+ async function instructionRequest(options, method, signal) {
47
+ const modern = method === "server/discover";
48
+ const response = await (options.fetch ?? fetch)(connectaEndpoint(options), {
49
+ method: "POST",
50
+ headers: {
51
+ Authorization: `Bearer ${await tokenFor(options)}`,
52
+ "Content-Type": "application/json",
53
+ Accept: "application/json, text/event-stream",
54
+ ...(modern ? { "MCP-Protocol-Version": "2026-07-28", "Mcp-Method": method } : {}),
55
+ },
56
+ body: JSON.stringify({
57
+ jsonrpc: "2.0",
58
+ id: 1,
59
+ method,
60
+ params: modern
61
+ ? {
62
+ _meta: {
63
+ "io.modelcontextprotocol/protocolVersion": "2026-07-28",
64
+ "io.modelcontextprotocol/clientCapabilities": {},
65
+ "io.modelcontextprotocol/clientInfo": { name: "connecta-eve-instructions", version: "1" },
66
+ },
67
+ }
68
+ : {
69
+ protocolVersion: "2025-06-18",
70
+ capabilities: {},
71
+ clientInfo: { name: "connecta-eve-instructions", version: "1" },
72
+ },
73
+ }),
74
+ signal: AbortSignal.any([signal, AbortSignal.timeout(5_000)]),
75
+ });
76
+ if (!response.ok)
77
+ return null;
78
+ const text = await response.text();
79
+ // Streamable HTTP answers as JSON or as one SSE `data:` event.
80
+ const json = text.trimStart().startsWith("{")
81
+ ? text
82
+ : text
83
+ .split("\n")
84
+ .find((line) => line.startsWith("data:"))
85
+ ?.slice("data:".length);
86
+ if (!json)
87
+ return null;
88
+ const instructions = JSON.parse(json).result?.instructions;
89
+ return typeof instructions === "string" && instructions.trim() ? instructions.trim() : null;
90
+ }
91
+ async function fetchConnectaInstructions(options, signal) {
92
+ // Only a completed discovery response can justify legacy negotiation.
93
+ // Network failures and timeouts propagate to the resolver's fail-open path.
94
+ const discovered = await instructionRequest(options, "server/discover", signal);
95
+ return discovered ?? instructionRequest(options, "initialize", signal);
96
+ }
97
+ /**
98
+ * Connecta's instructions as Eve system instructions, refreshed once per turn.
99
+ * Eve does not pass an MCP server's `instructions` to the model, so this
100
+ * fetches them and caches each success; a failure keeps the last copy, or adds
101
+ * nothing, and the tools still work. Eve evaluates the resolver before every
102
+ * model call; a memo keyed on the turn keeps one result for the whole turn.
103
+ */
104
+ export function connectaInstructions(options = {}) {
105
+ const ttlMs = options.ttlMs ?? 600_000;
106
+ let cached;
107
+ let retryAfter = 0;
108
+ const current = async (signal) => {
109
+ signal.throwIfAborted();
110
+ if (cached && Date.now() - cached.at <= ttlMs)
111
+ return cached.content;
112
+ if (Date.now() < retryAfter)
113
+ return cached?.content ?? null;
114
+ let fresh;
115
+ try {
116
+ fresh = await fetchConnectaInstructions(options, signal);
117
+ }
118
+ catch {
119
+ // Eve's cancellation is not an endpoint failure: rethrowing keeps it out
120
+ // of the memo and the backoff, which every session of this resolver shares.
121
+ signal.throwIfAborted();
122
+ fresh = null;
123
+ }
124
+ if (fresh)
125
+ cached = { at: Date.now(), content: fresh };
126
+ retryAfter = fresh ? 0 : Date.now() + 30_000;
127
+ return fresh ?? cached?.content ?? null;
128
+ };
129
+ return defineDynamic(async (ctx) => {
130
+ const content = await ctx.memo(["connecta-instructions", ctx.turn?.id ?? null], ({ abortSignal }) => current(abortSignal));
131
+ return content ? defineInstructions({ content: `Connecta guidance:\n${content}` }) : null;
132
+ });
133
+ }
package/package.json CHANGED
@@ -1,19 +1,47 @@
1
1
  {
2
2
  "name": "@zackbart/connecta-eve",
3
- "version": "0.0.0",
4
- "description": "Bootstrap placeholder for the Connecta Eve helper; use 0.35.0 or later.",
5
- "license": "MIT",
3
+ "version": "0.36.0",
6
4
  "type": "module",
5
+ "sideEffects": false,
6
+ "description": "Eve connection and instructions helpers for Connecta agent endpoints.",
7
+ "license": "MIT",
8
+ "engines": {
9
+ "node": ">=24"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
7
14
  "repository": {
8
15
  "type": "git",
9
16
  "url": "git+https://github.com/zackbart/connecta.git",
10
17
  "directory": "packages/connecta-eve"
11
18
  },
12
- "publishConfig": {
13
- "access": "public"
14
- },
15
19
  "files": [
20
+ "dist",
16
21
  "README.md",
17
22
  "LICENSE"
18
- ]
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "peerDependencies": {
31
+ "eve": ">=0.84.0 <0.85.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^24.0.0",
35
+ "eve": "0.84.0",
36
+ "typescript": "^5.9.3",
37
+ "vitest": "^4.1.6"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.build.json",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "check": "pnpm run typecheck && pnpm run test && pnpm run build",
44
+ "prepack": "pnpm run check",
45
+ "probe:catalog": "node scripts/probe-catalog.mjs"
46
+ }
19
47
  }