@zackbart/connecta 0.24.3 → 0.25.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/AGENTS.md +58 -30
- package/CHANGELOG.md +317 -1
- package/README.md +5 -6
- package/dist/advertised-schema.js +53 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +141 -105
- package/dist/catalog.js +258 -33
- package/dist/concurrency.js +24 -23
- package/dist/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +3 -3
- package/dist/connectors/api.js +10 -2
- package/dist/connectors/guarded-fetch.js +34 -4
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +325 -245
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/errors.d.ts +11 -3
- package/dist/errors.js +2 -1
- package/dist/execute.js +363 -257
- package/dist/executor-admission.js +67 -50
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +251 -210
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +63 -21
- package/dist/invocation.js +324 -285
- package/dist/meta-tools.js +304 -114
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +46 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +109 -2
- package/dist/program-source.js +205 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +85 -12
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +70 -5
- package/dist/providers/stripe.d.ts +10 -3
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +12 -2
- package/dist/registry.d.ts +13 -5
- package/dist/registry.js +317 -258
- package/dist/routes/activity.js +120 -137
- package/dist/routes/credentials.js +174 -163
- package/dist/routes/mcp.js +129 -70
- package/dist/routes/oauth-management.js +60 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +82 -0
- package/dist/routes/shared.d.ts +2 -0
- package/dist/routes/ui-data.js +210 -0
- package/dist/routes/ui.js +64 -32
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +111 -89
- package/dist/skills.js +1 -1
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +17 -47
- package/dist/types.d.ts +64 -7
- package/dist/ui.d.ts +16 -2
- package/dist/ui.js +68 -176
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +368 -182
- package/documentation/auth.md +277 -176
- package/documentation/code-mode.md +479 -329
- package/documentation/meta-tools.md +494 -417
- package/ethos.md +44 -47
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +62 -14
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +18 -21
- package/examples/worker/wrangler.jsonc +16 -4
- package/package.json +11 -4
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -92
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -151
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
- package/dist/tool-safety.d.ts +0 -9
- package/documentation/call-admission.md +0 -158
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -431
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -729
- package/documentation/request-admission.md +0 -204
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -254
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -768
- package/documentation/vercel.md +0 -241
package/dist/routes/activity.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Duration, Effect } from "effect";
|
|
1
2
|
import { InvalidActivityCursorError, } from "../activity.js";
|
|
2
|
-
import {
|
|
3
|
+
import { authorized, refuse, serveOperator } from "./operator.js";
|
|
4
|
+
import { activityActorNamespace, privateJson, } from "./shared.js";
|
|
3
5
|
const ACTIVITY_LABEL_CONCURRENCY = 8;
|
|
4
6
|
const ACTIVITY_LABEL_PAGE_BUDGET_MS = 1_500;
|
|
5
7
|
const ACTIVITY_LABEL_MAX_LENGTH = 160;
|
|
@@ -11,162 +13,143 @@ function cleanActivityActorLabel(value) {
|
|
|
11
13
|
return undefined;
|
|
12
14
|
return Array.from(compact).slice(0, ACTIVITY_LABEL_MAX_LENGTH).join("");
|
|
13
15
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
return await Promise.race([
|
|
18
|
-
Promise.resolve(hook(id))
|
|
19
|
-
.then(cleanActivityActorLabel)
|
|
20
|
-
.catch(() => undefined),
|
|
21
|
-
new Promise((resolve) => {
|
|
22
|
-
timer = setTimeout(resolve, budgetMs);
|
|
23
|
-
}),
|
|
24
|
-
]);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
if (timer !== undefined)
|
|
31
|
-
clearTimeout(timer);
|
|
32
|
-
}
|
|
16
|
+
function actorKey(actor) {
|
|
17
|
+
return JSON.stringify([actor.kind, actor.namespace, actor.id]);
|
|
33
18
|
}
|
|
34
19
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
20
|
+
* One lookup per distinct actor on the page, each against the single provider
|
|
21
|
+
* that owns the actor's directory. An actor no provider can claim unambiguously
|
|
22
|
+
* gets no lookup at all.
|
|
38
23
|
*/
|
|
39
|
-
|
|
24
|
+
function labelLookups(page, auth) {
|
|
40
25
|
const identities = new Map();
|
|
41
26
|
for (const event of page.events) {
|
|
42
27
|
if (!event.actor.id)
|
|
43
28
|
continue;
|
|
44
|
-
identities.set(
|
|
45
|
-
event.actor.kind,
|
|
46
|
-
event.actor.namespace,
|
|
47
|
-
event.actor.id,
|
|
48
|
-
]), {
|
|
29
|
+
identities.set(actorKey(event.actor), {
|
|
49
30
|
kind: event.actor.kind,
|
|
50
31
|
id: event.actor.id,
|
|
51
|
-
...(event.actor.namespace
|
|
52
|
-
? { namespace: event.actor.namespace }
|
|
53
|
-
: {}),
|
|
32
|
+
...(event.actor.namespace ? { namespace: event.actor.namespace } : {}),
|
|
54
33
|
});
|
|
55
34
|
}
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
35
|
+
const lookups = [];
|
|
36
|
+
for (const [key, identity] of identities) {
|
|
37
|
+
const sameKindProviders = auth
|
|
38
|
+
.map((provider, index) => ({ provider, index }))
|
|
39
|
+
.filter(({ provider }) => provider.kind === identity.kind);
|
|
40
|
+
const candidates = sameKindProviders.filter(({ provider }) => Boolean(provider.activityActorLabel));
|
|
41
|
+
const eligible = identity.namespace
|
|
42
|
+
? candidates.filter(({ provider }) => activityActorNamespace(provider) === identity.namespace)
|
|
43
|
+
: (() => {
|
|
44
|
+
const directoryKey = ({ provider, index, }) => {
|
|
45
|
+
const namespace = activityActorNamespace(provider);
|
|
46
|
+
return namespace === undefined
|
|
47
|
+
? `provider:${index}`
|
|
48
|
+
: `namespace:${namespace}`;
|
|
49
|
+
};
|
|
50
|
+
// Every same-kind provider participates in the ambiguity check,
|
|
51
|
+
// even if it cannot resolve labels. Otherwise a legacy ID owned
|
|
52
|
+
// by a provider without a resolver could be disclosed to a
|
|
53
|
+
// different provider that happens to have one.
|
|
54
|
+
const directories = new Set(sameKindProviders.map(directoryKey));
|
|
55
|
+
if (directories.size !== 1)
|
|
56
|
+
return [];
|
|
57
|
+
const [directory] = directories;
|
|
58
|
+
return candidates.filter((candidate) => directoryKey(candidate) === directory);
|
|
59
|
+
})();
|
|
60
|
+
// One namespace is one directory. Use its first configured resolver so
|
|
61
|
+
// duplicate gate adapters over the same Clerk instance do not multiply
|
|
62
|
+
// the provider-level concurrency cap.
|
|
63
|
+
const provider = eligible[0]?.provider;
|
|
64
|
+
if (provider)
|
|
65
|
+
lookups.push({ key, id: identity.id, provider });
|
|
66
|
+
}
|
|
67
|
+
return lookups;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Add display-only actor labels to one authorized activity page. Resolution is
|
|
71
|
+
* best-effort, bounded, and read-time only: stored events retain stable ids and
|
|
72
|
+
* a profile-provider outage falls back to those ids without failing the page.
|
|
73
|
+
*
|
|
74
|
+
* At most eight lookups run at once, and the page waits for them for 1.5
|
|
75
|
+
* seconds in all: whatever has resolved by then is used, and the rest are
|
|
76
|
+
* abandoned, as they are when the reader leaves.
|
|
77
|
+
*/
|
|
78
|
+
function enrichActivityActorLabels(page, auth) {
|
|
79
|
+
return Effect.suspend(() => {
|
|
80
|
+
const labels = new Map();
|
|
81
|
+
const resolve = ({ key, id, provider }) => Effect.tryPromise(() => Promise.resolve(provider.activityActorLabel(id))).pipe(Effect.map(cleanActivityActorLabel), Effect.orElseSucceed(() => undefined), Effect.map((label) => {
|
|
99
82
|
if (label)
|
|
100
83
|
labels.set(key, label);
|
|
84
|
+
}));
|
|
85
|
+
return Effect.forEach(labelLookups(page, auth), resolve, {
|
|
86
|
+
concurrency: ACTIVITY_LABEL_CONCURRENCY,
|
|
87
|
+
discard: true,
|
|
88
|
+
}).pipe(Effect.timeoutOption(Duration.millis(ACTIVITY_LABEL_PAGE_BUDGET_MS)), Effect.map(() => ({
|
|
89
|
+
...page,
|
|
90
|
+
events: page.events.map((event) => {
|
|
91
|
+
const resolved = event.actor.id
|
|
92
|
+
? labels.get(actorKey(event.actor))
|
|
93
|
+
: undefined;
|
|
94
|
+
// Never trust or echo a `label` supplied by storage. The persisted event
|
|
95
|
+
// schema has no label; only this authenticated read path may add one.
|
|
96
|
+
const actor = {
|
|
97
|
+
kind: event.actor.kind,
|
|
98
|
+
...(event.actor.id ? { id: event.actor.id } : {}),
|
|
99
|
+
...(event.actor.namespace
|
|
100
|
+
? { namespace: event.actor.namespace }
|
|
101
|
+
: {}),
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
...event,
|
|
105
|
+
actor: resolved ? { ...actor, label: resolved } : actor,
|
|
106
|
+
};
|
|
107
|
+
}),
|
|
108
|
+
})));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function activityRead(context) {
|
|
112
|
+
const { url, opts } = context;
|
|
113
|
+
return Effect.gen(function* () {
|
|
114
|
+
const authz = yield* authorized(context, false);
|
|
115
|
+
if (!authz.operator)
|
|
116
|
+
return yield* refuse("operator access required", 403);
|
|
117
|
+
if (opts.activityReadGate &&
|
|
118
|
+
!(yield* Effect.promise(async () => opts.activityReadGate(authz.actor)))) {
|
|
119
|
+
return yield* refuse("forbidden", 403);
|
|
101
120
|
}
|
|
121
|
+
const list = opts.activity?.list?.bind(opts.activity);
|
|
122
|
+
if (!list)
|
|
123
|
+
return yield* refuse("activity history is not configured", 404);
|
|
124
|
+
const cursor = url.searchParams.get("cursor") ?? undefined;
|
|
125
|
+
if (cursor && cursor.length > 500)
|
|
126
|
+
return yield* refuse("invalid cursor", 400);
|
|
127
|
+
const requestedLimit = Number(url.searchParams.get("limit") ?? "50");
|
|
128
|
+
const limit = Number.isFinite(requestedLimit)
|
|
129
|
+
? Math.min(100, Math.max(1, Math.trunc(requestedLimit)))
|
|
130
|
+
: 50;
|
|
131
|
+
return yield* Effect.tryPromise({
|
|
132
|
+
try: () => list({ ...(cursor !== undefined ? { cursor } : {}), limit }),
|
|
133
|
+
catch: (error) => error,
|
|
134
|
+
}).pipe(Effect.flatMap((page) => enrichActivityActorLabels(page, opts.auth)), Effect.map((page) => privateJson(page)),
|
|
135
|
+
// A page too malformed to label or serialize is the store's failure,
|
|
136
|
+
// like any other.
|
|
137
|
+
Effect.catchDefect((defect) => Effect.fail(defect)), Effect.catch((error) => {
|
|
138
|
+
if (error instanceof InvalidActivityCursorError) {
|
|
139
|
+
return refuse(error.message, 400);
|
|
140
|
+
}
|
|
141
|
+
opts.logger.error("[connecta] activity read failed", error);
|
|
142
|
+
return refuse("activity history is temporarily unavailable", 503);
|
|
143
|
+
}));
|
|
102
144
|
});
|
|
103
|
-
await Promise.all(workers);
|
|
104
|
-
return {
|
|
105
|
-
...page,
|
|
106
|
-
events: page.events.map((event) => {
|
|
107
|
-
const resolved = event.actor.id
|
|
108
|
-
? labels.get(JSON.stringify([
|
|
109
|
-
event.actor.kind,
|
|
110
|
-
event.actor.namespace,
|
|
111
|
-
event.actor.id,
|
|
112
|
-
]))
|
|
113
|
-
: undefined;
|
|
114
|
-
// Never trust or echo a `label` supplied by storage. The persisted event
|
|
115
|
-
// schema has no label; only this authenticated read path may add one.
|
|
116
|
-
const actor = {
|
|
117
|
-
kind: event.actor.kind,
|
|
118
|
-
...(event.actor.id ? { id: event.actor.id } : {}),
|
|
119
|
-
...(event.actor.namespace
|
|
120
|
-
? { namespace: event.actor.namespace }
|
|
121
|
-
: {}),
|
|
122
|
-
};
|
|
123
|
-
return {
|
|
124
|
-
...event,
|
|
125
|
-
actor: resolved ? { ...actor, label: resolved } : actor,
|
|
126
|
-
};
|
|
127
|
-
}),
|
|
128
|
-
};
|
|
129
145
|
}
|
|
130
146
|
export async function routeActivity(context) {
|
|
131
|
-
const { path, request
|
|
147
|
+
const { path, request } = context;
|
|
132
148
|
if (path !== "/ui/activity")
|
|
133
149
|
return null;
|
|
134
150
|
if (request.method !== "GET") {
|
|
135
151
|
return privateJson({ error: "method not allowed" }, { status: 405 });
|
|
136
152
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return authz.response;
|
|
140
|
-
if (!authz.operator) {
|
|
141
|
-
return privateJson({ error: "operator access required" }, { status: 403 });
|
|
142
|
-
}
|
|
143
|
-
if (opts.activityReadGate &&
|
|
144
|
-
!(await opts.activityReadGate(authz.actor))) {
|
|
145
|
-
return privateJson({ error: "forbidden" }, { status: 403 });
|
|
146
|
-
}
|
|
147
|
-
if (!opts.activity?.list) {
|
|
148
|
-
return privateJson({ error: "activity history is not configured" }, { status: 404 });
|
|
149
|
-
}
|
|
150
|
-
const cursor = url.searchParams.get("cursor") ?? undefined;
|
|
151
|
-
if (cursor && cursor.length > 500) {
|
|
152
|
-
return privateJson({ error: "invalid cursor" }, { status: 400 });
|
|
153
|
-
}
|
|
154
|
-
const requestedLimit = Number(url.searchParams.get("limit") ?? "50");
|
|
155
|
-
const limit = Number.isFinite(requestedLimit)
|
|
156
|
-
? Math.min(100, Math.max(1, Math.trunc(requestedLimit)))
|
|
157
|
-
: 50;
|
|
158
|
-
try {
|
|
159
|
-
const page = await opts.activity.list({
|
|
160
|
-
...(cursor !== undefined ? { cursor } : {}),
|
|
161
|
-
limit,
|
|
162
|
-
});
|
|
163
|
-
return privateJson(await enrichActivityActorLabels(page, opts.auth));
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
if (error instanceof InvalidActivityCursorError) {
|
|
167
|
-
return privateJson({ error: error.message }, { status: 400 });
|
|
168
|
-
}
|
|
169
|
-
opts.logger.error("[connecta] activity read failed", error);
|
|
170
|
-
return privateJson({ error: "activity history is temporarily unavailable" }, { status: 503 });
|
|
171
|
-
}
|
|
153
|
+
// A read: a reader who leaves stops the label lookups still queued.
|
|
154
|
+
return serveOperator(activityRead(context), request.signal);
|
|
172
155
|
}
|