@zackbart/connecta 0.37.1 → 0.39.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 +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/dist/native-tools.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { bytesToHex } from "./bytes.js";
|
|
2
|
+
import { routeNames } from "./agent-routes.js";
|
|
1
3
|
import { duringViewEffect } from "./runtime/call.js";
|
|
2
4
|
import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
3
5
|
import { Effect } from "effect";
|
|
@@ -35,7 +37,7 @@ function shortHash(text) {
|
|
|
35
37
|
const shortHashNative = /* @__PURE__ */ registerAuth(shortHash, function (text) {
|
|
36
38
|
return authFlow(function* () {
|
|
37
39
|
const digest = new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", enc.encode(text)])));
|
|
38
|
-
return
|
|
40
|
+
return bytesToHex(digest.subarray(0, HASH_CHARS / 2));
|
|
39
41
|
});
|
|
40
42
|
});
|
|
41
43
|
/**
|
|
@@ -148,6 +150,8 @@ export class NativeSurface {
|
|
|
148
150
|
// Listing omits a read-only endpoint's writes, as search does.
|
|
149
151
|
readOnly: options.readOnly,
|
|
150
152
|
defer: options.defer,
|
|
153
|
+
// Declared schema descriptions read as this pool serves them.
|
|
154
|
+
routes: routeNames(options.executeCode ? "native-code" : "native"),
|
|
151
155
|
});
|
|
152
156
|
}
|
|
153
157
|
/**
|
|
@@ -182,7 +186,7 @@ export class NativeSurface {
|
|
|
182
186
|
...(entry ? { _meta: { "dev.connecta/address": `${entry.connector.id}.${entry.tool.name}` } } : {}),
|
|
183
187
|
}, authMethod((args, request) => Effect.gen({ self: this }, function* () {
|
|
184
188
|
const result = yield* authInvoke(this, "call", [name, args ?? {}, request]);
|
|
185
|
-
return nativeRecovery(result,
|
|
189
|
+
return nativeRecovery(result, name);
|
|
186
190
|
})));
|
|
187
191
|
}
|
|
188
192
|
/**
|
package/dist/oauth-handoff.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeBase64url } from "./bytes.js";
|
|
1
2
|
import { upwardStep } from "./upward.js";
|
|
2
3
|
import { authCall, authInvoke, authFlow, authStorage, registerAuth } from "./auth/native.js";
|
|
3
4
|
import { runEdge } from "./runtime/run.js";
|
|
@@ -58,8 +59,7 @@ function encodeSegment(value) {
|
|
|
58
59
|
function decodeSegment(value) {
|
|
59
60
|
if (!/^[A-Za-z0-9_-]+$/.test(value))
|
|
60
61
|
throw new Error("Invalid handoff encoding");
|
|
61
|
-
const
|
|
62
|
-
const decoded = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
|
|
62
|
+
const decoded = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(decodeBase64url(value));
|
|
63
63
|
if (encodeSegment(decoded) !== value)
|
|
64
64
|
throw new Error("Noncanonical handoff encoding");
|
|
65
65
|
return decoded;
|
package/dist/oauth-sealing.js
CHANGED
|
@@ -46,3 +46,13 @@ export async function deriveRequestStateKey(raw) {
|
|
|
46
46
|
info: new TextEncoder().encode("auth-elicitation"),
|
|
47
47
|
}, material, 256));
|
|
48
48
|
}
|
|
49
|
+
/** A non-extractable HMAC key for secret-bearing stored cache keys; nothing is stored. */
|
|
50
|
+
export async function deriveStorageDigestKey(raw) {
|
|
51
|
+
const material = await crypto.subtle.importKey("raw", new Uint8Array(raw), "HKDF", false, ["deriveKey"]);
|
|
52
|
+
return crypto.subtle.deriveKey({
|
|
53
|
+
name: "HKDF",
|
|
54
|
+
hash: "SHA-256",
|
|
55
|
+
salt: new TextEncoder().encode("connecta:storage-digest:v1"),
|
|
56
|
+
info: new TextEncoder().encode("cache-keys"),
|
|
57
|
+
}, material, { name: "HMAC", hash: "SHA-256", length: 256 }, false, ["sign"]);
|
|
58
|
+
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
// Private, request-free discovery facts. No provider, schema, handler or I/O dependency.
|
|
2
2
|
import { lexicalSearchContent, lexicalQueryTerms } from "./catalog.js";
|
|
3
|
+
/** Ready calls one vocabulary offers a search: only its top-ranked operation. */
|
|
4
|
+
const MAX_READY_PER_VOCABULARY = 1;
|
|
5
|
+
/**
|
|
6
|
+
* Normalized action words. A summary may supply these; every other query word
|
|
7
|
+
* names an object and must match the operation's resource identity.
|
|
8
|
+
*/
|
|
9
|
+
const ACTIONS = new Set(["get", "list", "count", "show", "create", "update", "delete"]);
|
|
10
|
+
/** Path tokens that version an API rather than name a resource. */
|
|
11
|
+
const VERSION = /^v?\d+$/;
|
|
3
12
|
const tools = new WeakMap();
|
|
4
13
|
const connectors = new WeakMap();
|
|
5
|
-
|
|
14
|
+
const synonymGroups = new WeakMap();
|
|
15
|
+
const OPERATION_VOCABULARY_BOUNDS = {
|
|
6
16
|
operations: 8192,
|
|
7
17
|
rowChars: 1024,
|
|
8
18
|
chars: 2_097_152,
|
|
@@ -28,20 +38,56 @@ const aliases = {
|
|
|
28
38
|
modify: "update",
|
|
29
39
|
remove: "delete",
|
|
30
40
|
};
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
const normal = (word) => aliases[word] ?? token(word);
|
|
42
|
+
const terms = (text) => [...new Set(lexicalQueryTerms(lexicalSearchContent(text)).map(normal))];
|
|
43
|
+
/** Resource words of a path segment or operation id, without version or action words. */
|
|
44
|
+
const identity = (text) => terms(text).filter((word) => !VERSION.test(word) && !ACTIONS.has(word));
|
|
45
|
+
function resourceOf(path, operationId) {
|
|
46
|
+
const segments = path.slice(0, OPERATION_VOCABULARY_BOUNDS.rowChars).split("/").map(identity);
|
|
47
|
+
const names = [...segments, identity(operationId.slice(0, OPERATION_VOCABULARY_BOUNDS.rowChars))].filter((name) => name.length > 0);
|
|
48
|
+
return { names, last: segments.at(-1) ?? [], terms: new Set(names.flat()) };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Attach a maintained provider's lexical synonym groups to its connector. Each
|
|
52
|
+
* group's words are interchangeable query terms for that connector's tool
|
|
53
|
+
* ranking and operation vocabulary; nothing is inferred.
|
|
54
|
+
*/
|
|
55
|
+
export function registerSearchSynonyms(connector, groups) {
|
|
56
|
+
synonymGroups.set(connector, groups);
|
|
57
|
+
}
|
|
58
|
+
/** A connector's groups, including those a provider stamp inherits. */
|
|
59
|
+
function groupsOf(connector) {
|
|
60
|
+
let target = connector;
|
|
61
|
+
while (target && !synonymGroups.has(target))
|
|
62
|
+
target = Object.getPrototypeOf(target);
|
|
63
|
+
return target ? synonymGroups.get(target) : undefined;
|
|
64
|
+
}
|
|
65
|
+
/** The other words of every group holding `word`, each word under `normalize`. */
|
|
66
|
+
function synonymsIn(groups, word, normalize = (raw) => raw) {
|
|
67
|
+
return groups.flatMap((group) => {
|
|
68
|
+
const words = group.map(normalize);
|
|
69
|
+
return words.includes(word) ? words.filter((other) => other !== word) : [];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Word to its declared synonyms for a connector's tool ranking, when its provider declares any. */
|
|
73
|
+
export function searchSynonyms(connector) {
|
|
74
|
+
const groups = groupsOf(connector);
|
|
75
|
+
return groups && ((word) => synonymsIn(groups, word));
|
|
76
|
+
}
|
|
34
77
|
/** Register only construction-authored compact summaries; never whole request/response schemas. */
|
|
35
78
|
export function registerOperationVocabulary(tool, rows) {
|
|
36
79
|
let postings;
|
|
37
80
|
const routes = [];
|
|
38
81
|
const cache = new Map();
|
|
39
|
-
tools.set(tool, (query, visible, safety, readOnly) => {
|
|
82
|
+
tools.set(tool, (query, visible, safety, readOnly, synonyms) => {
|
|
83
|
+
const none = { matched: false, ready: [] };
|
|
40
84
|
if (query.length > OPERATION_VOCABULARY_BOUNDS.queryChars)
|
|
41
|
-
return
|
|
85
|
+
return none;
|
|
42
86
|
const wanted = terms(query);
|
|
43
87
|
if (!wanted.length || wanted.length > OPERATION_VOCABULARY_BOUNDS.terms)
|
|
44
|
-
return
|
|
88
|
+
return none;
|
|
89
|
+
// Each wanted word matches through itself or any declared synonym.
|
|
90
|
+
const alternatives = wanted.map((word) => [word, ...(synonyms ? synonymsIn(synonyms, word, normal) : [])]);
|
|
45
91
|
if (!postings) {
|
|
46
92
|
postings = new Map();
|
|
47
93
|
let chars = 0;
|
|
@@ -52,7 +98,19 @@ export function registerOperationVocabulary(tool, rows) {
|
|
|
52
98
|
break;
|
|
53
99
|
chars += text.length;
|
|
54
100
|
const id = routes.length;
|
|
55
|
-
routes.push({
|
|
101
|
+
routes.push({
|
|
102
|
+
route: row.route,
|
|
103
|
+
write: row.write,
|
|
104
|
+
...(row.ready
|
|
105
|
+
? {
|
|
106
|
+
ready: {
|
|
107
|
+
path: row.ready.path,
|
|
108
|
+
summary: row.ready.summary.slice(0, OPERATION_VOCABULARY_BOUNDS.rowChars),
|
|
109
|
+
...resourceOf(row.ready.path, row.ready.operationId),
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
: {}),
|
|
113
|
+
});
|
|
56
114
|
for (const word of terms(text)) {
|
|
57
115
|
const hits = postings.get(word);
|
|
58
116
|
if (hits)
|
|
@@ -62,17 +120,20 @@ export function registerOperationVocabulary(tool, rows) {
|
|
|
62
120
|
}
|
|
63
121
|
}
|
|
64
122
|
}
|
|
65
|
-
const key =
|
|
123
|
+
const key = alternatives.map((words) => words.join("|")).join(" ");
|
|
66
124
|
let found = cache.get(key);
|
|
67
125
|
if (!found) {
|
|
68
|
-
const lists =
|
|
126
|
+
const lists = alternatives
|
|
127
|
+
.map((words) => [...new Set(words.flatMap((word) => postings.get(word) ?? []))])
|
|
128
|
+
.sort((a, b) => a.length - b.length);
|
|
69
129
|
const rest = lists.slice(1).map((list) => new Set(list));
|
|
70
130
|
found = lists[0].filter((id) => rest.every((set) => set.has(id)));
|
|
71
131
|
if (cache.size >= OPERATION_VOCABULARY_BOUNDS.cache)
|
|
72
132
|
cache.delete(cache.keys().next().value);
|
|
73
133
|
cache.set(key, found);
|
|
74
134
|
}
|
|
75
|
-
|
|
135
|
+
// The same endpoint, safety and visibility checks decide both answers.
|
|
136
|
+
const admitted = found.filter((id) => {
|
|
76
137
|
const row = routes[id];
|
|
77
138
|
if (row.write && readOnly)
|
|
78
139
|
return false;
|
|
@@ -80,6 +141,47 @@ export function registerOperationVocabulary(tool, rows) {
|
|
|
80
141
|
return false;
|
|
81
142
|
return row.route !== undefined && visible.has(row.route);
|
|
82
143
|
});
|
|
144
|
+
// A ready call must name what the query asks for. Every object word, or
|
|
145
|
+
// a declared synonym, belongs to a whole resource name whose words the
|
|
146
|
+
// query states in order. A word met only in a summary, or only as part of
|
|
147
|
+
// a compound name, can route a search, never stand in for the resource.
|
|
148
|
+
const objects = alternatives.filter((words) => !ACTIONS.has(words[0]));
|
|
149
|
+
const at = (name, start) => start + name.length <= objects.length && name.every((word, offset) => objects[start + offset].includes(word));
|
|
150
|
+
const covers = (names) => {
|
|
151
|
+
const covered = objects.map(() => false);
|
|
152
|
+
for (const name of names)
|
|
153
|
+
for (let start = 0; start < objects.length; start++)
|
|
154
|
+
if (at(name, start))
|
|
155
|
+
name.forEach((_, offset) => (covered[start + offset] = true));
|
|
156
|
+
return covered.every(Boolean);
|
|
157
|
+
};
|
|
158
|
+
const asked = new Set(alternatives.flat());
|
|
159
|
+
return {
|
|
160
|
+
matched: admitted.length > 0,
|
|
161
|
+
ready: objects.length
|
|
162
|
+
? admitted
|
|
163
|
+
.flatMap((id) => {
|
|
164
|
+
const { route, write, ready } = routes[id];
|
|
165
|
+
if (!ready || write || !route || !covers(ready.names))
|
|
166
|
+
return [];
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
path: ready.path,
|
|
170
|
+
summary: ready.summary,
|
|
171
|
+
route,
|
|
172
|
+
id,
|
|
173
|
+
// Relevance first: the last segment names the resource asked
|
|
174
|
+
// for, then the fewest resource words the query did not ask for.
|
|
175
|
+
last: Number(objects.some((_, start) => ready.last.length > 0 && at(ready.last, start))),
|
|
176
|
+
extra: [...ready.terms].filter((word) => !asked.has(word) && !ACTIONS.has(word)).length,
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
})
|
|
180
|
+
.sort((a, b) => b.last - a.last || a.extra - b.extra || a.path.length - b.path.length || a.id - b.id)
|
|
181
|
+
.slice(0, MAX_READY_PER_VOCABULARY)
|
|
182
|
+
.map(({ path, summary, route }) => ({ path, summary, route }))
|
|
183
|
+
: [],
|
|
184
|
+
};
|
|
83
185
|
});
|
|
84
186
|
}
|
|
85
187
|
/** Move private facts across the public ApiTool -> Connector construction boundary. */
|
|
@@ -91,11 +193,31 @@ export function registerConnectorVocabulary(connector, declarations) {
|
|
|
91
193
|
if (entries.length)
|
|
92
194
|
connectors.set(connector, new Map(entries));
|
|
93
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Read tools whose operation vocabulary matches every query word, and the
|
|
198
|
+
* fully determined reads among those operations, each through a visible read
|
|
199
|
+
* tool of `catalog`: the caller's view, after endpoint, connector, hidden-tool
|
|
200
|
+
* and grant filtering.
|
|
201
|
+
*/
|
|
94
202
|
export function operationVocabularyMatches(connector, catalog, query, safety, readOnly) {
|
|
95
203
|
const registered = connectors.get(connector);
|
|
204
|
+
const tools = [];
|
|
205
|
+
const ready = [];
|
|
96
206
|
if (!registered)
|
|
97
|
-
return
|
|
207
|
+
return { tools, ready };
|
|
98
208
|
const visible = new Set(catalog.map((tool) => tool.name));
|
|
99
|
-
|
|
100
|
-
|
|
209
|
+
const synonyms = groupsOf(connector);
|
|
210
|
+
for (const tool of catalog) {
|
|
211
|
+
const vocabulary = tool.classification === "read" ? registered.get(tool.name) : undefined;
|
|
212
|
+
const found = vocabulary?.(query, visible, safety, readOnly || connector.readOnly === true, synonyms);
|
|
213
|
+
if (!found?.matched)
|
|
214
|
+
continue;
|
|
215
|
+
tools.push(tool);
|
|
216
|
+
for (const { route, ...call } of found.ready) {
|
|
217
|
+
const target = catalog.find((candidate) => candidate.name === route);
|
|
218
|
+
if (target?.classification === "read")
|
|
219
|
+
ready.push({ ...call, tool: target });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { tools, ready };
|
|
101
223
|
}
|
|
@@ -8,7 +8,7 @@ type FailureStep = "MCP handshake" | "tools/list" | "tools/call" | "resources/re
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const OAUTH_ERROR_CODES: ReadonlySet<string>;
|
|
10
10
|
/** Inbound denial facts, never identity text or provider diagnostics. */
|
|
11
|
-
export type AuthDenialReason = "authentication_abandoned_saturated" | "authentication_busy" | "authentication_failed" | "authentication_provider_missing" | "authentication_unreachable" | "access_context_missing" | "access_context_unavailable" | "access_audience_invalid" | "access_identity_unavailable" | "access_authorization_header_rejected" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "token_identity_mismatch" | "organization_membership_denied" | "organization_lookup_failed" | "organization_lookup_busy" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "not_maintainer" | "pool_not_declared" | "not_pool_member";
|
|
11
|
+
export type AuthDenialReason = "authentication_abandoned_saturated" | "authentication_busy" | "authentication_failed" | "authentication_provider_missing" | "authentication_unreachable" | "access_context_missing" | "access_context_unavailable" | "access_audience_invalid" | "access_identity_unavailable" | "access_authorization_header_rejected" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "token_identity_mismatch" | "organization_membership_denied" | "organization_lookup_failed" | "organization_lookup_busy" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "ambiguous_pool" | "not_maintainer" | "pool_not_declared" | "not_pool_member";
|
|
12
12
|
/**
|
|
13
13
|
* Name `ctor`'s instances `label` in records and fixed text. Call once, at
|
|
14
14
|
* module scope, beside the class. An error whose prototype chain reaches no
|
|
@@ -110,7 +110,7 @@ export declare function failureRecord(subject: FailureSubject, failure?: unknown
|
|
|
110
110
|
* The events an operator log reports failures as. Closed: a new one is a
|
|
111
111
|
* reviewed line here, never a string assembled at the call site.
|
|
112
112
|
*/
|
|
113
|
-
export type FailureEvent = "Authentication admission refused" | "Cloudflare Access verification unavailable" | "operator_refused" | "Authentication released after its pending work became unreachable" | "negotiation cache read failed" | "negotiation cache write failed" | "pool tool grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "MCP agent endpoint request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "operator agent slots read failed" | "result paging unavailable" | "native catalog unavailable" | "native name collision" | "native tools/list refused a pool over the 1,000-tool native cap" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
|
|
113
|
+
export type FailureEvent = "Authentication admission refused" | "Cloudflare Access verification unavailable" | "operator_refused" | "Authentication released after its pending work became unreachable" | "negotiation cache read failed" | "negotiation cache write failed" | "discovery health read failed" | "discovery health write failed" | "pool tool grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "credential verification record failed" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "MCP agent endpoint request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "operator agent slots read failed" | "result paging unavailable" | "native catalog unavailable" | "native name collision" | "native tools/list refused a pool over the 1,000-tool native cap" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
|
|
114
114
|
/**
|
|
115
115
|
* Log one failure as `[connecta] <event>` and its record. A record
|
|
116
116
|
* `failureRecord` did not build is replaced, whatever its shape, by a fixed
|
package/dist/operator-record.js
CHANGED
|
@@ -1,52 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Web-API only — no node: imports here.
|
|
3
|
-
//
|
|
4
|
-
// Inbound identity-provider text never enters operator logs or status, even
|
|
5
|
-
// when it fits a grammar. Activity retains its separate typed actor fields.
|
|
6
|
-
//
|
|
7
|
-
// Threat model. INV-6 here covers records of calls and failures: logs,
|
|
8
|
-
// activity rows, status and statusFor, health, doctor output, and any native
|
|
9
|
-
// runtime output connecta triggers (workerd's console). Text a downstream
|
|
10
|
-
// server, HTTP API, or client authored, and anything derived from it, never
|
|
11
|
-
// reaches one as arbitrary text. Self-declared client name/version facts in
|
|
12
|
-
// activity use the bounded ASCII grammars in activityClientFact; invalid values are
|
|
13
|
-
// absent at recording, SQL write/read, and operator UI boundaries. Client
|
|
14
|
-
// capabilities and other metadata are never recorded. Every typed field
|
|
15
|
-
// connecta records is checked against a closed set or a grammar. Operator-
|
|
16
|
-
// authored code (custom connectors, plugin `status()`, decorators) is trusted
|
|
17
|
-
// to follow the contract, and connecta still checks
|
|
18
|
-
// the typed fields it records from it; deliberately adversarial operator code
|
|
19
|
-
// is out of scope.
|
|
20
|
-
//
|
|
21
|
-
// Catalog metadata a connector serves is configuration the operator chose to
|
|
22
|
-
// load, and agents already see it: a tool name that fits MCP's tool-name
|
|
23
|
-
// grammar, a description, and input and output schemas may appear in the
|
|
24
|
-
// authenticated operator page's catalog views, and such a name may be
|
|
25
|
-
// logged. A name outside the grammar is withheld everywhere operator-facing,
|
|
26
|
-
// the page included. Names containing C0, DEL, or C1 are dropped at catalog
|
|
27
|
-
// intake; spaces and non-ASCII names remain callable with withheld records.
|
|
28
|
-
//
|
|
29
|
-
// INV-6 says logs carry no arguments, results, code, or raw downstream error
|
|
30
|
-
// text. Filtering errors where they arise kept missing sources: a validator's
|
|
31
|
-
// diagnostic, a stream's TypeError, a JSON-RPC error the agent may see. So the
|
|
32
|
-
// rule is enforced here, at the sink. A failure reaches an operator only as
|
|
33
|
-
// the fields of a `FailureRecord`, which `failureRecord` alone builds: each
|
|
34
|
-
// one copied from an explicit list, checked against a constant table or a
|
|
35
|
-
// grammar, and never taken from an error's message, cause, stack, data,
|
|
36
|
-
// nested errors, or name. `logFailure` writes nothing else, and every status
|
|
37
|
-
// message about a failure comes from `describeFailure`
|
|
38
|
-
// (test/operator-record-sources.node.test.ts holds src/ to that).
|
|
39
|
-
//
|
|
40
|
-
// Provenance is by identity, never by shape. Text that merely looks like
|
|
41
|
-
// connecta's (an error named like one of its classes, a status sentence
|
|
42
|
-
// shaped like a record) carries no weight: a class label comes from the
|
|
43
|
-
// prototype chain, a status message from the status object connecta built.
|
|
44
|
-
//
|
|
45
|
-
// Agent-facing results are a different surface with a different rule: a
|
|
46
|
-
// downstream's own answer to a call may reach the agent that made it (see
|
|
47
|
-
// documentation/architecture.md, "Errors and records"). Core's sent-secrets
|
|
48
|
-
// boundary removes credentials used by the upstream request from those answers, including
|
|
49
|
-
// allowed diagnostics, tool results and nested error causes/data (INV-5).
|
|
1
|
+
// Payload-free operator failure records. See documentation/architecture.md, "Errors and records".
|
|
50
2
|
import { carryProviderFailure } from "./provider-call-facts.js";
|
|
51
3
|
import { followRedactedCopies, keepErrorName } from "./redaction-identity.js";
|
|
52
4
|
import { classificationCode, ConnectorCallError, NETWORK_ERROR_CODES, WithheldTextError, } from "./errors.js";
|
|
@@ -134,6 +86,7 @@ const AUTH_DENIAL_REASONS = new Set([
|
|
|
134
86
|
"email_domain_denied",
|
|
135
87
|
"gate_denied",
|
|
136
88
|
"gate_failed",
|
|
89
|
+
"ambiguous_pool",
|
|
137
90
|
"not_maintainer",
|
|
138
91
|
"pool_not_declared",
|
|
139
92
|
"not_pool_member",
|
|
@@ -25,8 +25,18 @@ export interface OperatorConnectorOverlay {
|
|
|
25
25
|
catalogAgeMs: number | null;
|
|
26
26
|
/** Latest visible call in the bounded activity window, or null when unknown. */
|
|
27
27
|
lastCall: OperatorLastCall | null;
|
|
28
|
+
/**
|
|
29
|
+
* Age of the stored status observation this row shows, when it came from
|
|
30
|
+
* storage rather than a live read. Its tools may be empty until a live read.
|
|
31
|
+
*/
|
|
32
|
+
observedAgeMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Browser-only: when that stored observation was made, in epoch
|
|
35
|
+
* milliseconds, fixed once as the row lands so its age keeps counting.
|
|
36
|
+
*/
|
|
37
|
+
observedAt?: number;
|
|
28
38
|
}
|
|
29
|
-
/** Whole connector access, or exact tool grants. `requireReadOnly` is always false since guarded grants were removed
|
|
39
|
+
/** Whole connector access, or exact tool grants. `requireReadOnly` is always false since guarded grants were removed. */
|
|
30
40
|
export interface OperatorGrant {
|
|
31
41
|
connectorId: string;
|
|
32
42
|
tools: "all" | Array<{
|
|
@@ -197,5 +207,5 @@ export interface OperatorUiContractV2 {
|
|
|
197
207
|
viewerKey: string;
|
|
198
208
|
};
|
|
199
209
|
}
|
|
200
|
-
/** The operator contract `GET /ui/api/config` serves; schema version 1 was removed
|
|
210
|
+
/** The operator contract `GET /ui/api/config` serves; schema version 1 was removed. */
|
|
201
211
|
export type OperatorUiContract = OperatorUiContractV2;
|