@zackbart/connecta 0.32.0 → 0.33.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 +63 -0
- package/README.md +15 -7
- 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/agent-identity.js +47 -0
- package/dist/auth/cloudflare-access.js +13 -5
- package/dist/call-admission-context.js +15 -0
- package/dist/call-admission.js +30 -43
- package/dist/call-recovery.js +6 -2
- package/dist/config-value-sources.js +11 -9
- package/dist/config.d.ts +37 -75
- package/dist/config.js +110 -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 +3 -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 +29 -7
- package/dist/describe-config.js +7 -17
- package/dist/index.d.ts +4 -3
- package/dist/index.js +29 -10
- package/dist/invocation.js +5 -3
- package/dist/membership.d.ts +51 -0
- package/dist/membership.js +121 -0
- package/dist/module-contracts.d.ts +23 -6
- 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/contract.d.ts +149 -28
- package/dist/operator-ui/endpoints.js +192 -0
- package/dist/operator-ui/fix-prompts.js +4 -4
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/member-page.js +112 -0
- package/dist/operator-ui/model.d.ts +14 -6
- package/dist/operator-ui/view.d.ts +84 -19
- package/dist/operator-ui/view.js +82 -21
- package/dist/page-styles.js +1 -1
- package/dist/provider.d.ts +61 -8
- package/dist/provider.js +46 -7
- package/dist/providers/_shared/google/workspace.d.ts +53 -16
- package/dist/providers/_shared/google/workspace.js +30 -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 +1 -1
- 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 +3 -3
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.js +3 -3
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.js +3 -3
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/gmail/index.js +10 -8
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/mixpanel/index.js +1 -1
- 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 +1 -1
- package/dist/providers/planning-center/index.js +1 -1
- package/dist/providers/sheets/index.d.ts +3 -0
- package/dist/providers/sheets/index.js +6 -3
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.js +3 -3
- 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/vercel/rest.js +20 -2
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/value-safety.js +22 -0
- package/dist/registry.d.ts +2 -3
- package/dist/registry.js +8 -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 +145 -95
- package/dist/routes/oauth-management.js +11 -5
- package/dist/routes/oauth.js +3 -9
- 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/storage/keys.js +9 -1
- package/dist/types.d.ts +11 -0
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +16 -14
- 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 +46 -22
- package/documentation/architecture.md +355 -50
- package/documentation/auth.md +775 -243
- package/documentation/code-mode.md +14 -11
- package/documentation/deploying.md +121 -24
- package/documentation/integrating.md +92 -21
- package/documentation/meta-tools.md +14 -9
- package/documentation/migration-0.33.md +344 -0
- package/documentation/operating.md +3 -1
- package/documentation/operator-tests.md +115 -28
- package/documentation/operator-ui.md +326 -61
- package/documentation/provider-auth-migration.md +37 -23
- package/documentation/provider-migration-0.29.md +2 -2
- package/examples/worker/AGENTS.md +30 -8
- package/examples/worker/README.md +92 -53
- package/examples/worker/src/connecta.config.ts +36 -20
- package/examples/worker/wrangler.jsonc +3 -0
- package/package.json +3 -2
- package/templates/node/.env.example +3 -0
- package/templates/node/AGENTS.md +27 -6
- package/templates/node/Dockerfile +2 -2
- package/templates/node/README.md +67 -42
- 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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Code-declared agents (#837): the names deployment code lists in `agents`,
|
|
2
|
+
// the identity an admitted agent carries, and the one provider trusted to
|
|
3
|
+
// assert it. Pure, so core reads it without reaching the token module.
|
|
4
|
+
import { ConfigError } from "./config-schema.js";
|
|
5
|
+
/** Agent names: lowercase letters, digits, `_` and `-`. */
|
|
6
|
+
const AGENT_NAME_RE = /^[a-z0-9_-]+$/u;
|
|
7
|
+
/** Longest accepted agent name, so a name always fits a storage key. */
|
|
8
|
+
const MAX_AGENT_NAME_LENGTH = 64;
|
|
9
|
+
/** Directory namespace of agent subjects and activity actors. */
|
|
10
|
+
export const AGENT_NAMESPACE = "connecta:access-tokens:v1";
|
|
11
|
+
/** Whether `value` is an acceptable agent name. */
|
|
12
|
+
export function isAgentName(value) {
|
|
13
|
+
return typeof value === "string" && value.length <= MAX_AGENT_NAME_LENGTH && AGENT_NAME_RE.test(value);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The subject id that partitions an agent's per-caller state, such as result
|
|
17
|
+
* handles. The `agent:` prefix keeps it apart from the UUID subjects older
|
|
18
|
+
* tokens used in the same namespace.
|
|
19
|
+
*/
|
|
20
|
+
export function agentSubjectId(name) {
|
|
21
|
+
return `agent:${name}`;
|
|
22
|
+
}
|
|
23
|
+
/** Validate a declared agent list: an array of unique, well-formed names. */
|
|
24
|
+
export function assertAgentNames(value, path) {
|
|
25
|
+
if (!Array.isArray(value))
|
|
26
|
+
throw new ConfigError(`${path} must be an array of agent names.`);
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
value.forEach((name, index) => {
|
|
29
|
+
if (!isAgentName(name)) {
|
|
30
|
+
throw new ConfigError(`${path}[${index}] must be an agent name: 1 to ${MAX_AGENT_NAME_LENGTH} characters of ` +
|
|
31
|
+
"lowercase letters, digits, _ and -.");
|
|
32
|
+
}
|
|
33
|
+
if (seen.has(name))
|
|
34
|
+
throw new ConfigError(`${path}[${index}] repeats the agent name "${name}".`);
|
|
35
|
+
seen.add(name);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const agentProviders = new WeakSet();
|
|
39
|
+
/** Mark the built-in access-token adapter as the one provider whose `agent` is trusted. */
|
|
40
|
+
export function trustAgentProvider(provider) {
|
|
41
|
+
agentProviders.add(provider);
|
|
42
|
+
return provider;
|
|
43
|
+
}
|
|
44
|
+
/** Whether `provider` is the built-in access-token adapter. */
|
|
45
|
+
export function assertsAgents(provider) {
|
|
46
|
+
return agentProviders.has(provider);
|
|
47
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDeadline } from "../timeout.js";
|
|
1
2
|
import { authorizationCredential } from "../inbound-credential.js";
|
|
2
3
|
import { validIdentityReference } from "../identity.js";
|
|
3
4
|
function identityString(identity, field) {
|
|
@@ -9,7 +10,7 @@ function identityString(identity, field) {
|
|
|
9
10
|
function unauthorized() {
|
|
10
11
|
return {
|
|
11
12
|
ok: false,
|
|
12
|
-
response: Response.json({ error: "Cloudflare Access authentication required" }, { status: 401, headers: { "WWW-Authenticate": 'Bearer scope="openid email"' } }),
|
|
13
|
+
response: Response.json({ error: "Cloudflare Access authentication required" }, { status: 401, headers: { "WWW-Authenticate": 'Bearer scope="openid email"', "Cache-Control": "no-store" } }),
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
@@ -34,17 +35,24 @@ export function cloudflareAccessAuth() {
|
|
|
34
35
|
return unauthorized();
|
|
35
36
|
let identity;
|
|
36
37
|
try {
|
|
37
|
-
identity = await access.getIdentity()
|
|
38
|
+
identity = await withDeadline(() => access.getIdentity(), {
|
|
39
|
+
timeoutMs: 5000,
|
|
40
|
+
signal: request.signal,
|
|
41
|
+
timeoutError: new Error("Access identity lookup timed out"),
|
|
42
|
+
});
|
|
38
43
|
}
|
|
39
44
|
catch {
|
|
40
|
-
return
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
response: Response.json({ error: "Cloudflare Access identity unavailable", problem: "identity_unavailable" }, { status: 503, headers: { "Cache-Control": "no-store", "Retry-After": "1" } }),
|
|
48
|
+
};
|
|
41
49
|
}
|
|
42
50
|
// Access binds ctx.access.aud to its Worker application at the edge.
|
|
43
51
|
// Service credentials can pass that edge, but connecta machines must
|
|
44
52
|
// present a cta_ token to the machine provider instead.
|
|
45
53
|
const forbidden = () => ({
|
|
46
54
|
ok: false,
|
|
47
|
-
response: Response.json({ error: "Cloudflare Access human identity required" }, { status: 403 }),
|
|
55
|
+
response: Response.json({ error: "Cloudflare Access human identity required" }, { status: 403, headers: { "Cache-Control": "no-store" } }),
|
|
48
56
|
});
|
|
49
57
|
if (!identity || typeof identity !== "object" || Array.isArray(identity))
|
|
50
58
|
return forbidden();
|
|
@@ -56,7 +64,7 @@ export function cloudflareAccessAuth() {
|
|
|
56
64
|
if (!userId) {
|
|
57
65
|
return {
|
|
58
66
|
ok: false,
|
|
59
|
-
response: Response.json({ error: "Cloudflare Access user identity required" }, { status: 403 }),
|
|
67
|
+
response: Response.json({ error: "Cloudflare Access user identity required" }, { status: 403, headers: { "Cache-Control": "no-store" } }),
|
|
60
68
|
};
|
|
61
69
|
}
|
|
62
70
|
return { ok: true, userId, subjectId: userId };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { chargeCallBudget } from "./runtime/call-admission.js";
|
|
2
|
+
import { runEdge } from "./runtime/run.js";
|
|
3
|
+
const permits = new WeakMap();
|
|
4
|
+
export function attachCallAdmission(ctx, permit) {
|
|
5
|
+
permits.set(ctx, permit);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Charge one sequential auxiliary request using the enclosing concurrency slot.
|
|
9
|
+
* Await before dispatch and propagate refusal. Contexts outside an invocation
|
|
10
|
+
* have no registry policy to charge.
|
|
11
|
+
*/
|
|
12
|
+
export function chargeConnectorCall(ctx) {
|
|
13
|
+
const permit = permits.get(ctx);
|
|
14
|
+
return permit ? runEdge(chargeCallBudget(permit, ctx.signal)) : Promise.resolve();
|
|
15
|
+
}
|
package/dist/call-admission.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Deferred, Duration, Effect } from "effect";
|
|
2
|
+
import { CALL_ADMISSION } from "./provider.js";
|
|
2
3
|
import { ConnectorCallError } from "./errors.js";
|
|
3
|
-
import { provideCallAdmissionProgram, startCallAdmission } from "./runtime/call-admission.js";
|
|
4
|
+
import { provideCallAdmissionProgram, provideCallBudgetCharge, startCallAdmission } from "./runtime/call-admission.js";
|
|
4
5
|
import { fromSignal, runEdge } from "./runtime/run.js";
|
|
5
6
|
const DEFAULT_MAX_QUEUE_SIZE = 32;
|
|
6
7
|
const DEFAULT_QUEUE_TIMEOUT_MS = 5_000;
|
|
@@ -50,18 +51,6 @@ export function aggregateCallAdmissionSnapshots(snapshots) {
|
|
|
50
51
|
}
|
|
51
52
|
return aggregate;
|
|
52
53
|
}
|
|
53
|
-
function positiveWhole(value, name) {
|
|
54
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
|
|
55
|
-
throw new TypeError(`${name} must be a positive whole number.`);
|
|
56
|
-
}
|
|
57
|
-
return value;
|
|
58
|
-
}
|
|
59
|
-
function nonNegativeWhole(value, name) {
|
|
60
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
|
|
61
|
-
throw new TypeError(`${name} must be a non-negative whole number.`);
|
|
62
|
-
}
|
|
63
|
-
return value;
|
|
64
|
-
}
|
|
65
54
|
/**
|
|
66
55
|
* Per-runtime, per-connector call admission. State contains only bounded
|
|
67
56
|
* partition keys, counters, timestamps, signals, and promise continuations;
|
|
@@ -88,36 +77,14 @@ export class ConnectorCallAdmissionController {
|
|
|
88
77
|
queueWaitMaxMs = 0;
|
|
89
78
|
constructor(connectorId, policy) {
|
|
90
79
|
this.connectorId = connectorId;
|
|
91
|
-
|
|
92
|
-
throw new TypeError(`connector "${connectorId}" callAdmission.rules must contain exactly one rule in this release.`);
|
|
93
|
-
}
|
|
80
|
+
policy = CALL_ADMISSION.parse(policy, `connector "${connectorId}" callAdmission`);
|
|
94
81
|
const rule = policy.rules[0];
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
: positiveWhole(rule.maxConcurrency, `connector "${connectorId}" callAdmission maxConcurrency`);
|
|
102
|
-
if (this.maxConcurrency === undefined &&
|
|
103
|
-
(rule.maxQueueSize !== undefined || rule.queueTimeoutMs !== undefined || rule.retryAfterMs !== undefined)) {
|
|
104
|
-
throw new TypeError(`connector "${connectorId}" callAdmission queue settings require maxConcurrency.`);
|
|
105
|
-
}
|
|
106
|
-
this.maxQueueSize = nonNegativeWhole(rule.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, `connector "${connectorId}" callAdmission maxQueueSize`);
|
|
107
|
-
this.queueTimeoutMs = positiveWhole(rule.queueTimeoutMs ?? DEFAULT_QUEUE_TIMEOUT_MS, `connector "${connectorId}" callAdmission queueTimeoutMs`);
|
|
108
|
-
this.retryAfterMs = nonNegativeWhole(rule.retryAfterMs ?? DEFAULT_RETRY_AFTER_MS, `connector "${connectorId}" callAdmission retryAfterMs`);
|
|
109
|
-
this.maxPartitions = positiveWhole(policy.maxPartitions ?? DEFAULT_MAX_PARTITIONS, `connector "${connectorId}" callAdmission maxPartitions`);
|
|
110
|
-
if (rule.budget) {
|
|
111
|
-
if (rule.budget.kind !== "rolling-window") {
|
|
112
|
-
throw new TypeError(`connector "${connectorId}" callAdmission budget kind must be "rolling-window".`);
|
|
113
|
-
}
|
|
114
|
-
const maxCalls = positiveWhole(rule.budget.maxCalls, `connector "${connectorId}" callAdmission budget.maxCalls`);
|
|
115
|
-
const windowMs = positiveWhole(rule.budget.windowMs, `connector "${connectorId}" callAdmission budget.windowMs`);
|
|
116
|
-
this.budget = { maxCalls, windowMs };
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
this.budget = undefined;
|
|
120
|
-
}
|
|
82
|
+
this.maxConcurrency = rule.maxConcurrency;
|
|
83
|
+
this.maxQueueSize = rule.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
84
|
+
this.queueTimeoutMs = rule.queueTimeoutMs ?? DEFAULT_QUEUE_TIMEOUT_MS;
|
|
85
|
+
this.retryAfterMs = rule.retryAfterMs ?? DEFAULT_RETRY_AFTER_MS;
|
|
86
|
+
this.maxPartitions = policy.maxPartitions ?? DEFAULT_MAX_PARTITIONS;
|
|
87
|
+
this.budget = rule.budget ? { maxCalls: rule.budget.maxCalls, windowMs: rule.budget.windowMs } : undefined;
|
|
121
88
|
this.partitionKey = rule.partitionKey;
|
|
122
89
|
}
|
|
123
90
|
acquire(input) {
|
|
@@ -179,7 +146,7 @@ export class ConnectorCallAdmissionController {
|
|
|
179
146
|
this.queueWaitMaxMs = Math.max(this.queueWaitMaxMs, waitMs);
|
|
180
147
|
}
|
|
181
148
|
let released = false;
|
|
182
|
-
|
|
149
|
+
const permit = {
|
|
183
150
|
waitMs,
|
|
184
151
|
release: () => {
|
|
185
152
|
if (released)
|
|
@@ -190,6 +157,26 @@ export class ConnectorCallAdmissionController {
|
|
|
190
157
|
this.pump(state);
|
|
191
158
|
},
|
|
192
159
|
};
|
|
160
|
+
provideCallBudgetCharge(permit, (chargedAt, signal) => {
|
|
161
|
+
if (this.closed || released) {
|
|
162
|
+
return Effect.fail(new CallAdmissionError("closed", "unavailable", `Connector "${this.connectorId}" call admission ${this.closed ? "is closed" : "permit is released"}.`));
|
|
163
|
+
}
|
|
164
|
+
if (signal?.aborted) {
|
|
165
|
+
this.cancelledTotal++;
|
|
166
|
+
return Effect.fail(this.cancelled(signal));
|
|
167
|
+
}
|
|
168
|
+
this.pruneBudget(state, chargedAt);
|
|
169
|
+
const retryAfterMs = this.budgetRetryAfterMs(state, chargedAt);
|
|
170
|
+
if (retryAfterMs !== undefined) {
|
|
171
|
+
this.rateLimitedTotal++;
|
|
172
|
+
return Effect.fail(this.budgetLimited(retryAfterMs));
|
|
173
|
+
}
|
|
174
|
+
if (this.budget)
|
|
175
|
+
state.admittedAt.push(chargedAt);
|
|
176
|
+
this.admittedTotal++;
|
|
177
|
+
return Effect.void;
|
|
178
|
+
});
|
|
179
|
+
return permit;
|
|
193
180
|
}
|
|
194
181
|
pump(state) {
|
|
195
182
|
if (this.closed || this.maxConcurrency === undefined)
|
package/dist/call-recovery.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
const MAX_FACTS = 4;
|
|
2
2
|
const MAX_TEXT = 128;
|
|
3
|
+
// Shared with the REST write schema so every accepted key survives recovery.
|
|
4
|
+
export const IDEMPOTENCY_KEY_MAX_LENGTH = 255;
|
|
3
5
|
const NAME = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
4
6
|
const facts = new WeakMap();
|
|
5
7
|
/**
|
|
6
8
|
* Record facts that let the caller reconcile or safely retry this call's
|
|
7
9
|
* write. Bounded: at most four string facts with short camelCase names; a fact
|
|
8
|
-
* outside the bound is dropped rather than truncated.
|
|
10
|
+
* outside the bound is dropped rather than truncated. Idempotency keys allow
|
|
11
|
+
* 255 characters to match the REST write schema; other values allow 128.
|
|
9
12
|
*/
|
|
10
13
|
export function recordRecovery(ctx, recovery) {
|
|
11
14
|
const kept = { ...facts.get(ctx) };
|
|
12
15
|
for (const [name, value] of Object.entries(recovery)) {
|
|
13
16
|
if (Object.keys(kept).length >= MAX_FACTS && !Object.hasOwn(kept, name))
|
|
14
17
|
break;
|
|
15
|
-
|
|
18
|
+
const maxLength = name === "idempotencyKey" ? IDEMPOTENCY_KEY_MAX_LENGTH : MAX_TEXT;
|
|
19
|
+
if (!NAME.test(name) || name.length > 32 || typeof value !== "string" || value.length > maxLength)
|
|
16
20
|
continue;
|
|
17
21
|
kept[name] = value;
|
|
18
22
|
}
|
|
@@ -44,7 +44,6 @@ function factorySource(sources, parts) {
|
|
|
44
44
|
export function describeConfigSources(snapshot, raw) {
|
|
45
45
|
const sources = {};
|
|
46
46
|
const connectorSources = new Map(raw.connectors.map((connector) => [connector.id, factorySources(connector)]));
|
|
47
|
-
const moduleSources = { accessTokens: factorySources(raw.accessTokens) };
|
|
48
47
|
const provided = (value) => (value === undefined ? "default" : "config");
|
|
49
48
|
for (const path of configValuePaths(snapshot)) {
|
|
50
49
|
const parts = path.split(".").slice(1);
|
|
@@ -56,12 +55,12 @@ export function describeConfigSources(snapshot, raw) {
|
|
|
56
55
|
source = provided(raw.serverInfo?.[key]);
|
|
57
56
|
else if (group === "urls")
|
|
58
57
|
source = key === "mcpPath" ? "default" : provided(raw[key]);
|
|
59
|
-
else if (group === "
|
|
60
|
-
source = provided(raw.
|
|
58
|
+
else if (group === "maintainers")
|
|
59
|
+
source = provided(raw.maintainers);
|
|
61
60
|
else if (group === "readOnly")
|
|
62
61
|
source = provided(raw.readOnly);
|
|
63
|
-
else if (group === "
|
|
64
|
-
source = provided(raw.
|
|
62
|
+
else if (group === "agents")
|
|
63
|
+
source = provided(raw.agents);
|
|
65
64
|
else if (group === "storage")
|
|
66
65
|
source = provided(raw.storage);
|
|
67
66
|
else if (group === "deploymentInfo")
|
|
@@ -70,11 +69,14 @@ export function describeConfigSources(snapshot, raw) {
|
|
|
70
69
|
source = provided(raw.classification);
|
|
71
70
|
else if (group === "pools")
|
|
72
71
|
// An inherited value comes from wherever the deployment's came from.
|
|
73
|
-
source =
|
|
72
|
+
source =
|
|
73
|
+
field === "readOnly"
|
|
74
|
+
? provided(raw.pools?.[key]?.readOnly ?? raw.readOnly)
|
|
75
|
+
: field === "owner"
|
|
76
|
+
? provided(raw.pools?.[key]?.owner)
|
|
77
|
+
: provided(raw.pools);
|
|
74
78
|
else if (group === "modules") {
|
|
75
|
-
|
|
76
|
-
const factory = moduleSources[key];
|
|
77
|
-
source = (factory ? factorySource(factory, parts.slice(2)) : undefined) ?? provided(module);
|
|
79
|
+
source = provided(key === "ui" ? raw.ui : key === "vault" ? raw.vault : key === "activity" ? raw.activity : raw.accessTokens);
|
|
78
80
|
}
|
|
79
81
|
else if (group === "branding") {
|
|
80
82
|
const brand = raw.ui?.branding;
|
package/dist/config.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { ConnectorGrant } from "./connector-access.js";
|
|
2
1
|
import type { CredentialVault } from "./credential-contract.js";
|
|
3
2
|
import { type ConfigInput, type ConfigOutput } from "./config-schema.js";
|
|
3
|
+
import type { MemberRef } from "./membership.js";
|
|
4
4
|
import type { AccessTokensModule, ActivityModule, OperatorSurface } from "./module-contracts.js";
|
|
5
|
-
import type {
|
|
6
|
-
/** Config-owned identity rules for one deployment and tenant. */
|
|
7
|
-
export type ConnectorPermission = "all" | "none" | readonly string[];
|
|
5
|
+
import type { Connector, Executor, InboundAuth, KVStorage, Logger } from "./types.js";
|
|
8
6
|
declare const discovery: {
|
|
9
7
|
/**
|
|
10
8
|
* Maximum connector catalogs/status probes fetched at once by discovery
|
|
@@ -162,26 +160,6 @@ declare const admission: {
|
|
|
162
160
|
};
|
|
163
161
|
};
|
|
164
162
|
};
|
|
165
|
-
declare const identity: {
|
|
166
|
-
/**
|
|
167
|
-
* What this admitted identity may discover and call: `"all"`, or a list
|
|
168
|
-
* whose entries are connector ids (the whole connector) and `connector.tool`
|
|
169
|
-
* addresses (that tool only), or `{ tool: "connector.tool",
|
|
170
|
-
* requireReadOnly: true }` for an exact tool only while its loaded catalog
|
|
171
|
-
* explicitly classifies it read-only. Grants are additive: an unrestricted
|
|
172
|
-
* connector or address grant wins over the guarded form. An address naming
|
|
173
|
-
* a tool the catalog lacks is unreachable and warned once, never widened.
|
|
174
|
-
*/
|
|
175
|
-
connectorAccess: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>, ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined, true>;
|
|
176
|
-
/** Global payload-free activity reads. Defaults to interactive humans. */
|
|
177
|
-
activityAccess: import("./config-schema.js").Field<(principal: Readonly<IdentityReference>) => boolean | Promise<boolean>, ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined, true>;
|
|
178
|
-
/** Shared credential and OAuth administration. Defaults to none. */
|
|
179
|
-
credentialAdministration: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
|
|
180
|
-
/** Client-token lifecycle management by interactive humans. Defaults to false. */
|
|
181
|
-
accessTokenManagement: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>, ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined, true>;
|
|
182
|
-
/** Connecting or changing the caller's personal account. Defaults to none. */
|
|
183
|
-
personalConnection: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
|
|
184
|
-
};
|
|
185
163
|
declare const pool: {
|
|
186
164
|
/**
|
|
187
165
|
* Whether this pool's endpoint refuses every write: write tools are absent
|
|
@@ -193,66 +171,52 @@ declare const pool: {
|
|
|
193
171
|
/** Connector ids and exact `connector.tool` addresses in this pool. */
|
|
194
172
|
tools: import("./config-schema.js").Field<readonly string[], readonly string[], false>;
|
|
195
173
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
174
|
+
* Who may open `/mcp/<name>`: principal ids in the namespace of the one
|
|
175
|
+
* interactive auth provider, `{ namespace, id }`, or `"agent:<name>"` for a
|
|
176
|
+
* declared agent. Required and non-empty; the pool serves exactly its
|
|
177
|
+
* `tools` to these members and the same 404 to everyone else.
|
|
199
178
|
*/
|
|
200
|
-
|
|
179
|
+
members: import("./config-schema.js").Field<readonly MemberRef[], readonly MemberRef[], false>;
|
|
180
|
+
/**
|
|
181
|
+
* The Google Workspace address agents in this pool act as, on a Workspace
|
|
182
|
+
* connector whose `subject` is a mapping function. Humans always act as
|
|
183
|
+
* themselves through that mapping, and a fixed `subject` address applies to
|
|
184
|
+
* everyone. Without an owner, an agent's Workspace calls fail
|
|
185
|
+
* `auth_required`.
|
|
186
|
+
*/
|
|
187
|
+
owner: import("./config-schema.js").Field<string, string | undefined, true>;
|
|
201
188
|
};
|
|
202
189
|
declare const connectaConfig: {
|
|
203
190
|
connectors: import("./config-schema.js").Field<Connector[], readonly Connector[], false>;
|
|
204
191
|
/** Inbound auth adapters, such as clerkAuth(...); omit for open (dev). */
|
|
205
192
|
auth: import("./config-schema.js").Field<InboundAuth | InboundAuth[], readonly InboundAuth[], true>;
|
|
206
|
-
/** Code-derived connection visibility and independent management permissions. */
|
|
207
|
-
identity: import("./config-schema.js").Field<{
|
|
208
|
-
connectorAccess?: ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined;
|
|
209
|
-
activityAccess?: ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined;
|
|
210
|
-
credentialAdministration?: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
|
|
211
|
-
accessTokenManagement?: ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined;
|
|
212
|
-
personalConnection?: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
|
|
213
|
-
}, {
|
|
214
|
-
readonly connectorAccess: ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined;
|
|
215
|
-
readonly activityAccess: ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined;
|
|
216
|
-
readonly credentialAdministration: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
|
|
217
|
-
readonly accessTokenManagement: ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined;
|
|
218
|
-
readonly personalConnection: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
|
|
219
|
-
}, true> & {
|
|
220
|
-
readonly shape: {
|
|
221
|
-
/**
|
|
222
|
-
* What this admitted identity may discover and call: `"all"`, or a list
|
|
223
|
-
* whose entries are connector ids (the whole connector) and `connector.tool`
|
|
224
|
-
* addresses (that tool only), or `{ tool: "connector.tool",
|
|
225
|
-
* requireReadOnly: true }` for an exact tool only while its loaded catalog
|
|
226
|
-
* explicitly classifies it read-only. Grants are additive: an unrestricted
|
|
227
|
-
* connector or address grant wins over the guarded form. An address naming
|
|
228
|
-
* a tool the catalog lacks is unreachable and warned once, never widened.
|
|
229
|
-
*/
|
|
230
|
-
connectorAccess: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>, ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined, true>;
|
|
231
|
-
/** Global payload-free activity reads. Defaults to interactive humans. */
|
|
232
|
-
activityAccess: import("./config-schema.js").Field<(principal: Readonly<IdentityReference>) => boolean | Promise<boolean>, ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined, true>;
|
|
233
|
-
/** Shared credential and OAuth administration. Defaults to none. */
|
|
234
|
-
credentialAdministration: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
|
|
235
|
-
/** Client-token lifecycle management by interactive humans. Defaults to false. */
|
|
236
|
-
accessTokenManagement: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>, ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined, true>;
|
|
237
|
-
/** Connecting or changing the caller's personal account. Defaults to none. */
|
|
238
|
-
personalConnection: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
|
|
239
|
-
};
|
|
240
|
-
};
|
|
241
193
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
194
|
+
* Agents, declared by name: lowercase letters, digits, `_` and `-`, each
|
|
195
|
+
* unique. Each has one `cta_` token slot that operators issue, rotate, and
|
|
196
|
+
* revoke; nothing outside code adds, renames, or rebinds an agent. Removing
|
|
197
|
+
* a name stops its token on the next request. Requires `accessTokens`.
|
|
198
|
+
* An admitted agent carries `identity.agent` and has no principal, so
|
|
199
|
+
* personal connectors are absent for it. Defaults to none.
|
|
200
|
+
*/
|
|
201
|
+
agents: import("./config-schema.js").Field<readonly string[], readonly string[], true>;
|
|
202
|
+
/**
|
|
203
|
+
* The people who may use plain `/mcp`, which serves the whole catalog with
|
|
204
|
+
* the deployment's `readOnly`, and who administer the deployment. Same
|
|
205
|
+
* reference forms as pool `members`, people only. Required with inbound
|
|
206
|
+
* auth; a maintainer opens no pool unless listed among its members.
|
|
245
207
|
*/
|
|
246
|
-
|
|
247
|
-
/** Named tool pools, each served at `/mcp/<name>` to
|
|
208
|
+
maintainers: import("./config-schema.js").Field<readonly MemberRef[], readonly MemberRef[] | undefined, true>;
|
|
209
|
+
/** Named tool pools, each served at `/mcp/<name>` to its members only. */
|
|
248
210
|
pools: import("./config-schema.js").Field<Record<string, {
|
|
249
211
|
readOnly?: boolean | undefined;
|
|
250
|
-
|
|
212
|
+
owner?: string | undefined;
|
|
251
213
|
tools: readonly string[];
|
|
214
|
+
members: readonly MemberRef[];
|
|
252
215
|
}>, Readonly<Record<string, {
|
|
253
216
|
readonly readOnly: boolean | undefined;
|
|
254
217
|
readonly tools: readonly string[];
|
|
255
|
-
readonly
|
|
218
|
+
readonly members: readonly MemberRef[];
|
|
219
|
+
readonly owner: string | undefined;
|
|
256
220
|
}>> | undefined, true>;
|
|
257
221
|
/** Exact connector id -> tool name -> verdict. Unknown names fail at catalog publication. */
|
|
258
222
|
classification: import("./config-schema.js").Field<Record<string, Record<string, "read" | "write">>, Readonly<Record<string, Readonly<Record<string, "read" | "write">>>> | undefined, true>;
|
|
@@ -281,7 +245,7 @@ declare const connectaConfig: {
|
|
|
281
245
|
vault: import("./config-schema.js").Field<CredentialVault, CredentialVault | undefined, true>;
|
|
282
246
|
/** Optional connection UI, created by operatorUi() from /ui. */
|
|
283
247
|
ui: import("./config-schema.js").Field<OperatorSurface, OperatorSurface | undefined, true>;
|
|
284
|
-
/** Optional
|
|
248
|
+
/** Optional agent tokens for the declared `agents`, created by accessTokens() from /auth/access-tokens. */
|
|
285
249
|
accessTokens: import("./config-schema.js").Field<AccessTokensModule, AccessTokensModule | undefined, true>;
|
|
286
250
|
/** Tool-catalog TTL bounds and probe deadlines. */
|
|
287
251
|
discovery: import("./config-schema.js").Field<{
|
|
@@ -581,11 +545,9 @@ export type AdmissionPoolConfig = ConfigInput<typeof codeAdmission>;
|
|
|
581
545
|
export type RequestAdmissionConfig = ConfigInput<typeof requestAdmission>;
|
|
582
546
|
/** Runtime-portable server-memory boundaries for `/mcp` and fallback code-mode work. */
|
|
583
547
|
export type ConnectaAdmissionConfig = ConfigInput<typeof admission>;
|
|
584
|
-
export type ConnectaIdentityConfig = ConfigInput<typeof identity>;
|
|
585
548
|
/**
|
|
586
|
-
* A named tool pool served at `/mcp/<name
|
|
587
|
-
*
|
|
588
|
-
* remains its ceiling and the pool can only narrow it.
|
|
549
|
+
* A named tool pool served at `/mcp/<name>`: exactly its `tools`, to its
|
|
550
|
+
* `members` only.
|
|
589
551
|
*/
|
|
590
552
|
export type ConnectaPoolConfig = ConfigInput<typeof pool>;
|
|
591
553
|
type Parsed = ConfigOutput<typeof connectaConfig>;
|