@zackbart/connecta 0.32.0 → 0.34.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/CHANGELOG.md +98 -0
- package/README.md +95 -116
- package/SECURITY.md +1 -1
- package/bin/connecta.mjs +24 -6
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +167 -23
- package/dist/access-tokens.js +676 -140
- package/dist/activity.d.ts +3 -0
- package/dist/agent-identity.js +47 -0
- package/dist/auth/cloudflare-access.js +13 -5
- package/dist/branding.d.ts +7 -0
- package/dist/branding.js +76 -9
- package/dist/call-admission-context.js +15 -0
- package/dist/call-admission.js +30 -43
- package/dist/call-recovery.js +6 -2
- package/dist/catalog-service.js +15 -1
- package/dist/config-value-sources.js +16 -9
- package/dist/config.d.ts +48 -75
- package/dist/config.js +144 -36
- package/dist/connector-access.d.ts +4 -34
- package/dist/connector-access.js +15 -99
- package/dist/connector-caller.d.ts +7 -1
- package/dist/connector-caller.js +4 -2
- package/dist/connectors/api-connector.d.ts +27 -4
- package/dist/connectors/api-connector.js +2 -0
- package/dist/connectors/remote-mcp.js +2 -0
- package/dist/describe-config.d.ts +31 -7
- package/dist/describe-config.js +8 -17
- package/dist/execute.js +1 -11
- package/dist/fix-prompt.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +30 -10
- package/dist/invocation.js +5 -3
- package/dist/membership.d.ts +53 -0
- package/dist/membership.js +121 -0
- package/dist/module-contracts.d.ts +23 -6
- package/dist/oauth-callback-outcome.js +2 -2
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -4
- package/dist/operator-ui/agents.d.ts +74 -0
- package/dist/operator-ui/agents.js +104 -0
- package/dist/operator-ui/config-summary.js +562 -0
- package/dist/operator-ui/connection-map.js +375 -0
- package/dist/operator-ui/contract.d.ts +149 -28
- package/dist/operator-ui/endpoints.js +221 -0
- package/dist/operator-ui/fix-prompts.js +5 -5
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/member-page.js +97 -0
- package/dist/operator-ui/model.d.ts +14 -6
- package/dist/operator-ui/overview.js +134 -0
- package/dist/operator-ui/view.d.ts +107 -37
- package/dist/operator-ui/view.js +163 -86
- package/dist/page-styles.js +5 -3
- package/dist/provider.d.ts +61 -8
- package/dist/provider.js +46 -7
- package/dist/providers/_shared/google/workspace.d.ts +56 -17
- package/dist/providers/_shared/google/workspace.js +34 -8
- package/dist/providers/_shared/rest/tools.js +2 -2
- package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
- package/dist/providers/_shared/rest/value-safety.js +699 -7
- package/dist/providers/ccb/index.js +2 -2
- package/dist/providers/cloudflare/index.js +3 -3
- package/dist/providers/cloudflare/rest.js +25 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/cloudflare/value-safety.js +32 -0
- package/dist/providers/docs/index.js +4 -4
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.js +4 -4
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.js +4 -4
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/gmail/index.js +11 -9
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/mixpanel/index.js +1 -1
- package/dist/providers/mixpanel/skill.generated.js +3 -3
- package/dist/providers/notion/index.d.ts +7 -0
- package/dist/providers/notion/index.js +100 -11
- package/dist/providers/notion/rest.js +45 -5
- package/dist/providers/notion/skill.generated.js +2 -2
- package/dist/providers/notion/value-safety.js +30 -5
- package/dist/providers/overflow/index.js +2 -2
- package/dist/providers/overflow/skill.generated.js +2 -2
- package/dist/providers/planning-center/index.js +2 -2
- package/dist/providers/planning-center/skill.generated.js +4 -4
- package/dist/providers/revenuecat/skill.generated.js +2 -2
- package/dist/providers/sheets/index.d.ts +3 -0
- package/dist/providers/sheets/index.js +7 -4
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.js +4 -4
- package/dist/providers/slides/skill.generated.js +2 -2
- package/dist/providers/stripe/rest.js +9 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/stripe/value-safety.js +22 -0
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/index.js +1 -1
- package/dist/providers/vercel/rest.js +20 -2
- package/dist/providers/vercel/skill.generated.js +2 -2
- package/dist/providers/vercel/value-safety.js +22 -0
- package/dist/registry.d.ts +2 -3
- package/dist/registry.js +23 -9
- package/dist/routes/access-tokens.js +82 -75
- package/dist/routes/activity-disclosure.js +17 -18
- package/dist/routes/activity.js +34 -54
- package/dist/routes/actor-labels.js +33 -4
- package/dist/routes/agent-path.js +43 -0
- package/dist/routes/connect.js +16 -12
- package/dist/routes/credentials.js +16 -40
- package/dist/routes/mcp.js +161 -104
- package/dist/routes/oauth-management.js +11 -5
- package/dist/routes/oauth.js +5 -11
- package/dist/routes/operator.js +24 -23
- package/dist/routes/shared.d.ts +60 -20
- package/dist/routes/shared.js +187 -96
- package/dist/routes/ui-config.js +437 -97
- package/dist/routes/ui-data.js +2 -2
- package/dist/routes/ui.js +9 -16
- package/dist/runtime/call-admission.js +9 -0
- package/dist/server.js +5 -0
- package/dist/skills.js +10 -0
- package/dist/storage/keys.js +9 -1
- package/dist/storage/sql.js +5 -1
- package/dist/types.d.ts +29 -7
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +23 -16
- package/dist/usage-guide.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace-address.js +9 -0
- package/dist/workspace-subject.js +23 -0
- package/documentation/README.md +47 -23
- package/documentation/architecture.md +371 -61
- package/documentation/auth.md +897 -249
- package/documentation/code-mode.md +37 -16
- package/documentation/deploying.md +126 -25
- package/documentation/integrating.md +97 -23
- package/documentation/meta-tools.md +50 -31
- package/documentation/migration-0.33.md +352 -0
- package/documentation/migration-0.34.md +67 -0
- package/documentation/operating.md +3 -1
- package/documentation/operator-tests.md +147 -34
- package/documentation/operator-ui.md +519 -64
- package/documentation/provider-auth-migration.md +37 -23
- package/documentation/provider-migration-0.29.md +2 -2
- package/examples/worker/AGENTS.md +37 -9
- package/examples/worker/README.md +127 -61
- package/examples/worker/src/connecta.config.ts +40 -23
- package/examples/worker/wrangler.jsonc +4 -1
- package/package.json +19 -18
- package/templates/node/.env.example +3 -0
- package/templates/node/AGENTS.md +33 -6
- package/templates/node/Dockerfile +2 -2
- package/templates/node/README.md +80 -52
- package/templates/node/docker-compose.yml +3 -2
- package/templates/node/package.json +4 -3
- package/templates/node/src/adopt-token.ts +23 -0
- package/templates/node/src/connecta.config.ts +28 -12
- package/templates/node/src/rotate-token.ts +17 -0
- package/templates/node/src/provision-token.ts +0 -17
|
@@ -9,8 +9,14 @@ export interface ConnectorCaller {
|
|
|
9
9
|
* with or without a subject, a Connecta-issued access token.
|
|
10
10
|
*/
|
|
11
11
|
authenticated: boolean;
|
|
12
|
-
/** The
|
|
12
|
+
/** The pool the call arrived on, at `/mcp/<pool>` or `/agent/<pool>`. */
|
|
13
13
|
pool?: string;
|
|
14
|
+
/**
|
|
15
|
+
* That pool's configured `owner`, the Workspace address an agent acts as
|
|
16
|
+
* (decisions/0006). Core copies it from the admitted pool's config; no
|
|
17
|
+
* request can set it, and humans never act as it.
|
|
18
|
+
*/
|
|
19
|
+
poolOwner?: string;
|
|
14
20
|
}
|
|
15
21
|
export declare function attachCaller(ctx: ConnectorContext, caller: ConnectorCaller | undefined): ConnectorContext;
|
|
16
22
|
export declare function callerOf(ctx: ConnectorContext): ConnectorCaller | undefined;
|
package/dist/connector-caller.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
// - maintained providers that act *as* the caller downstream — Google
|
|
9
9
|
// Workspace through domain-wide delegation (`src/providers/_shared/google/`) — which
|
|
10
10
|
// hand the identity to a deployment-config function that names the
|
|
11
|
-
// downstream subject (PRINCIPLES.md INV-3, #678)
|
|
12
|
-
//
|
|
11
|
+
// downstream subject (PRINCIPLES.md INV-3, #678), or, for an agent, use the
|
|
12
|
+
// admitted pool's configured owner (decisions/0006), and the scoped registry
|
|
13
|
+
// view hides the connector from an agent whose pool has none. The provider never
|
|
14
|
+
// decides whose account to open; config does, from this.
|
|
13
15
|
//
|
|
14
16
|
// Core sets it from the authorization that admitted the request, on the scoped
|
|
15
17
|
// registry view that request receives, and `api()` hands handlers that same
|
|
@@ -146,10 +146,33 @@ export interface ApiOptions {
|
|
|
146
146
|
/** The closed options api() accepts; see `assertKnownOptions`. */
|
|
147
147
|
export declare const API_OPTIONS: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
148
148
|
readonly shape: {
|
|
149
|
-
callAdmission: import("../config-schema.js").Field<unknown,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
maxPartitions: import("../config-schema.js").Field<
|
|
149
|
+
callAdmission: Omit<import("../config-schema.js").Field<unknown, ConnectorCallAdmissionPolicy | undefined, true>, "parse"> & {
|
|
150
|
+
parse(value: unknown, path: string): ConnectorCallAdmissionPolicy;
|
|
151
|
+
readonly shape: import("../config-schema.js").Shape & {
|
|
152
|
+
maxPartitions: import("../config-schema.js").Field<number, number | undefined, true>;
|
|
153
|
+
rules: import("../config-schema.js").Field<{
|
|
154
|
+
maxConcurrency?: number | undefined;
|
|
155
|
+
maxQueueSize?: number | undefined;
|
|
156
|
+
queueTimeoutMs?: number | undefined;
|
|
157
|
+
retryAfterMs?: number | undefined;
|
|
158
|
+
partitionKey?: ((input: Readonly<import("../types.js").ConnectorCallAdmissionInput>) => string) | undefined;
|
|
159
|
+
budget?: {
|
|
160
|
+
kind: "rolling-window";
|
|
161
|
+
maxCalls: number;
|
|
162
|
+
windowMs: number;
|
|
163
|
+
} | undefined;
|
|
164
|
+
}[], readonly {
|
|
165
|
+
readonly maxConcurrency: number | undefined;
|
|
166
|
+
readonly maxQueueSize: number | undefined;
|
|
167
|
+
readonly queueTimeoutMs: number | undefined;
|
|
168
|
+
readonly retryAfterMs: number | undefined;
|
|
169
|
+
readonly partitionKey: ((input: Readonly<import("../types.js").ConnectorCallAdmissionInput>) => string) | undefined;
|
|
170
|
+
readonly budget: {
|
|
171
|
+
readonly kind: "rolling-window";
|
|
172
|
+
readonly maxCalls: number;
|
|
173
|
+
readonly windowMs: number;
|
|
174
|
+
} | undefined;
|
|
175
|
+
}[], false>;
|
|
153
176
|
};
|
|
154
177
|
};
|
|
155
178
|
usageGuide: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
@@ -90,6 +90,8 @@ function describedApiAuth(opts) {
|
|
|
90
90
|
*/
|
|
91
91
|
export function apiConnector(id, opts, oauth) {
|
|
92
92
|
opts = assertKnownOptions(opts, `api(${JSON.stringify(id)})`, API_OPTIONS);
|
|
93
|
+
if (opts.callAdmission !== undefined)
|
|
94
|
+
CALL_ADMISSION.parse(opts.callAdmission, `api(${JSON.stringify(id)}).callAdmission`);
|
|
93
95
|
assertStaticToolNames(opts.tools, `api(${JSON.stringify(id)}).tools`);
|
|
94
96
|
if (opts.oauth !== undefined && oauth === undefined) {
|
|
95
97
|
throw new Error(`api() connector "${id}" declares oauth but was built without its grant; construct it with api().`);
|
|
@@ -1024,6 +1024,8 @@ function registrationErrorCode(body) {
|
|
|
1024
1024
|
*/
|
|
1025
1025
|
export function remoteMcp(id, opts) {
|
|
1026
1026
|
opts = assertKnownOptions(opts, `remoteMcp(${JSON.stringify(id)})`, REMOTE_MCP_OPTIONS);
|
|
1027
|
+
if (opts.callAdmission !== undefined)
|
|
1028
|
+
CALL_ADMISSION.parse(opts.callAdmission, `remoteMcp(${JSON.stringify(id)}).callAdmission`);
|
|
1027
1029
|
const skillsEnabled = opts.skills;
|
|
1028
1030
|
if (skillsEnabled !== undefined && typeof skillsEnabled !== "boolean") {
|
|
1029
1031
|
throw new Error(`[connecta] connector "${id}" skills must be a boolean.`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
2
2
|
import type { ConnectaConfig, ResolvedConfig } from "./config.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ResolvedPool } from "./membership.js";
|
|
4
|
+
import type { ConnectorAuthDescription, ConnectorDescription, ConnectorToolDescription, DescribedEndpoint, IdentityReference } from "./types.js";
|
|
4
5
|
/** Where a limit's value came from. */
|
|
5
6
|
export type ConfigValueSource = "default" | "config";
|
|
6
7
|
/** One limit and where its value came from. `null` means unset (no bound). */
|
|
@@ -8,6 +9,15 @@ export interface DescribedLimit<T = number> {
|
|
|
8
9
|
value: T | null;
|
|
9
10
|
source: ConfigValueSource;
|
|
10
11
|
}
|
|
12
|
+
/** A resolved pool member: a principal, or a declared agent by name. */
|
|
13
|
+
export type DescribedMember = {
|
|
14
|
+
kind: "human";
|
|
15
|
+
namespace: string;
|
|
16
|
+
id: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "agent";
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
11
21
|
/** One connector as `describeConfig()` reports it. */
|
|
12
22
|
export interface DescribedConnector {
|
|
13
23
|
id: string;
|
|
@@ -88,8 +98,11 @@ export interface ConnectaConfigDescription {
|
|
|
88
98
|
};
|
|
89
99
|
/** Whether the default /mcp endpoint refuses every write, and exact classification overrides from deployment code. */
|
|
90
100
|
readOnly: boolean;
|
|
91
|
-
/**
|
|
92
|
-
|
|
101
|
+
/** The principals plain `/mcp` admits, resolved, in declaration order. Empty on an open deployment. */
|
|
102
|
+
maintainers: Array<{
|
|
103
|
+
namespace: string;
|
|
104
|
+
id: string;
|
|
105
|
+
}>;
|
|
93
106
|
classification: Record<string, Record<string, "read" | "write">>;
|
|
94
107
|
/** Inbound auth providers in the order they are tried. */
|
|
95
108
|
auth: Array<{
|
|
@@ -97,14 +110,22 @@ export interface ConnectaConfigDescription {
|
|
|
97
110
|
interactive: boolean;
|
|
98
111
|
ui?: string;
|
|
99
112
|
}>;
|
|
100
|
-
|
|
101
|
-
|
|
113
|
+
/** Agents declared in code, by name, in declaration order. Never token state. */
|
|
114
|
+
agents: string[];
|
|
115
|
+
/**
|
|
116
|
+
* `readOnly` is resolved: the pool's own value, else the deployment's.
|
|
117
|
+
* `members` are resolved references in declaration order. `owner` is the
|
|
118
|
+
* Workspace address agents act as, or null. `description` is the trimmed
|
|
119
|
+
* text the pool's endpoints add to their MCP instructions, or null.
|
|
120
|
+
*/
|
|
102
121
|
pools: Array<{
|
|
103
122
|
name: string;
|
|
104
123
|
path: string;
|
|
105
124
|
tools: string[];
|
|
106
|
-
|
|
125
|
+
members: DescribedMember[];
|
|
107
126
|
readOnly: boolean;
|
|
127
|
+
owner: string | null;
|
|
128
|
+
description: string | null;
|
|
108
129
|
}>;
|
|
109
130
|
modules: {
|
|
110
131
|
ui: {
|
|
@@ -125,7 +146,6 @@ export interface ConnectaConfigDescription {
|
|
|
125
146
|
};
|
|
126
147
|
accessTokens: {
|
|
127
148
|
enabled: boolean;
|
|
128
|
-
maxActive?: number;
|
|
129
149
|
};
|
|
130
150
|
};
|
|
131
151
|
/** Every accepted store implements `list` and `compareAndSet`, so only its kind is told. */
|
|
@@ -157,6 +177,10 @@ export interface DescribeConfigInput {
|
|
|
157
177
|
registry: import("./registry.js").Registry;
|
|
158
178
|
raw: ConnectaConfig;
|
|
159
179
|
config: ResolvedConfig;
|
|
180
|
+
/** Resolved `maintainers`. */
|
|
181
|
+
maintainers: readonly IdentityReference[];
|
|
182
|
+
/** Resolved pools; members are read from here, never from raw references. */
|
|
183
|
+
pools: ReadonlyMap<string, ResolvedPool>;
|
|
160
184
|
executorName: string | undefined;
|
|
161
185
|
/** True when the executor owns admission, so `admission.code` is unused. */
|
|
162
186
|
executorAdmits: boolean;
|
package/dist/describe-config.js
CHANGED
|
@@ -183,13 +183,6 @@ function describeConnector(connector, deploymentCap, registry) {
|
|
|
183
183
|
...(connector.staticTools ? { tools: describedTools(registry.describeStaticTools(connector.id) ?? []) } : {}),
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
|
-
const IDENTITY_KEYS = [
|
|
187
|
-
"connectorAccess",
|
|
188
|
-
"activityAccess",
|
|
189
|
-
"credentialAdministration",
|
|
190
|
-
"accessTokenManagement",
|
|
191
|
-
"personalConnection",
|
|
192
|
-
];
|
|
193
186
|
const STORE_KINDS = new Set(["memory", "d1", "sqlite"]);
|
|
194
187
|
const storeKind = (described) => typeof described?.kind === "string" && STORE_KINDS.has(described.kind)
|
|
195
188
|
? described.kind
|
|
@@ -198,7 +191,6 @@ const storeKind = (described) => typeof described?.kind === "string" && STORE_KI
|
|
|
198
191
|
export function describeConfig(input) {
|
|
199
192
|
const { raw, config } = input;
|
|
200
193
|
const brand = resolveBranding(config.ui?.branding);
|
|
201
|
-
const accessTokens = config.accessTokens?.describe?.();
|
|
202
194
|
const storage = config.storage;
|
|
203
195
|
const activityStore = config.activity?.store.describe?.();
|
|
204
196
|
const D = CONFIG_DEFAULTS;
|
|
@@ -241,20 +233,24 @@ export function describeConfig(input) {
|
|
|
241
233
|
requests: limitGroup(config.admission.requests, ownValue(ownValue(raw, "admission"), "requests"), Object.keys(D.admission.requests)),
|
|
242
234
|
},
|
|
243
235
|
readOnly: config.readOnly,
|
|
244
|
-
|
|
236
|
+
maintainers: input.maintainers.map(({ namespace, id }) => ({ namespace: String(namespace), id: String(id) })),
|
|
245
237
|
classification: structuredClone(config.classification ?? {}),
|
|
246
238
|
auth: config.auth.map((provider) => ({
|
|
247
239
|
kind: String(provider.kind),
|
|
248
240
|
interactive: provider.interactiveOperator === true,
|
|
249
241
|
...(str(provider.uiAuth?.kind) !== undefined ? { ui: provider.uiAuth.kind } : {}),
|
|
250
242
|
})),
|
|
251
|
-
|
|
243
|
+
agents: [...config.agents],
|
|
252
244
|
pools: Object.entries(config.pools ?? {}).map(([name, pool]) => ({
|
|
253
245
|
readOnly: pool.readOnly ?? config.readOnly,
|
|
254
246
|
name,
|
|
255
247
|
path: `/mcp/${name}`,
|
|
256
248
|
tools: pool.tools.map(String),
|
|
257
|
-
|
|
249
|
+
members: (input.pools.get(name)?.members ?? []).map((member) => member.kind === "agent"
|
|
250
|
+
? { kind: "agent", name: member.name }
|
|
251
|
+
: { kind: "human", namespace: member.namespace, id: member.id }),
|
|
252
|
+
owner: input.pools.get(name)?.owner ?? null,
|
|
253
|
+
description: input.pools.get(name)?.description ?? null,
|
|
258
254
|
})),
|
|
259
255
|
modules: {
|
|
260
256
|
ui: { enabled: config.ui !== undefined },
|
|
@@ -277,12 +273,7 @@ export function describeConfig(input) {
|
|
|
277
273
|
},
|
|
278
274
|
}
|
|
279
275
|
: { enabled: false },
|
|
280
|
-
accessTokens: config.accessTokens
|
|
281
|
-
? {
|
|
282
|
-
enabled: true,
|
|
283
|
-
...(num(accessTokens?.maxActive) !== undefined ? { maxActive: accessTokens.maxActive } : {}),
|
|
284
|
-
}
|
|
285
|
-
: { enabled: false },
|
|
276
|
+
accessTokens: { enabled: config.accessTokens !== undefined },
|
|
286
277
|
},
|
|
287
278
|
storage: {
|
|
288
279
|
configured: raw.storage !== undefined,
|
package/dist/execute.js
CHANGED
|
@@ -798,18 +798,8 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
798
798
|
...args,
|
|
799
799
|
includeSchemas: args.includeSchemas ?? "json",
|
|
800
800
|
includeSchemaKeys: args.includeSchemaKeys !== false,
|
|
801
|
-
}));
|
|
801
|
+
}, catalogDependency));
|
|
802
802
|
boundedDiscoveryText(result, "Request a smaller limit, omit fullDescriptions, use compact schemas, or pass includeSchemaKeys: false.");
|
|
803
|
-
for (const tool of result.tools)
|
|
804
|
-
catalogAddressDependency(tool.address);
|
|
805
|
-
for (const error of result.catalogErrors)
|
|
806
|
-
catalogDependency(error.connector);
|
|
807
|
-
for (const connector of result.absence?.configuredConnectors ?? [])
|
|
808
|
-
catalogDependency(connector);
|
|
809
|
-
for (const connector of result.queryAnalysis?.configuredConnectors ?? [])
|
|
810
|
-
catalogDependency(connector);
|
|
811
|
-
if (result.queryAnalysis?.connectorScope)
|
|
812
|
-
catalogDependency(result.queryAnalysis.connectorScope);
|
|
813
803
|
return result;
|
|
814
804
|
}),
|
|
815
805
|
describe: (raw) => discovery("describe", async () => {
|
package/dist/fix-prompt.js
CHANGED
|
@@ -29,7 +29,7 @@ export function renderFixPrompt(spec, connectorId) {
|
|
|
29
29
|
"own logs (lines prefixed [connecta]) or by reproducing the failure locally. Never put a secret in source, " +
|
|
30
30
|
"a log line, or your reply: secrets belong in the deployment's environment or its credential vault.",
|
|
31
31
|
"Make the smallest change that fixes it, then verify it by redeploying and refreshing the connection on the " +
|
|
32
|
-
"operator
|
|
33
|
-
"a secret only the operator holds
|
|
32
|
+
"operator Connectors page. If the fix needs a step you cannot do yourself, such as a provider console setting " +
|
|
33
|
+
"or a secret only the operator holds, name that exact step instead.",
|
|
34
34
|
].join("\n\n");
|
|
35
35
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,9 @@ export { customExecutor, type CustomExecutorOptions } from "./executor-contract.
|
|
|
5
5
|
export type { AccessTokensModule, ActivityModule, OperatorSurface } from "./module-contracts.js";
|
|
6
6
|
export type { CredentialVault, CredentialMetadata } from "./credential-contract.js";
|
|
7
7
|
export { defineConfig } from "./config.js";
|
|
8
|
-
export type { AdmissionPoolConfig, ConnectaAdmissionConfig, ConnectaCallsConfig, ConnectaConfig, ConnectaDiscoveryConfig, ConnectaExecuteConfig,
|
|
9
|
-
export type {
|
|
8
|
+
export type { AdmissionPoolConfig, ConnectaAdmissionConfig, ConnectaCallsConfig, ConnectaConfig, ConnectaDiscoveryConfig, ConnectaExecuteConfig, ConnectaPoolConfig, ConnectaResultsConfig, RequestAdmissionConfig, } from "./config.js";
|
|
9
|
+
export type { MemberRef } from "./membership.js";
|
|
10
|
+
export type { ConfigValueSource, ConnectaConfigDescription, DescribedConnector, DescribedMember, DescribedLimit, } from "./describe-config.js";
|
|
10
11
|
export interface Connecta {
|
|
11
12
|
/** Web-standard fetch handler. Usable as `export default { fetch: connecta.fetch }`. */
|
|
12
13
|
fetch: (request: Request, env?: unknown, ctx?: unknown) => Promise<Response>;
|
|
@@ -14,7 +15,7 @@ export interface Connecta {
|
|
|
14
15
|
/**
|
|
15
16
|
* A secret-free snapshot of the configuration this deployment runs with,
|
|
16
17
|
* built once at construction: limits and where each came from, auth
|
|
17
|
-
* providers,
|
|
18
|
+
* providers, maintainers, agents, pools, modules, and every connector's source,
|
|
18
19
|
* endpoint, auth mode, and static tools. Header values, keys, client
|
|
19
20
|
* secrets, credentials, and functions never appear. The same object is
|
|
20
21
|
* returned on every call.
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@ import { describeConfigSources } from "./config-value-sources.js";
|
|
|
2
2
|
import { credentialTestRule, describeCredentialTestMismatch } from "./credential-rules.js";
|
|
3
3
|
import { Registry } from "./registry.js";
|
|
4
4
|
import { parseConnectorAccess, POOL_NAME_RE } from "./connector-access.js";
|
|
5
|
+
import { assertsAgents } from "./agent-identity.js";
|
|
6
|
+
import { ConfigError } from "./config-schema.js";
|
|
7
|
+
import { memberContext, resolveMaintainers, resolveMembers, } from "./membership.js";
|
|
5
8
|
import { createFetchHandler } from "./server.js";
|
|
6
9
|
import { droppedBrandingUrls, droppedThemeTokens, droppedUiAuthUrls } from "./branding.js";
|
|
7
10
|
import { readConfig } from "./config.js";
|
|
@@ -10,19 +13,21 @@ export { customExecutor } from "./executor-contract.js";
|
|
|
10
13
|
import { AdmissionController, executorName, isAdmittingExecutor, withExecutorAdmission } from "./executor-admission.js";
|
|
11
14
|
export { defineConfig } from "./config.js";
|
|
12
15
|
/**
|
|
13
|
-
* Validate declared pools against the connector set
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Validate declared pools against the connector set and the members against
|
|
17
|
+
* auth and agents. Everything checkable at construction throws here: a
|
|
18
|
+
* malformed name, an unparseable grant, an unknown connector id, a tool
|
|
19
|
+
* address on an `api()` connector whose static catalog lacks it, and an empty,
|
|
20
|
+
* ambiguous, unknown, or repeated member. Remote catalogs load lazily, so
|
|
21
|
+
* their addresses are checked at catalog load instead and stay unreachable
|
|
22
|
+
* until they match.
|
|
18
23
|
*/
|
|
19
|
-
function resolvePools(pools, readOnly, registry) {
|
|
24
|
+
function resolvePools(pools, readOnly, registry, members) {
|
|
20
25
|
const resolved = new Map();
|
|
21
26
|
if (!pools)
|
|
22
27
|
return resolved;
|
|
23
28
|
// The schema has already refused a pool that is not an object, a missing or
|
|
24
|
-
// non-array `tools
|
|
25
|
-
//
|
|
29
|
+
// non-array `tools` or `members`, and a misspelled key; the retired `grant`
|
|
30
|
+
// is refused there with its replacement named.
|
|
26
31
|
for (const [name, pool] of Object.entries(pools)) {
|
|
27
32
|
if (!POOL_NAME_RE.test(name)) {
|
|
28
33
|
throw new Error(`ConnectaConfig.pools: pool name "${name}" must match [a-z0-9_-]+`);
|
|
@@ -52,8 +57,18 @@ function resolvePools(pools, readOnly, registry) {
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
}
|
|
60
|
+
const path = `ConnectaConfig.pools.${name}.members`;
|
|
61
|
+
if (pool.members.length === 0) {
|
|
62
|
+
throw new ConfigError(`${path} is empty; a pool serves only its members, so list at least one.`);
|
|
63
|
+
}
|
|
55
64
|
// A pool inherits the deployment value unless it sets its own, either way.
|
|
56
|
-
resolved.set(name,
|
|
65
|
+
resolved.set(name, Object.freeze({
|
|
66
|
+
access,
|
|
67
|
+
readOnly: pool.readOnly ?? readOnly,
|
|
68
|
+
members: Object.freeze(resolveMembers(pool.members, path, members)),
|
|
69
|
+
...(pool.owner !== undefined ? { owner: pool.owner.trim() } : {}),
|
|
70
|
+
...(pool.description !== undefined ? { description: pool.description.trim() } : {}),
|
|
71
|
+
}));
|
|
57
72
|
}
|
|
58
73
|
return resolved;
|
|
59
74
|
}
|
|
@@ -191,7 +206,9 @@ export function createConnecta(config) {
|
|
|
191
206
|
results: resolved.results,
|
|
192
207
|
classification: resolved.classification,
|
|
193
208
|
});
|
|
194
|
-
const
|
|
209
|
+
const members = memberContext(resolved.auth, resolved.agents);
|
|
210
|
+
const maintainers = resolveMaintainers(input.maintainers, resolved.auth.some((provider) => !assertsAgents(provider)), members);
|
|
211
|
+
const pools = resolvePools(resolved.pools, resolved.readOnly, registry, members);
|
|
195
212
|
warnInsecureConfig(resolved);
|
|
196
213
|
const requestAdmission = new AdmissionController(resolved.admission.requests);
|
|
197
214
|
let codeAdmission;
|
|
@@ -214,6 +231,8 @@ export function createConnecta(config) {
|
|
|
214
231
|
registry,
|
|
215
232
|
raw: input,
|
|
216
233
|
config: resolved,
|
|
234
|
+
maintainers,
|
|
235
|
+
pools,
|
|
217
236
|
executorName: configuredExecutorName,
|
|
218
237
|
executorAdmits,
|
|
219
238
|
});
|
|
@@ -222,6 +241,7 @@ export function createConnecta(config) {
|
|
|
222
241
|
configDescription: description,
|
|
223
242
|
configValueSources: describeConfigSources(description, input),
|
|
224
243
|
registry,
|
|
244
|
+
maintainers,
|
|
225
245
|
pools,
|
|
226
246
|
executor,
|
|
227
247
|
executorName: configuredExecutorName,
|
package/dist/invocation.js
CHANGED
|
@@ -3,6 +3,7 @@ import { hasControlCharacters } from "./tool-name.js";
|
|
|
3
3
|
import { writeRefusal } from "./tool-safety.js";
|
|
4
4
|
import { Cause, Effect, Exit } from "effect";
|
|
5
5
|
import { isCallAdmissionError } from "./call-admission.js";
|
|
6
|
+
import { attachCallAdmission } from "./call-admission-context.js";
|
|
6
7
|
import { boundedEchoText, classificationCode, classifyCallError, ConnectorCallError, echoedCallArgs, framingError, } from "./errors.js";
|
|
7
8
|
import { downstreamValue } from "./mcp-result.js";
|
|
8
9
|
import { carryFailureFacts, classifiedFailure, failureRecord, logFailure, recordedToolName, } from "./operator-record.js";
|
|
@@ -494,8 +495,9 @@ export class InvocationService {
|
|
|
494
495
|
if (callSignal?.aborted)
|
|
495
496
|
return yield* Effect.fail(callSignal.reason);
|
|
496
497
|
attempts = 1;
|
|
497
|
-
const call = async () => {
|
|
498
|
+
const call = async (permit) => {
|
|
498
499
|
const connectorContext = this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({ signal: callSignal, timeoutMs: context.timeoutMs, defer: this.catalog.defer }));
|
|
500
|
+
attachCallAdmission(connectorContext, permit);
|
|
499
501
|
trackCredentialReads(connectorContext);
|
|
500
502
|
sentSecrets.include(sentSecretsFor(connectorContext));
|
|
501
503
|
dispatchContext = connectorContext;
|
|
@@ -537,14 +539,14 @@ export class InvocationService {
|
|
|
537
539
|
// cannot hold its permit past the deadline.
|
|
538
540
|
const attempt = yield* Effect.exit(Effect.scoped(Effect.gen({ self: this }, function* () {
|
|
539
541
|
stage = "admission";
|
|
540
|
-
yield* timed((elapsed) => {
|
|
542
|
+
const permit = yield* timed((elapsed) => {
|
|
541
543
|
admissionMs += elapsed;
|
|
542
544
|
}, admitted(this.registry, target, args, admissionSignal));
|
|
543
545
|
stage = "downstream";
|
|
544
546
|
const reply = yield* timed((elapsed) => {
|
|
545
547
|
connectorMs += elapsed;
|
|
546
548
|
}, Effect.tryPromise({
|
|
547
|
-
try: () => Promise.resolve(call()),
|
|
549
|
+
try: () => Promise.resolve(call(permit)),
|
|
548
550
|
catch: (error) => error,
|
|
549
551
|
}));
|
|
550
552
|
if (target.connector.kind === "mcp" && isDownstreamInputResult(reply)) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ConnectorAccess } from "./connector-access.js";
|
|
2
|
+
import type { AuthenticatedIdentity, IdentityReference, InboundAuth } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A person or an agent, as deployment code lists it: a bare principal id in
|
|
5
|
+
* the namespace of the deployment's one interactive auth provider,
|
|
6
|
+
* `{ namespace, id }` when that is ambiguous or the id contains `:`, or
|
|
7
|
+
* `"agent:<name>"` for an agent declared in `agents`.
|
|
8
|
+
*/
|
|
9
|
+
export type MemberRef = string | Readonly<IdentityReference>;
|
|
10
|
+
/** A resolved member reference. Humans match by principal, agents by name. */
|
|
11
|
+
export type Member = {
|
|
12
|
+
kind: "human";
|
|
13
|
+
namespace: string;
|
|
14
|
+
id: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "agent";
|
|
17
|
+
name: string;
|
|
18
|
+
};
|
|
19
|
+
/** A declared pool after construction-time validation. */
|
|
20
|
+
export interface ResolvedPool {
|
|
21
|
+
/** Resolved: the pool's own `readOnly`, else the deployment's. */
|
|
22
|
+
readOnly: boolean;
|
|
23
|
+
/** Exactly the pool's `tools`; nothing intersects or widens it. */
|
|
24
|
+
access: ConnectorAccess;
|
|
25
|
+
members: readonly Member[];
|
|
26
|
+
/** Trimmed `owner`: the Workspace address agents in this pool act as. */
|
|
27
|
+
owner?: string;
|
|
28
|
+
/** Trimmed `description`, appended to this pool's MCP instructions. */
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
/** What resolving a reference needs to know about the deployment. */
|
|
32
|
+
export interface MemberContext {
|
|
33
|
+
/** `activityActorNamespace` of each interactive provider, `undefined` where it has none. */
|
|
34
|
+
interactiveNamespaces: ReadonlyArray<string | undefined>;
|
|
35
|
+
agents: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/** The namespaces bare ids may resolve in, read from the resolved providers. */
|
|
38
|
+
export declare function memberContext(auth: readonly InboundAuth[], agents: readonly string[]): MemberContext;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve one list of references, throwing on any that is malformed,
|
|
41
|
+
* ambiguous, unknown, or repeated (INV-11).
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveMembers(value: unknown, path: string, context: MemberContext): Member[];
|
|
44
|
+
/**
|
|
45
|
+
* `maintainers`: people only. Required whenever an inbound provider can admit
|
|
46
|
+
* a person, which is any provider but the built-in agent-token adapter. With
|
|
47
|
+
* agents alone nobody can be a maintainer, so plain `/mcp` admits nobody.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveMaintainers(value: unknown, admitsPeople: boolean, context: MemberContext): readonly IdentityReference[];
|
|
50
|
+
/** Whether an admitted identity is this member. An agent never matches a person. */
|
|
51
|
+
export declare function isMember(member: Member, identity: Readonly<AuthenticatedIdentity>): boolean;
|
|
52
|
+
/** Whether an admitted identity is a listed maintainer. */
|
|
53
|
+
export declare function isMaintainer(maintainers: readonly IdentityReference[], identity: Readonly<AuthenticatedIdentity>): boolean;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Who may open which endpoint (#837, decisions/0006): `maintainers` open plain
|
|
2
|
+
// `/mcp`, and each pool's static `members` open `/mcp/<pool>`. References are
|
|
3
|
+
// resolved once at construction; a request is matched against them and never
|
|
4
|
+
// names its own scope (INV-4). Pure, so core reads it without any auth module.
|
|
5
|
+
import { isAgentName } from "./agent-identity.js";
|
|
6
|
+
import { ConfigError } from "./config-schema.js";
|
|
7
|
+
import { validIdentityReference } from "./identity.js";
|
|
8
|
+
const AGENT_PREFIX = "agent:";
|
|
9
|
+
/** The namespaces bare ids may resolve in, read from the resolved providers. */
|
|
10
|
+
export function memberContext(auth, agents) {
|
|
11
|
+
return {
|
|
12
|
+
interactiveNamespaces: auth
|
|
13
|
+
.filter((provider) => provider.interactiveOperator === true)
|
|
14
|
+
.map((provider) => typeof provider.activityActorNamespace === "string" &&
|
|
15
|
+
validIdentityReference({ namespace: provider.activityActorNamespace, id: "x" })
|
|
16
|
+
? provider.activityActorNamespace
|
|
17
|
+
: undefined),
|
|
18
|
+
agents,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function resolveRef(value, path, context) {
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
if (value.startsWith(AGENT_PREFIX)) {
|
|
24
|
+
const name = value.slice(AGENT_PREFIX.length);
|
|
25
|
+
if (!isAgentName(name) || !context.agents.includes(name)) {
|
|
26
|
+
throw new ConfigError(`${path} names agent "${name}", which ConnectaConfig.agents does not declare.`);
|
|
27
|
+
}
|
|
28
|
+
return { kind: "agent", name };
|
|
29
|
+
}
|
|
30
|
+
if (value.includes(":")) {
|
|
31
|
+
throw new ConfigError(`${path} contains ":". Write a principal whose id contains ":" as { namespace, id }; ` +
|
|
32
|
+
'agents are "agent:<name>".');
|
|
33
|
+
}
|
|
34
|
+
if (!validIdentityReference({ namespace: "x", id: value })) {
|
|
35
|
+
throw new ConfigError(`${path} must be a principal id of 1 to 256 printable, non-space ASCII characters.`);
|
|
36
|
+
}
|
|
37
|
+
const namespaces = context.interactiveNamespaces;
|
|
38
|
+
if (namespaces.length !== 1) {
|
|
39
|
+
throw new ConfigError(`${path} is a bare principal id, which resolves only when exactly one interactive auth provider is ` +
|
|
40
|
+
`configured (found ${namespaces.length}). Write it as { namespace, id }.`);
|
|
41
|
+
}
|
|
42
|
+
const namespace = namespaces[0];
|
|
43
|
+
if (namespace === undefined) {
|
|
44
|
+
throw new ConfigError(`${path} is a bare principal id, but the interactive auth provider declares no activityActorNamespace. ` +
|
|
45
|
+
"Write it as { namespace, id }.");
|
|
46
|
+
}
|
|
47
|
+
return { kind: "human", namespace, id: value };
|
|
48
|
+
}
|
|
49
|
+
if (typeof value !== "object" ||
|
|
50
|
+
value === null ||
|
|
51
|
+
Array.isArray(value) ||
|
|
52
|
+
Object.keys(value).length !== 2 ||
|
|
53
|
+
!Object.hasOwn(value, "namespace") ||
|
|
54
|
+
!Object.hasOwn(value, "id")) {
|
|
55
|
+
throw new ConfigError(`${path} must be a principal id string, { namespace, id }, or "agent:<name>".`);
|
|
56
|
+
}
|
|
57
|
+
const { namespace, id } = value;
|
|
58
|
+
if (typeof namespace !== "string" || typeof id !== "string" || !validIdentityReference({ namespace, id })) {
|
|
59
|
+
throw new ConfigError(`${path} must have a namespace and id of 1 to 256 printable, non-space ASCII characters each.`);
|
|
60
|
+
}
|
|
61
|
+
return { kind: "human", namespace, id };
|
|
62
|
+
}
|
|
63
|
+
const memberKey = (member) => member.kind === "agent" ? `agent\0${member.name}` : `human\0${member.namespace}\0${member.id}`;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve one list of references, throwing on any that is malformed,
|
|
66
|
+
* ambiguous, unknown, or repeated (INV-11).
|
|
67
|
+
*/
|
|
68
|
+
export function resolveMembers(value, path, context) {
|
|
69
|
+
if (!Array.isArray(value)) {
|
|
70
|
+
throw new ConfigError(`${path} must be an array of principal ids, { namespace, id }, or "agent:<name>".`);
|
|
71
|
+
}
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
return value.map((entry, index) => {
|
|
74
|
+
const member = resolveRef(entry, `${path}[${index}]`, context);
|
|
75
|
+
const key = memberKey(member);
|
|
76
|
+
if (seen.has(key))
|
|
77
|
+
throw new ConfigError(`${path}[${index}] repeats an earlier member.`);
|
|
78
|
+
seen.add(key);
|
|
79
|
+
return Object.freeze(member);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `maintainers`: people only. Required whenever an inbound provider can admit
|
|
84
|
+
* a person, which is any provider but the built-in agent-token adapter. With
|
|
85
|
+
* agents alone nobody can be a maintainer, so plain `/mcp` admits nobody.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveMaintainers(value, admitsPeople, context) {
|
|
88
|
+
const path = "ConnectaConfig.maintainers";
|
|
89
|
+
if (value === undefined || (Array.isArray(value) && value.length === 0)) {
|
|
90
|
+
if (admitsPeople) {
|
|
91
|
+
throw new ConfigError(`${path} is required with inbound auth: list the people who may use plain /mcp, administer shared ` +
|
|
92
|
+
"credentials, manage agent tokens, and read activity.");
|
|
93
|
+
}
|
|
94
|
+
return Object.freeze([]);
|
|
95
|
+
}
|
|
96
|
+
if (!admitsPeople) {
|
|
97
|
+
throw new ConfigError(`${path} needs an inbound auth provider that admits people, such as clerkAuth or cloudflareAccessAuth. ` +
|
|
98
|
+
"Without one no maintainer can be admitted; agents are never maintainers.");
|
|
99
|
+
}
|
|
100
|
+
const members = resolveMembers(value, path, context);
|
|
101
|
+
return Object.freeze(members.map((member, index) => {
|
|
102
|
+
if (member.kind === "agent") {
|
|
103
|
+
throw new ConfigError(`${path}[${index}] is an agent. Agents are never maintainers; add them to a pool's members.`);
|
|
104
|
+
}
|
|
105
|
+
return Object.freeze({ namespace: member.namespace, id: member.id });
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
/** Whether an admitted identity is this member. An agent never matches a person. */
|
|
109
|
+
export function isMember(member, identity) {
|
|
110
|
+
if (member.kind === "agent")
|
|
111
|
+
return identity.agent !== undefined && identity.agent === member.name;
|
|
112
|
+
const principal = identity.principal;
|
|
113
|
+
return (identity.agent === undefined &&
|
|
114
|
+
principal !== undefined &&
|
|
115
|
+
principal.namespace === member.namespace &&
|
|
116
|
+
principal.id === member.id);
|
|
117
|
+
}
|
|
118
|
+
/** Whether an admitted identity is a listed maintainer. */
|
|
119
|
+
export function isMaintainer(maintainers, identity) {
|
|
120
|
+
return maintainers.some((maintainer) => isMember({ kind: "human", ...maintainer }, identity));
|
|
121
|
+
}
|
|
@@ -17,13 +17,30 @@ export interface ActivityModule {
|
|
|
17
17
|
readonly recordTool: typeof recordToolActivity;
|
|
18
18
|
readonly recordChange: typeof recordCatalogChangeActivity;
|
|
19
19
|
}
|
|
20
|
-
/** Optional
|
|
20
|
+
/** Optional agent-token authentication and operator lifecycle. */
|
|
21
21
|
export interface AccessTokensModule {
|
|
22
22
|
readonly auth: import("./types.js").InboundAuth;
|
|
23
23
|
handle(context: RouteContext): Promise<Response | null>;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Every declared agent's token slot, in declaration order: the list GET
|
|
26
|
+
* /ui/access-tokens serves, which the maintainer's operator contract
|
|
27
|
+
* repeats field by field. Never secret material.
|
|
28
|
+
*/
|
|
29
|
+
slots(): Promise<Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
state: "none";
|
|
32
|
+
revision: string;
|
|
33
|
+
} | {
|
|
34
|
+
name: string;
|
|
35
|
+
state: "issued";
|
|
36
|
+
issuedAt: string;
|
|
37
|
+
revision: string;
|
|
38
|
+
} | {
|
|
39
|
+
name: string;
|
|
40
|
+
state: "revoked";
|
|
41
|
+
revokedAt: string;
|
|
42
|
+
revision: string;
|
|
43
|
+
}>>;
|
|
44
|
+
/** The same module bound to the deployment's declared `agents`; createConnecta calls it once. */
|
|
45
|
+
forAgents(agents: readonly string[]): AccessTokensModule;
|
|
29
46
|
}
|