@zackbart/connecta 0.28.1 → 0.29.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 +788 -5
- package/README.md +62 -42
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +84 -35
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +664 -231
- package/documentation/auth.md +936 -546
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +120 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
package/dist/access-tokens.js
CHANGED
|
@@ -1,35 +1,27 @@
|
|
|
1
|
+
import { authorizationCredential, isMachineCredential } from "./inbound-credential.js";
|
|
2
|
+
import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
|
|
1
3
|
import { routeAccessTokens } from "./routes/access-tokens.js";
|
|
2
4
|
import { validIdentityReference } from "./identity.js";
|
|
5
|
+
import { accessTokenKeys } from "./storage/keys.js";
|
|
3
6
|
const TOKEN_PREFIX = "cta_";
|
|
4
7
|
const TOKEN_BYTES = 32;
|
|
5
8
|
const TOKEN_VALUE_RE = /^cta_[A-Za-z0-9_-]{43}$/;
|
|
6
|
-
const RECORD_PREFIX = "access-token:v1:record:";
|
|
7
|
-
const LOOKUP_PREFIX = "access-token:v1:lookup:";
|
|
8
9
|
const MAX_NAME_CHARACTERS = 80;
|
|
9
10
|
const DEFAULT_MAX_ACTIVE = 100;
|
|
10
11
|
const MAX_CONFIGURED_ACTIVE = 1_000;
|
|
11
|
-
const ACTIVE_KEY = "access-token:v1:active";
|
|
12
12
|
const ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
|
13
13
|
const encoder = new TextEncoder();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function lookupKey(hash) {
|
|
18
|
-
return `${LOOKUP_PREFIX}${hash}`;
|
|
19
|
-
}
|
|
14
|
+
const recordKey = accessTokenKeys.record;
|
|
15
|
+
const lookupKey = accessTokenKeys.lookup;
|
|
16
|
+
const ACTIVE_KEY = accessTokenKeys.active;
|
|
20
17
|
function bytesToBase64Url(bytes) {
|
|
21
18
|
let binary = "";
|
|
22
19
|
for (const byte of bytes)
|
|
23
20
|
binary += String.fromCharCode(byte);
|
|
24
|
-
return btoa(binary)
|
|
25
|
-
.replaceAll("+", "-")
|
|
26
|
-
.replaceAll("/", "_")
|
|
27
|
-
.replace(/=+$/u, "");
|
|
21
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
28
22
|
}
|
|
29
23
|
function bytesToHex(bytes) {
|
|
30
|
-
return [...bytes]
|
|
31
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
32
|
-
.join("");
|
|
24
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
33
25
|
}
|
|
34
26
|
async function hashToken(token) {
|
|
35
27
|
return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token))));
|
|
@@ -62,12 +54,10 @@ function parseRecord(raw) {
|
|
|
62
54
|
typeof value.createdAt !== "string" ||
|
|
63
55
|
!Number.isFinite(Date.parse(value.createdAt)) ||
|
|
64
56
|
typeof value.createdBy !== "string" ||
|
|
65
|
-
(value.principal !== undefined &&
|
|
66
|
-
!validIdentityReference(value.principal)) ||
|
|
57
|
+
(value.principal !== undefined && !validIdentityReference(value.principal)) ||
|
|
67
58
|
(value.revokedAt !== undefined &&
|
|
68
59
|
(typeof value.revokedAt !== "string" || !value.revokedAt || !Number.isFinite(Date.parse(value.revokedAt)))) ||
|
|
69
|
-
(value.revokedBy !== undefined &&
|
|
70
|
-
typeof value.revokedBy !== "string")) {
|
|
60
|
+
(value.revokedBy !== undefined && typeof value.revokedBy !== "string")) {
|
|
71
61
|
throw new Error("invalid token record");
|
|
72
62
|
}
|
|
73
63
|
return value;
|
|
@@ -119,18 +109,20 @@ export class AccessTokenManager {
|
|
|
119
109
|
maxActive;
|
|
120
110
|
constructor(storage, options = {}) {
|
|
121
111
|
this.storage = storage;
|
|
122
|
-
|
|
123
|
-
|
|
112
|
+
// Issuance claims capacity by compare-and-set and listing enumerates
|
|
113
|
+
// records; refuse a store without either here, not at the first create.
|
|
114
|
+
if (typeof storage?.list !== "function" || typeof storage.compareAndSet !== "function") {
|
|
115
|
+
throw new Error("accessTokens requires storage that implements list and compareAndSet " +
|
|
116
|
+
"(d1Storage, sqliteStorage, or memoryStorage)");
|
|
124
117
|
}
|
|
125
118
|
const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
|
|
126
|
-
if (!Number.isInteger(maxActive) ||
|
|
127
|
-
maxActive < 1 ||
|
|
128
|
-
maxActive > MAX_CONFIGURED_ACTIVE) {
|
|
119
|
+
if (!Number.isInteger(maxActive) || maxActive < 1 || maxActive > MAX_CONFIGURED_ACTIVE) {
|
|
129
120
|
throw new Error(`accessTokens.maxActive must be a whole number from 1 to ${MAX_CONFIGURED_ACTIVE}`);
|
|
130
121
|
}
|
|
131
122
|
this.maxActive = maxActive;
|
|
132
123
|
this.auth = {
|
|
133
124
|
kind: "access_token",
|
|
125
|
+
recognizesCredential: isMachineCredential,
|
|
134
126
|
activityActorNamespace: "connecta:access-tokens:v1",
|
|
135
127
|
activityActorLabel: async (id) => {
|
|
136
128
|
try {
|
|
@@ -153,7 +145,7 @@ export class AccessTokenManager {
|
|
|
153
145
|
return record;
|
|
154
146
|
}
|
|
155
147
|
async list() {
|
|
156
|
-
const keys = await this.storage.list(
|
|
148
|
+
const keys = await this.storage.list(accessTokenKeys.recordPrefix);
|
|
157
149
|
const records = await Promise.all(keys.map(async (key) => {
|
|
158
150
|
const raw = await this.storage.get(key);
|
|
159
151
|
return raw ? parseRecord(raw) : null;
|
|
@@ -165,9 +157,6 @@ export class AccessTokenManager {
|
|
|
165
157
|
}
|
|
166
158
|
async create(name, createdBy) {
|
|
167
159
|
const normalizedName = normalizeName(name);
|
|
168
|
-
if (!this.storage.compareAndSet) {
|
|
169
|
-
throw new Error("Creating access tokens requires atomic compareAndSet storage");
|
|
170
|
-
}
|
|
171
160
|
if (typeof createdBy !== "string" && !validIdentityReference(createdBy)) {
|
|
172
161
|
throw new Error("Invalid token owner");
|
|
173
162
|
}
|
|
@@ -184,12 +173,8 @@ export class AccessTokenManager {
|
|
|
184
173
|
tokenHash: hash,
|
|
185
174
|
tokenPrefix: token.slice(0, 12),
|
|
186
175
|
createdAt: new Date().toISOString(),
|
|
187
|
-
createdBy: typeof createdBy === "string"
|
|
188
|
-
|
|
189
|
-
: `${createdBy.namespace}:${createdBy.id}`,
|
|
190
|
-
...(typeof createdBy === "string"
|
|
191
|
-
? {}
|
|
192
|
-
: { principal: { ...createdBy } }),
|
|
176
|
+
createdBy: typeof createdBy === "string" ? createdBy : `${createdBy.namespace}:${createdBy.id}`,
|
|
177
|
+
...(typeof createdBy === "string" ? {} : { principal: { ...createdBy } }),
|
|
193
178
|
};
|
|
194
179
|
try {
|
|
195
180
|
await this.updateActive(record.id, true);
|
|
@@ -211,7 +196,7 @@ export class AccessTokenManager {
|
|
|
211
196
|
}
|
|
212
197
|
async rename(id, name) {
|
|
213
198
|
const normalized = normalizeName(name);
|
|
214
|
-
const record = await this.updateRecord(id, current => ({ ...current, name: normalized }));
|
|
199
|
+
const record = await this.updateRecord(id, (current) => ({ ...current, name: normalized }));
|
|
215
200
|
return record ? metadata(record) : null;
|
|
216
201
|
}
|
|
217
202
|
async revoke(id, revokedBy) {
|
|
@@ -221,11 +206,14 @@ export class AccessTokenManager {
|
|
|
221
206
|
// Remove admission first. A metadata failure cannot leave a successful
|
|
222
207
|
// revocation's lookup alive. Retry deletion even if already marked revoked.
|
|
223
208
|
await this.storage.delete(lookupKey(record.tokenHash));
|
|
224
|
-
const updated = await this.updateRecord(id, current => current.revokedAt
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
209
|
+
const updated = await this.updateRecord(id, (current) => current.revokedAt
|
|
210
|
+
? current
|
|
211
|
+
: {
|
|
212
|
+
...current,
|
|
213
|
+
revokedAt: new Date().toISOString(),
|
|
214
|
+
revokedBy,
|
|
215
|
+
});
|
|
216
|
+
await this.updateActive(id, false);
|
|
229
217
|
return updated ? metadata(updated) : null;
|
|
230
218
|
}
|
|
231
219
|
async updateRecord(id, update) {
|
|
@@ -239,45 +227,36 @@ export class AccessTokenManager {
|
|
|
239
227
|
if (record.id !== id)
|
|
240
228
|
throw new Error("Stored access token id mismatch");
|
|
241
229
|
const next = update(record);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
// Legacy adapters cannot create, so no late lookup writer exists.
|
|
250
|
-
// Revocation's deleted lookup still refuses a stale metadata write.
|
|
251
|
-
await this.storage.set(recordKey(id), JSON.stringify(next));
|
|
252
|
-
}
|
|
230
|
+
// A rename racing revocation must retain revokedAt, including when
|
|
231
|
+
// an in-flight create has not yet published its lookup.
|
|
232
|
+
if (!(await this.storage.compareAndSet(recordKey(id), raw, JSON.stringify(next))))
|
|
233
|
+
continue;
|
|
253
234
|
return next;
|
|
254
235
|
}
|
|
255
236
|
throw new Error("Access token metadata is busy; retry the operation");
|
|
256
237
|
}
|
|
257
238
|
async updateActive(id, adding) {
|
|
258
|
-
const cas = this.storage.compareAndSet;
|
|
259
|
-
if (!cas)
|
|
260
|
-
throw new Error("Creating access tokens requires atomic compareAndSet storage");
|
|
261
239
|
for (let attempt = 0; attempt < 32; attempt++) {
|
|
262
240
|
const raw = await this.storage.get(ACTIVE_KEY);
|
|
263
241
|
const ids = raw === null
|
|
264
|
-
? (await this.list()).filter(token => !token.revokedAt).map(token => token.id)
|
|
242
|
+
? (await this.list()).filter((token) => !token.revokedAt).map((token) => token.id)
|
|
265
243
|
: JSON.parse(raw);
|
|
266
|
-
if (!Array.isArray(ids) ||
|
|
244
|
+
if (!Array.isArray(ids) ||
|
|
245
|
+
!ids.every((value) => typeof value === "string" && ID_RE.test(value)) ||
|
|
246
|
+
new Set(ids).size !== ids.length) {
|
|
267
247
|
throw new Error("Stored access token capacity is invalid");
|
|
268
248
|
}
|
|
269
249
|
if (adding && ids.length >= this.maxActive)
|
|
270
250
|
throw new Error(`This deployment already has the maximum of ${this.maxActive} active access tokens`);
|
|
271
|
-
const next = adding ? [...ids, id] : ids.filter(value => value !== id);
|
|
272
|
-
if (await
|
|
251
|
+
const next = adding ? [...ids, id] : ids.filter((value) => value !== id);
|
|
252
|
+
if (await this.storage.compareAndSet(ACTIVE_KEY, raw, JSON.stringify(next)))
|
|
273
253
|
return;
|
|
274
254
|
}
|
|
275
255
|
throw new Error("Access token capacity is busy; retry the operation");
|
|
276
256
|
}
|
|
277
257
|
async authorize(request) {
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
const token = match?.[1];
|
|
258
|
+
const credential = authorizationCredential(request);
|
|
259
|
+
const token = credential.kind === "bearer" ? credential.token : undefined;
|
|
281
260
|
if (!token || !TOKEN_VALUE_RE.test(token))
|
|
282
261
|
return unauthorized();
|
|
283
262
|
const hash = await hashToken(token);
|
|
@@ -298,8 +277,19 @@ export class AccessTokenManager {
|
|
|
298
277
|
};
|
|
299
278
|
}
|
|
300
279
|
}
|
|
280
|
+
/** The closed options accessTokens() accepts; see `assertKnownOptions`. */
|
|
281
|
+
const ACCESS_TOKENS_OPTIONS = optionsOf()(keys("maxActive"));
|
|
301
282
|
/** Opt in using the same storage namespace that held the v0.23 records. */
|
|
302
283
|
export function accessTokens(storage, options = {}) {
|
|
284
|
+
options = assertKnownOptions(options, "accessTokens()", ACCESS_TOKENS_OPTIONS);
|
|
303
285
|
const manager = new AccessTokenManager(storage, options);
|
|
304
|
-
|
|
286
|
+
const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
|
|
287
|
+
const optionSources = Object.freeze({
|
|
288
|
+
maxActive: options.maxActive === undefined ? "default" : "config",
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
auth: manager.auth,
|
|
292
|
+
handle: (context) => routeAccessTokens(context, manager),
|
|
293
|
+
describe: () => ({ maxActive, optionSources }),
|
|
294
|
+
};
|
|
305
295
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Weak ownership keeps the grouping context within the request's lifetime.
|
|
2
|
+
const requests = new WeakMap();
|
|
3
|
+
export function bindActivityRequest(scope, context) {
|
|
4
|
+
requests.set(scope, context);
|
|
5
|
+
}
|
|
6
|
+
export function activityRequest(scope) {
|
|
7
|
+
return scope ? requests.get(scope) : undefined;
|
|
8
|
+
}
|
package/dist/activity.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ActivityModule } from "./module-contracts.js";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import { type ClassificationCode } from "./errors.js";
|
|
3
|
+
import type { Logger } from "./types.js";
|
|
4
|
+
/** One policy for the record builder, SQL write/read boundaries, and UI. */
|
|
5
|
+
export declare function activityClientFact(value: unknown, field: "name" | "version"): string | undefined;
|
|
6
|
+
/** Stored package versions use a bounded release-version grammar, including prereleases. */
|
|
7
|
+
export declare function activityPackageVersion(value: unknown): string | undefined;
|
|
8
|
+
export type ActivityCallSource = "catalog_refresh" | "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
|
|
4
9
|
export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled" | "paused" | "approved";
|
|
5
10
|
/** Read-only history: how much one `resume_execution` approved (#672). */
|
|
6
11
|
export type ActivityApproval = "call" | "tool";
|
|
@@ -28,11 +33,37 @@ export interface ActivityActor {
|
|
|
28
33
|
* by construction. Deployments that need a safe human summary can add a
|
|
29
34
|
* separate, explicit connector-level feature later.
|
|
30
35
|
*/
|
|
36
|
+
export interface ActivityCatalogChange {
|
|
37
|
+
kind: "catalog_changed";
|
|
38
|
+
addedTools: number;
|
|
39
|
+
removedTools: number;
|
|
40
|
+
changedTools: number;
|
|
41
|
+
}
|
|
42
|
+
/** Integer metadata is checked at every write and read boundary. */
|
|
43
|
+
export declare function activityCount(value: unknown): number | undefined;
|
|
44
|
+
export declare function activityBehaviorFacts(event: {
|
|
45
|
+
classification?: unknown;
|
|
46
|
+
resultBytes?: unknown;
|
|
47
|
+
kind?: unknown;
|
|
48
|
+
drift?: unknown;
|
|
49
|
+
pool?: unknown;
|
|
50
|
+
actorBasis?: unknown;
|
|
51
|
+
}): Pick<ToolCallActivityEvent, "classification" | "resultBytes" | "kind" | "drift" | "pool" | "actorBasis">;
|
|
31
52
|
export interface ToolCallActivityEvent {
|
|
32
53
|
schemaVersion: 1;
|
|
33
54
|
id: string;
|
|
34
55
|
occurredAt: string;
|
|
35
56
|
requestId: string;
|
|
57
|
+
/** Absent on legacy tool rows. Catalog changes use the same paging envelope. */
|
|
58
|
+
kind?: "catalog_drift";
|
|
59
|
+
drift?: ActivityCatalogChange;
|
|
60
|
+
/** The final registry verdict captured when resolution succeeded. */
|
|
61
|
+
classification?: "read" | "write";
|
|
62
|
+
/** UTF-8 bytes of the downstream value before result paging or truncation. */
|
|
63
|
+
resultBytes?: number;
|
|
64
|
+
pool?: string;
|
|
65
|
+
/** Personal ownership is provable only for rows recorded from the admitted principal. */
|
|
66
|
+
actorBasis?: "principal";
|
|
36
67
|
actor: ActivityActor;
|
|
37
68
|
connectorId: string;
|
|
38
69
|
toolName: string;
|
|
@@ -55,28 +86,13 @@ export interface ToolCallActivityEvent {
|
|
|
55
86
|
* here — the approved arguments were never recorded.
|
|
56
87
|
*/
|
|
57
88
|
approval?: ActivityApproval;
|
|
89
|
+
/** Real build version. Absent only in history written before telemetry existed. */
|
|
90
|
+
packageVersion?: string;
|
|
58
91
|
serverName: string;
|
|
59
92
|
serverVersion: string;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* A downstream catalog moved away from the manifest a release reviewed.
|
|
64
|
-
*
|
|
65
|
-
* Four integers and an id: the type has nowhere to put a tool name, a schema,
|
|
66
|
-
* an argument, a result, or a downstream error string, which is the same
|
|
67
|
-
* construction guarantee `ToolCallActivityEvent` makes. Which tool drifted is
|
|
68
|
-
* deliberately absent — the runtime reports that something did, and the
|
|
69
|
-
* maintainer-run check with a live catalog in front of it names names
|
|
70
|
-
* ([#343](https://github.com/zackbart/connecta/issues/343),
|
|
71
|
-
* [#351](https://github.com/zackbart/connecta/issues/351)).
|
|
72
|
-
*/
|
|
73
|
-
export interface CatalogDriftActivityEvent extends CatalogDriftCounts {
|
|
74
|
-
schemaVersion: 1;
|
|
75
|
-
id: string;
|
|
76
|
-
occurredAt: string;
|
|
77
|
-
connectorId: string;
|
|
78
|
-
serverName: string;
|
|
79
|
-
serverVersion: string;
|
|
93
|
+
/** Self-declared client identity checked by activityClientFact; invalid facts are absent. */
|
|
94
|
+
clientName?: string;
|
|
95
|
+
clientVersion?: string;
|
|
80
96
|
deploymentId?: string;
|
|
81
97
|
}
|
|
82
98
|
export interface ActivityPage {
|
|
@@ -97,13 +113,6 @@ export interface ActivityReadPage {
|
|
|
97
113
|
/** Write-only deployments can implement only this small, vendor-neutral seam. */
|
|
98
114
|
export interface ActivitySink {
|
|
99
115
|
record(event: ToolCallActivityEvent): void | Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* Optional catalog-drift channel. Optional rather than a widened `record`,
|
|
102
|
-
* because a store written before drift existed already decided what a row
|
|
103
|
-
* looks like: a sink that does not implement this simply never hears about
|
|
104
|
-
* drift, and the operator still reads the same counts from connector status.
|
|
105
|
-
*/
|
|
106
|
-
recordCatalogDrift?(event: CatalogDriftActivityEvent): void | Promise<void>;
|
|
107
116
|
}
|
|
108
117
|
/** Optional read side used by Connecta's authenticated Activity UI. */
|
|
109
118
|
export interface ActivityReader {
|
|
@@ -114,6 +123,14 @@ export interface ActivityReader {
|
|
|
114
123
|
}
|
|
115
124
|
export interface ActivityStore extends ActivitySink {
|
|
116
125
|
list?: ActivityReader["list"];
|
|
126
|
+
/**
|
|
127
|
+
* The adapter and its retention, for `Connecta.describeConfig()`. Kinds
|
|
128
|
+
* other than the shipped `"d1"` and `"sqlite"` are described as `"custom"`.
|
|
129
|
+
*/
|
|
130
|
+
describe?(): {
|
|
131
|
+
kind: string;
|
|
132
|
+
retentionDays?: number;
|
|
133
|
+
};
|
|
117
134
|
}
|
|
118
135
|
/** Reader implementations throw this for an opaque cursor they cannot decode. */
|
|
119
136
|
export declare class InvalidActivityCursorError extends Error {
|
|
@@ -122,17 +139,21 @@ export declare class InvalidActivityCursorError extends Error {
|
|
|
122
139
|
}
|
|
123
140
|
export type ActivityReadGate = (actor: ActivityActor) => boolean | Promise<boolean>;
|
|
124
141
|
/**
|
|
125
|
-
*
|
|
126
|
-
* a
|
|
127
|
-
* did, so it carries no actor and no request id to attribute it to one.
|
|
142
|
+
* Default destination for catalog changes. Request attribution overrides it
|
|
143
|
+
* when a publication belongs to an admitted caller.
|
|
128
144
|
*/
|
|
129
145
|
export interface CatalogDriftActivityContext {
|
|
130
|
-
|
|
146
|
+
recordChange?: typeof recordCatalogChangeActivity;
|
|
147
|
+
defer?: (promise: Promise<unknown>) => void;
|
|
131
148
|
sink: ActivitySink;
|
|
132
149
|
serverInfo: {
|
|
133
150
|
name: string;
|
|
134
151
|
version: string;
|
|
135
152
|
};
|
|
153
|
+
clientInfo?: {
|
|
154
|
+
name: string;
|
|
155
|
+
version: string;
|
|
156
|
+
};
|
|
136
157
|
deploymentId?: string;
|
|
137
158
|
logger: Logger;
|
|
138
159
|
}
|
|
@@ -141,34 +162,47 @@ export interface ActivityRequestContext {
|
|
|
141
162
|
recordTool?: typeof recordToolActivity | undefined;
|
|
142
163
|
sink: ActivitySink;
|
|
143
164
|
actor: ActivityActor;
|
|
165
|
+
/** Personal rows identify their admitted owner in the existing actor fields. */
|
|
166
|
+
principalActor?: ActivityActor;
|
|
144
167
|
requestId: string;
|
|
168
|
+
pool?: string;
|
|
145
169
|
serverInfo: {
|
|
146
170
|
name: string;
|
|
147
171
|
version: string;
|
|
148
172
|
};
|
|
173
|
+
clientInfo?: {
|
|
174
|
+
name: string;
|
|
175
|
+
version: string;
|
|
176
|
+
};
|
|
149
177
|
deploymentId?: string;
|
|
150
178
|
defer?: (promise: Promise<unknown>) => void;
|
|
151
179
|
logger: Logger;
|
|
152
180
|
}
|
|
153
|
-
export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "
|
|
181
|
+
export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "friction" | "classification" | "resultBytes" | "kind" | "drift"> & {
|
|
182
|
+
/**
|
|
183
|
+
* A code connecta assigns. Rows written by older deployments may hold
|
|
184
|
+
* others, so the stored event keeps `string`; a new row never does.
|
|
185
|
+
*/
|
|
186
|
+
errorCode?: ClassificationCode;
|
|
187
|
+
personal?: boolean;
|
|
188
|
+
privateCatalog?: boolean;
|
|
189
|
+
};
|
|
154
190
|
/**
|
|
155
191
|
* Best-effort by design: activity storage can never change a tool result.
|
|
156
192
|
* Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
|
|
157
193
|
* Engine complete inline; Node promises remain detached from the response.
|
|
158
194
|
*/
|
|
159
195
|
export declare function recordToolActivity(context: ActivityRequestContext | undefined, input: ActivityEventInput): void;
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
* write: a store that is down, or a sink that never implemented the channel,
|
|
163
|
-
* can never change what a refresh returned to the caller who paid for it.
|
|
164
|
-
*/
|
|
165
|
-
export declare function recordCatalogDriftActivity(context: CatalogDriftActivityContext | undefined, input: {
|
|
196
|
+
/** A discrete catalog change, with no catalog names, descriptions or schemas. */
|
|
197
|
+
export declare function recordCatalogChangeActivity(context: CatalogDriftActivityContext | undefined, input: {
|
|
166
198
|
connectorId: string;
|
|
167
|
-
|
|
199
|
+
drift: ActivityCatalogChange;
|
|
200
|
+
personal?: boolean;
|
|
201
|
+
privateCatalog?: boolean;
|
|
202
|
+
}, request?: ActivityRequestContext): void;
|
|
168
203
|
export interface ActivityHistoryOptions {
|
|
169
204
|
store: ActivityStore;
|
|
170
205
|
deploymentId?: string;
|
|
171
206
|
readGate?: ActivityReadGate;
|
|
172
207
|
}
|
|
173
|
-
/** Attach payload-free history without changing tool results on store failure. */
|
|
174
208
|
export declare function activityHistory(options: ActivityHistoryOptions): ActivityModule;
|
package/dist/activity.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { CONNECTA_VERSION } from "./version.js";
|
|
2
|
+
import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
|
|
1
3
|
import { routeActivity } from "./routes/activity.js";
|
|
2
|
-
import { boundedEchoText } from "./errors.js";
|
|
4
|
+
import { boundedEchoText, classificationCode } from "./errors.js";
|
|
5
|
+
import { failureRecord, logFailure } from "./operator-record.js";
|
|
3
6
|
/**
|
|
4
7
|
* How long an identity field may be before the store stops believing it.
|
|
5
8
|
*
|
|
@@ -15,10 +18,72 @@ import { boundedEchoText } from "./errors.js";
|
|
|
15
18
|
* 40,000 would. The `…` marker keeps a clamped value from reading as a real one.
|
|
16
19
|
*/
|
|
17
20
|
const MAX_ACTIVITY_NAME_BYTES = 128;
|
|
21
|
+
/**
|
|
22
|
+
* Client facts use bounded ASCII grammars, never truncation or escaping.
|
|
23
|
+
* Names are 1 to 64 characters: alphanumeric or @ first (scoped package names
|
|
24
|
+
* such as `@modelcontextprotocol/inspector`), then alphanumerics, space, dot,
|
|
25
|
+
* underscore, @, /, + or -. Versions are 1 to 32 characters: alphanumeric
|
|
26
|
+
* first, then alphanumerics, dot, underscore, + or -. Prototype-key names are
|
|
27
|
+
* withheld too. The end assertion rejects even a final CR/LF, unlike `$`.
|
|
28
|
+
*/
|
|
29
|
+
const CLIENT_FACT_GRAMMARS = {
|
|
30
|
+
name: /^[A-Za-z0-9@][A-Za-z0-9 ._@/+-]{0,63}(?![\s\S])/,
|
|
31
|
+
version: /^[A-Za-z0-9][A-Za-z0-9._+-]{0,31}(?![\s\S])/,
|
|
32
|
+
};
|
|
33
|
+
const RESERVED_CLIENT_FACTS = new Set(["__proto__", "constructor", "prototype"]);
|
|
34
|
+
/** One policy for the record builder, SQL write/read boundaries, and UI. */
|
|
35
|
+
export function activityClientFact(value, field) {
|
|
36
|
+
return typeof value === "string" && !RESERVED_CLIENT_FACTS.has(value) && CLIENT_FACT_GRAMMARS[field].test(value)
|
|
37
|
+
? value
|
|
38
|
+
: undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Stored package versions use a bounded release-version grammar, including prereleases. */
|
|
41
|
+
export function activityPackageVersion(value) {
|
|
42
|
+
return typeof value === "string" &&
|
|
43
|
+
value.length <= 128 &&
|
|
44
|
+
/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?(?![\s\S])/.test(value)
|
|
45
|
+
? value
|
|
46
|
+
: undefined;
|
|
47
|
+
}
|
|
18
48
|
/** Two names and the dot between them. */
|
|
19
49
|
const MAX_ACTIVITY_ADDRESS_BYTES = MAX_ACTIVITY_NAME_BYTES * 2 + 1;
|
|
20
50
|
import { agentFrictionForCode } from "./activity-friction.js";
|
|
21
51
|
export { agentFrictionForCode } from "./activity-friction.js";
|
|
52
|
+
/** Integer metadata is checked at every write and read boundary. */
|
|
53
|
+
export function activityCount(value) {
|
|
54
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
55
|
+
}
|
|
56
|
+
export function activityBehaviorFacts(event) {
|
|
57
|
+
const classification = event.classification === "read" || event.classification === "write" ? event.classification : undefined;
|
|
58
|
+
const resultBytes = activityCount(event.resultBytes);
|
|
59
|
+
const change = event.drift;
|
|
60
|
+
const drift = event.kind === "catalog_drift" &&
|
|
61
|
+
change?.kind === "catalog_changed" &&
|
|
62
|
+
activityCount(change.addedTools) !== undefined &&
|
|
63
|
+
activityCount(change.removedTools) !== undefined &&
|
|
64
|
+
activityCount(change.changedTools) !== undefined
|
|
65
|
+
? {
|
|
66
|
+
kind: "catalog_changed",
|
|
67
|
+
addedTools: change.addedTools,
|
|
68
|
+
removedTools: change.removedTools,
|
|
69
|
+
changedTools: change.changedTools,
|
|
70
|
+
}
|
|
71
|
+
: undefined;
|
|
72
|
+
// Pool names come from matched deployment config, whose grammar has no size
|
|
73
|
+
// ceiling. Invalid stored scope stays withheld rather than becoming root scope.
|
|
74
|
+
const pool = event.pool === undefined || event.pool === null
|
|
75
|
+
? undefined
|
|
76
|
+
: typeof event.pool === "string" && /^[a-z0-9_-]+(?![\s\S])/.test(event.pool)
|
|
77
|
+
? event.pool
|
|
78
|
+
: "<withheld>";
|
|
79
|
+
return {
|
|
80
|
+
...(event.actorBasis === "principal" ? { actorBasis: "principal" } : {}),
|
|
81
|
+
...(classification !== undefined ? { classification } : {}),
|
|
82
|
+
...(resultBytes !== undefined ? { resultBytes } : {}),
|
|
83
|
+
...(drift ? { kind: "catalog_drift", drift } : {}),
|
|
84
|
+
...(pool !== undefined ? { pool } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
22
87
|
/** Reader implementations throw this for an opaque cursor they cannot decode. */
|
|
23
88
|
export class InvalidActivityCursorError extends Error {
|
|
24
89
|
name = "InvalidActivityCursorError";
|
|
@@ -36,13 +101,24 @@ export function recordToolActivity(context, input) {
|
|
|
36
101
|
return;
|
|
37
102
|
// A caller-supplied class wins because it knows something the code table
|
|
38
103
|
// cannot: friction that belongs to a call which did not fail.
|
|
39
|
-
|
|
104
|
+
// Checked here as well as by type: a handler can put any string in a
|
|
105
|
+
// `ConnectorCallError`'s code, and a downstream's own code is not recorded.
|
|
106
|
+
const errorCode = classificationCode(input.errorCode);
|
|
107
|
+
const friction = input.friction ?? agentFrictionForCode(errorCode);
|
|
108
|
+
// Re-check client grammars at the record boundary; never spread the envelope.
|
|
109
|
+
const clientName = activityClientFact(context.clientInfo?.name, "name");
|
|
110
|
+
const clientVersion = activityClientFact(context.clientInfo?.version, "version");
|
|
40
111
|
const event = {
|
|
41
112
|
schemaVersion: 1,
|
|
42
113
|
id: crypto.randomUUID(),
|
|
43
114
|
occurredAt: new Date().toISOString(),
|
|
44
115
|
requestId: context.requestId,
|
|
45
|
-
actor: context.actor,
|
|
116
|
+
actor: input.personal || input.privateCatalog ? (context.principalActor ?? { kind: context.actor.kind }) : context.actor,
|
|
117
|
+
...activityBehaviorFacts({
|
|
118
|
+
...input,
|
|
119
|
+
pool: context.pool,
|
|
120
|
+
actorBasis: input.privateCatalog || (input.personal && context.principalActor) ? "principal" : undefined,
|
|
121
|
+
}),
|
|
46
122
|
connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
|
|
47
123
|
toolName: boundedEchoText(input.toolName, MAX_ACTIVITY_NAME_BYTES),
|
|
48
124
|
address: boundedEchoText(input.address, MAX_ACTIVITY_ADDRESS_BYTES),
|
|
@@ -50,13 +126,14 @@ export function recordToolActivity(context, input) {
|
|
|
50
126
|
outcome: input.outcome,
|
|
51
127
|
durationMs: Math.max(0, Math.trunc(input.durationMs)),
|
|
52
128
|
attempts: Math.max(1, Math.trunc(input.attempts)),
|
|
53
|
-
...(
|
|
129
|
+
...(errorCode ? { errorCode } : {}),
|
|
54
130
|
...(friction ? { friction } : {}),
|
|
131
|
+
packageVersion: CONNECTA_VERSION,
|
|
55
132
|
serverName: context.serverInfo.name,
|
|
56
133
|
serverVersion: context.serverInfo.version,
|
|
57
|
-
...(
|
|
58
|
-
|
|
59
|
-
|
|
134
|
+
...(clientName !== undefined ? { clientName } : {}),
|
|
135
|
+
...(clientVersion !== undefined ? { clientVersion } : {}),
|
|
136
|
+
...(context.deploymentId ? { deploymentId: context.deploymentId } : {}),
|
|
60
137
|
};
|
|
61
138
|
try {
|
|
62
139
|
const result = context.sink.record(event);
|
|
@@ -64,51 +141,42 @@ export function recordToolActivity(context, input) {
|
|
|
64
141
|
return;
|
|
65
142
|
}
|
|
66
143
|
const pending = Promise.resolve(result).catch((error) => {
|
|
67
|
-
context.logger
|
|
144
|
+
logFailure(context.logger, "activity record failed", failureRecord({}, error));
|
|
68
145
|
});
|
|
69
146
|
if (context.defer)
|
|
70
147
|
context.defer(pending);
|
|
71
148
|
}
|
|
72
149
|
catch (error) {
|
|
73
|
-
context.logger
|
|
150
|
+
logFailure(context.logger, "activity record failed", failureRecord({}, error));
|
|
74
151
|
}
|
|
75
152
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* can never change what a refresh returned to the caller who paid for it.
|
|
80
|
-
*/
|
|
81
|
-
export function recordCatalogDriftActivity(context, input) {
|
|
82
|
-
if (!context?.sink.recordCatalogDrift)
|
|
153
|
+
/** A discrete catalog change, with no catalog names, descriptions or schemas. */
|
|
154
|
+
export function recordCatalogChangeActivity(context, input, request) {
|
|
155
|
+
if (!context)
|
|
83
156
|
return;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
void Promise.resolve(result).catch((error) => {
|
|
103
|
-
context.logger.warn("[connecta] catalog drift record failed", error);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
context.logger.warn("[connecta] catalog drift record failed", error);
|
|
108
|
-
}
|
|
157
|
+
recordToolActivity(request ?? {
|
|
158
|
+
...context,
|
|
159
|
+
actor: { kind: "system" },
|
|
160
|
+
requestId: crypto.randomUUID(),
|
|
161
|
+
}, {
|
|
162
|
+
connectorId: input.connectorId,
|
|
163
|
+
toolName: "<catalog>",
|
|
164
|
+
address: `${input.connectorId}.<catalog>`,
|
|
165
|
+
source: "catalog_refresh",
|
|
166
|
+
outcome: "success",
|
|
167
|
+
durationMs: 0,
|
|
168
|
+
attempts: 1,
|
|
169
|
+
kind: "catalog_drift",
|
|
170
|
+
drift: input.drift,
|
|
171
|
+
...(input.personal ? { personal: true } : {}),
|
|
172
|
+
...(input.privateCatalog ? { privateCatalog: true } : {}),
|
|
173
|
+
});
|
|
109
174
|
}
|
|
110
175
|
/** Attach payload-free history without changing tool results on store failure. */
|
|
176
|
+
/** The closed options activityHistory() accepts; see `assertKnownOptions`. */
|
|
177
|
+
const ACTIVITY_HISTORY_OPTIONS = optionsOf()(keys("store", "deploymentId", "readGate"));
|
|
111
178
|
export function activityHistory(options) {
|
|
179
|
+
options = assertKnownOptions(options, "activityHistory()", ACTIVITY_HISTORY_OPTIONS);
|
|
112
180
|
if (!options || typeof options.store?.record !== "function") {
|
|
113
181
|
throw new Error("activityHistory.store must implement record(event)");
|
|
114
182
|
}
|
|
@@ -116,6 +184,6 @@ export function activityHistory(options) {
|
|
|
116
184
|
...options,
|
|
117
185
|
handle: routeActivity,
|
|
118
186
|
recordTool: recordToolActivity,
|
|
119
|
-
|
|
187
|
+
recordChange: recordCatalogChangeActivity,
|
|
120
188
|
};
|
|
121
189
|
}
|