@zackbart/connecta 0.22.3 → 0.24.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 +5 -0
- package/CHANGELOG.md +82 -0
- package/README.md +19 -11
- package/dist/activity-friction.d.ts +3 -0
- package/dist/activity-friction.js +19 -0
- package/dist/activity.d.ts +11 -2
- package/dist/activity.js +15 -19
- package/dist/auth/downstream-oauth.d.ts +2 -1
- package/dist/auth/downstream-oauth.js +10 -1
- package/dist/branding.d.ts +67 -0
- package/dist/branding.js +176 -0
- package/dist/catalog-service.d.ts +1 -6
- package/dist/catalog-service.js +3 -53
- package/dist/connectors/remote-mcp.js +3 -5
- package/dist/credential-contract.d.ts +24 -0
- package/dist/credential-contract.js +1 -0
- package/dist/credential-rules.d.ts +85 -0
- package/dist/credential-rules.js +107 -0
- package/dist/credentials.d.ts +4 -100
- package/dist/credentials.js +3 -107
- package/dist/errors.d.ts +1 -1
- package/dist/execute.d.ts +5 -52
- package/dist/execute.js +40 -347
- package/dist/executors/quickjs-protocol.d.ts +0 -7
- package/dist/executors/quickjs-protocol.js +2 -10
- package/dist/executors/quickjs.js +1 -1
- package/dist/index.d.ts +23 -56
- package/dist/index.js +30 -58
- package/dist/invocation.d.ts +0 -33
- package/dist/invocation.js +56 -124
- package/dist/meta-tools.d.ts +7 -6
- package/dist/meta-tools.js +14 -21
- package/dist/module-contracts.d.ts +19 -0
- package/dist/module-contracts.js +1 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +6 -3
- package/dist/operator-ui/view.d.ts +2 -18
- package/dist/operator-ui/view.js +3 -20
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +8 -6
- package/dist/routes/activity.js +1 -1
- package/dist/routes/credentials.js +5 -2
- package/dist/routes/mcp.js +7 -51
- package/dist/routes/oauth-management.d.ts +2 -0
- package/dist/routes/oauth-management.js +108 -0
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +21 -121
- package/dist/routes/shared.d.ts +19 -17
- package/dist/routes/shared.js +48 -44
- package/dist/routes/ui.js +36 -33
- package/dist/server.d.ts +1 -2
- package/dist/server.js +7 -45
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +55 -19
- package/dist/types.d.ts +3 -15
- package/dist/ui.d.ts +15 -70
- package/dist/ui.js +176 -317
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +31 -26
- package/documentation/auth.md +65 -111
- package/documentation/call-admission.md +10 -11
- package/documentation/cloudflare.md +1 -1
- package/documentation/code-mode.md +65 -260
- package/documentation/connectors.md +8 -6
- package/documentation/linear.md +1 -1
- package/documentation/meta-tools.md +37 -18
- package/documentation/mixpanel.md +1 -1
- package/documentation/notion.md +2 -2
- package/documentation/operations.md +19 -23
- package/documentation/operator-ui.md +82 -104
- package/documentation/optional-modules-upgrade.md +243 -0
- package/documentation/provider-conventions.md +7 -5
- package/documentation/revenuecat.md +1 -1
- package/documentation/storage-and-credentials.md +59 -40
- package/documentation/stripe.md +1 -1
- package/documentation/upgrading.md +83 -8
- package/ethos.md +36 -43
- package/examples/worker/AGENTS.md +3 -1
- package/examples/worker/README.md +68 -84
- package/examples/worker/src/d1-activity.ts +1 -1
- package/examples/worker/src/index.ts +11 -6
- package/package.json +18 -2
- package/templates/node/AGENTS.md +8 -6
- package/templates/node/README.md +56 -67
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +1 -1
- package/templates/node/src/index.ts +11 -12
- package/dist/access-tokens.d.ts +0 -31
- package/dist/access-tokens.js +0 -236
- package/dist/apps-shell.d.ts +0 -37
- package/dist/apps-shell.js +0 -174
- package/dist/routes/access-tokens.d.ts +0 -6
- package/dist/routes/access-tokens.js +0 -83
package/AGENTS.md
CHANGED
|
@@ -82,6 +82,11 @@ Two boundaries CI enforces that are not obvious from reading a file:
|
|
|
82
82
|
unreachable from the root entry. `test/purity.test.ts` walks the import graph
|
|
83
83
|
and fails otherwise. Need a Node API? It goes behind an explicit Node-only
|
|
84
84
|
subpath (`/node` or `/quickjs`), never the root.
|
|
85
|
+
- **Optional modules.** Core owns catalog discovery, execution, invocation, and
|
|
86
|
+
enforcement. UI, activity history, encrypted credentials, and bearer auth
|
|
87
|
+
implementations stay behind explicit subpaths and outside the root import
|
|
88
|
+
graph. Use the typed `ui`, `activity`, and `vault` slots; do not add a generic
|
|
89
|
+
plugin registry or runtime installation.
|
|
85
90
|
- **The published surface.** Platform-specific storage adapters live in
|
|
86
91
|
`examples/worker/`, never in `src/` and never in the `exports` map. That
|
|
87
92
|
example does ship in the tarball, Cloudflare KV and D1 adapters included —
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.24.0 — 2026-09-07
|
|
6
|
+
|
|
7
|
+
Deployments now select UI, encrypted credentials, activity history, and inbound
|
|
8
|
+
auth through explicit module imports. Core keeps discovery, execution,
|
|
9
|
+
invocation, and enforcement together. This breaks configuration and removes
|
|
10
|
+
Connecta-issued client tokens; migrate those clients before upgrading. Shared
|
|
11
|
+
and personal auth management now require explicit permissions. Existing vault
|
|
12
|
+
and OAuth state need no format migration. See the
|
|
13
|
+
[detailed migration guide](./documentation/upgrading.md#0240-optional-modules)
|
|
14
|
+
for before-and-after configuration, team and personal deployment examples,
|
|
15
|
+
client migration, and verification.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Optional `operatorUi`, `encryptedCredentialVault`, and `activityHistory`
|
|
20
|
+
factories behind `/ui`, `/credentials`, and `/activity`, with contracts in core.
|
|
21
|
+
- Separate config-derived `credentialAdministration` and `personalConnection`
|
|
22
|
+
permissions, both denied by default; `activityAccess` controls history reads.
|
|
23
|
+
- Explicit `logger: "silent"`, independent of activity recording.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Move configured bearer authentication to `/auth/bearer`. Remove Connecta-issued
|
|
28
|
+
tokens and their management routes; old records remain inert in storage.
|
|
29
|
+
- Move root branding into UI options, replace `credentials` with `vault`, and
|
|
30
|
+
construct activity through its factory. Removed configuration fails at startup.
|
|
31
|
+
- Put credential and OAuth controls inside Connections; remove separate
|
|
32
|
+
Credentials and Tokens tabs. Show the current user's effective permissions.
|
|
33
|
+
- Keep optional implementations outside the root import graph. Omitted modules
|
|
34
|
+
contribute no runtime work or UI routes; OAuth callbacks remain in core.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Return the configured connection list without awaiting provider checks, then
|
|
39
|
+
load bounded connection details independently. Auth action feedback no longer
|
|
40
|
+
waits for an unrelated catalog reload.
|
|
41
|
+
- Start OAuth only through explicit authorized actions, never status reads.
|
|
42
|
+
- Return unavailable credential recovery when no UI or vault is mounted, and
|
|
43
|
+
complete UI-free OAuth without a dead return link.
|
|
44
|
+
|
|
45
|
+
## 0.23.0 — 2026-09-07
|
|
46
|
+
|
|
47
|
+
This release removes server-owned program views, connector HTTP routes, and
|
|
48
|
+
three convenience APIs. It also exposes configured account titles during
|
|
49
|
+
discovery and clarifies schema inspection and dependent calls.
|
|
50
|
+
Programs use canonical tool addresses and JavaScript promises; callers own
|
|
51
|
+
retry timing. Stored programs using shortcut globals, `connecta.batch`, or
|
|
52
|
+
`connecta.ui` need migration. Direct calls must omit `maxRetries`. Deployments
|
|
53
|
+
using none of these need no configuration or storage changes. The seven tools,
|
|
54
|
+
operator pages, credentials, and emitted media remain. Cloudflare Global API
|
|
55
|
+
Key authentication and multi-field credentials remain supported.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- Optional `investigate` guidance for purchase verification, experiments, and
|
|
60
|
+
customer or deployment investigations. It explains account selection,
|
|
61
|
+
evidence requirements, and when to stop with an unresolved gap (#527).
|
|
62
|
+
- Bounded configured account titles in the initial connector inventory and
|
|
63
|
+
program search results, without provider calls or changes to ranking (#527).
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- Remove connector `handleRequest` routes. Stale declarations fail at
|
|
68
|
+
construction; custom routes belong to the existing deployment fetch handler.
|
|
69
|
+
- Remove MCP Apps rendering, its HTML shell, resource handlers, extension
|
|
70
|
+
declaration, and tool UI metadata. Clients render returned data.
|
|
71
|
+
- Remove connector shortcut globals and their sanitization and collision rules.
|
|
72
|
+
Programs call the canonical address through `connecta.call`.
|
|
73
|
+
- Remove `connecta.batch`. Use `Promise.all` or `Promise.allSettled` under the
|
|
74
|
+
same host-call and connector admission limits.
|
|
75
|
+
- Remove automatic retries and backoff timing from direct calls. Failures keep
|
|
76
|
+
their classification and provider retry hint. Direct-call schemas reject
|
|
77
|
+
unknown arguments, including the removed `maxRetries` option.
|
|
78
|
+
- Clarify JSON schema inspection, provider result shapes, and promise failures.
|
|
79
|
+
Keep discovery and calls together when schemas suffice; allow a small sample
|
|
80
|
+
for unfamiliar results before continuing in another program (#527).
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Remove the stale CI invocation of the retired evaluation `audit` script.
|
|
85
|
+
The benchmark self-tests remain in CI; whole-agent comparisons run locally.
|
|
86
|
+
|
|
5
87
|
## 0.22.3 — 2026-09-03
|
|
6
88
|
|
|
7
89
|
This patch carries OAuth discovery across the browser redirect. It matters for
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ This is the kind of thing the agent writes, not you:
|
|
|
45
45
|
|
|
46
46
|
```js
|
|
47
47
|
async () => {
|
|
48
|
-
const { nodes } = await tracker.list_issues
|
|
48
|
+
const { nodes } = await connecta.call("tracker.list_issues", { state: "started" });
|
|
49
49
|
const byOwner = {};
|
|
50
50
|
for (const issue of nodes) {
|
|
51
51
|
(byOwner[issue.assignee?.name ?? "unassigned"] ??= []).push(issue.identifier);
|
|
@@ -78,18 +78,26 @@ Fifty issues in, one small object out. Your context window notices.
|
|
|
78
78
|
- **Run it where you like.** Node, a Docker container, or a Cloudflare Worker,
|
|
79
79
|
from the same small deployment file.
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
Deployments explicitly compose optional features: `operatorUi()` from
|
|
82
|
+
`@zackbart/connecta/ui`, `encryptedCredentialVault()` from `/credentials`,
|
|
83
|
+
`activityHistory()` from `/activity`, and inbound authentication adapters from
|
|
84
|
+
`/auth/*`. Omit a module and its implementation does no runtime work. Core
|
|
85
|
+
keeps connector discovery, execution, invocation, and enforcement together.
|
|
86
|
+
|
|
87
|
+
The optional UI shows each person's connections and effective permissions.
|
|
88
|
+
Authentication controls live inside each connection, with optional activity
|
|
89
|
+
history. The configured connection list loads before downstream checks finish;
|
|
90
|
+
a slow provider does not hold up the page. Connector selection and access rules
|
|
91
|
+
remain in deployment code.
|
|
86
92
|
|
|
87
93
|
One deployment may serve several authenticated people inside the same tenant.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
Cloudflare Access supplies Worker identity; Node can use Clerk or the optional
|
|
95
|
+
configured bearer adapter. Connecta owns no accounts or groups and issues no
|
|
96
|
+
client access tokens. Shared-credential administration and personal connection
|
|
97
|
+
setup require separate explicit permissions, both denied by default. See
|
|
98
|
+
[inbound auth](./documentation/auth.md#principals-visibility-and-operators),
|
|
99
|
+
[shared and personal auth](./documentation/storage-and-credentials.md#shared-and-personal-auth),
|
|
100
|
+
and the [module migration guide](./documentation/upgrading.md#0240-optional-modules).
|
|
93
101
|
|
|
94
102
|
Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
|
|
95
103
|
service. Those are decisions, and the [ethos](./ethos.md) records each one
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Coarse recovery class derived without inspecting payloads or error prose. */
|
|
2
|
+
export function agentFrictionForCode(code) {
|
|
3
|
+
switch (code) {
|
|
4
|
+
case "unknown_address":
|
|
5
|
+
case "unknown_tool":
|
|
6
|
+
case "ambiguous_tool_alias":
|
|
7
|
+
return "tool_not_found";
|
|
8
|
+
case "invalid_args":
|
|
9
|
+
return "schema_retry";
|
|
10
|
+
case "destructive_tool_requires_approval":
|
|
11
|
+
return "destructive_reroute";
|
|
12
|
+
case "auth_required":
|
|
13
|
+
return "auth_required";
|
|
14
|
+
case "result_too_large":
|
|
15
|
+
return "result_too_large";
|
|
16
|
+
default:
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/activity.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { ActivityModule } from "./module-contracts.js";
|
|
1
2
|
import type { CatalogDriftCounts, Logger } from "./types.js";
|
|
2
3
|
export type ActivityCallSource = "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code";
|
|
3
4
|
export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled";
|
|
4
5
|
export type AgentFriction = "tool_not_found" | "schema_retry" | "destructive_reroute" | "auth_required" | "result_too_large";
|
|
5
|
-
|
|
6
|
-
export declare function agentFrictionForCode(code: string | undefined): AgentFriction | undefined;
|
|
6
|
+
export { agentFrictionForCode } from "./activity-friction.js";
|
|
7
7
|
/**
|
|
8
8
|
* Authenticated identity attached to an activity event. `id` is intentionally
|
|
9
9
|
* optional: open deployments and shared bearer tokens cannot honestly identify
|
|
@@ -119,6 +119,7 @@ export type ActivityReadGate = (actor: ActivityActor) => boolean | Promise<boole
|
|
|
119
119
|
* did, so it carries no actor and no request id to attribute it to one.
|
|
120
120
|
*/
|
|
121
121
|
export interface CatalogDriftActivityContext {
|
|
122
|
+
recordDrift?: typeof recordCatalogDriftActivity;
|
|
122
123
|
sink: ActivitySink;
|
|
123
124
|
serverInfo: {
|
|
124
125
|
name: string;
|
|
@@ -129,6 +130,7 @@ export interface CatalogDriftActivityContext {
|
|
|
129
130
|
}
|
|
130
131
|
/** Request-scoped context shared by direct, batch, and code-mode call paths. */
|
|
131
132
|
export interface ActivityRequestContext {
|
|
133
|
+
recordTool?: typeof recordToolActivity | undefined;
|
|
132
134
|
sink: ActivitySink;
|
|
133
135
|
actor: ActivityActor;
|
|
134
136
|
requestId: string;
|
|
@@ -155,3 +157,10 @@ export declare function recordToolActivity(context: ActivityRequestContext | und
|
|
|
155
157
|
export declare function recordCatalogDriftActivity(context: CatalogDriftActivityContext | undefined, input: {
|
|
156
158
|
connectorId: string;
|
|
157
159
|
} & CatalogDriftCounts): void;
|
|
160
|
+
export interface ActivityHistoryOptions {
|
|
161
|
+
store: ActivityStore;
|
|
162
|
+
deploymentId?: string;
|
|
163
|
+
readGate?: ActivityReadGate;
|
|
164
|
+
}
|
|
165
|
+
/** Attach payload-free history without changing tool results on store failure. */
|
|
166
|
+
export declare function activityHistory(options: ActivityHistoryOptions): ActivityModule;
|
package/dist/activity.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { routeActivity } from "./routes/activity.js";
|
|
1
2
|
import { boundedEchoText } from "./errors.js";
|
|
2
3
|
/**
|
|
3
4
|
* How long an identity field may be before the store stops believing it.
|
|
@@ -16,25 +17,8 @@ import { boundedEchoText } from "./errors.js";
|
|
|
16
17
|
const MAX_ACTIVITY_NAME_BYTES = 128;
|
|
17
18
|
/** Two names and the dot between them. */
|
|
18
19
|
const MAX_ACTIVITY_ADDRESS_BYTES = MAX_ACTIVITY_NAME_BYTES * 2 + 1;
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
switch (code) {
|
|
22
|
-
case "unknown_address":
|
|
23
|
-
case "unknown_tool":
|
|
24
|
-
case "ambiguous_tool_alias":
|
|
25
|
-
return "tool_not_found";
|
|
26
|
-
case "invalid_args":
|
|
27
|
-
return "schema_retry";
|
|
28
|
-
case "destructive_tool_requires_approval":
|
|
29
|
-
return "destructive_reroute";
|
|
30
|
-
case "auth_required":
|
|
31
|
-
return "auth_required";
|
|
32
|
-
case "result_too_large":
|
|
33
|
-
return "result_too_large";
|
|
34
|
-
default:
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
20
|
+
import { agentFrictionForCode } from "./activity-friction.js";
|
|
21
|
+
export { agentFrictionForCode } from "./activity-friction.js";
|
|
38
22
|
/** Reader implementations throw this for an opaque cursor they cannot decode. */
|
|
39
23
|
export class InvalidActivityCursorError extends Error {
|
|
40
24
|
name = "InvalidActivityCursorError";
|
|
@@ -123,3 +107,15 @@ export function recordCatalogDriftActivity(context, input) {
|
|
|
123
107
|
context.logger.warn("[connecta] catalog drift record failed", error);
|
|
124
108
|
}
|
|
125
109
|
}
|
|
110
|
+
/** Attach payload-free history without changing tool results on store failure. */
|
|
111
|
+
export function activityHistory(options) {
|
|
112
|
+
if (!options || typeof options.store?.record !== "function") {
|
|
113
|
+
throw new Error("activityHistory.store must implement record(event)");
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
...options,
|
|
117
|
+
handle: routeActivity,
|
|
118
|
+
recordTool: recordToolActivity,
|
|
119
|
+
recordDrift: recordCatalogDriftActivity,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -71,6 +71,7 @@ export declare class KvOAuthProvider implements OAuthClientProvider {
|
|
|
71
71
|
private readonly storage;
|
|
72
72
|
private readonly redirectUri;
|
|
73
73
|
private readonly refreshCoordinator?;
|
|
74
|
+
private readonly allowAuthorization;
|
|
74
75
|
/**
|
|
75
76
|
* The reset generation this provider's flow started under. Every OAuth value
|
|
76
77
|
* it writes carries this epoch, so a late write can land after a reset without
|
|
@@ -80,7 +81,7 @@ export declare class KvOAuthProvider implements OAuthClientProvider {
|
|
|
80
81
|
private refreshFlight;
|
|
81
82
|
/** Tokens this request's issuer-aware auth flow decided to refresh. */
|
|
82
83
|
private refreshBasis;
|
|
83
|
-
constructor(connectorId: string, storage: KVStorage, redirectUri: string, refreshCoordinator?: OAuthRefreshCoordinator | undefined);
|
|
84
|
+
constructor(connectorId: string, storage: KVStorage, redirectUri: string, refreshCoordinator?: OAuthRefreshCoordinator | undefined, allowAuthorization?: boolean);
|
|
84
85
|
/**
|
|
85
86
|
* Stamp the force-reauth generation the current connect flow started under.
|
|
86
87
|
* Called by the connector once per connect, before c.connect(). The callback
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UnauthorizedError } from "@modelcontextprotocol/client";
|
|
1
2
|
/** A 256-bit random opaque value, hex-encoded — used for the OAuth `state`. */
|
|
2
3
|
function randomState() {
|
|
3
4
|
const bytes = new Uint8Array(32);
|
|
@@ -383,6 +384,7 @@ export class KvOAuthProvider {
|
|
|
383
384
|
storage;
|
|
384
385
|
redirectUri;
|
|
385
386
|
refreshCoordinator;
|
|
387
|
+
allowAuthorization;
|
|
386
388
|
/**
|
|
387
389
|
* The reset generation this provider's flow started under. Every OAuth value
|
|
388
390
|
* it writes carries this epoch, so a late write can land after a reset without
|
|
@@ -392,11 +394,12 @@ export class KvOAuthProvider {
|
|
|
392
394
|
refreshFlight;
|
|
393
395
|
/** Tokens this request's issuer-aware auth flow decided to refresh. */
|
|
394
396
|
refreshBasis;
|
|
395
|
-
constructor(connectorId, storage, redirectUri, refreshCoordinator) {
|
|
397
|
+
constructor(connectorId, storage, redirectUri, refreshCoordinator, allowAuthorization = true) {
|
|
396
398
|
this.connectorId = connectorId;
|
|
397
399
|
this.storage = storage;
|
|
398
400
|
this.redirectUri = redirectUri;
|
|
399
401
|
this.refreshCoordinator = refreshCoordinator;
|
|
402
|
+
this.allowAuthorization = allowAuthorization;
|
|
400
403
|
}
|
|
401
404
|
/**
|
|
402
405
|
* Stamp the force-reauth generation the current connect flow started under.
|
|
@@ -666,6 +669,8 @@ export class KvOAuthProvider {
|
|
|
666
669
|
* clearPending() once the flow completes.
|
|
667
670
|
*/
|
|
668
671
|
async state() {
|
|
672
|
+
if (!this.allowAuthorization)
|
|
673
|
+
throw new UnauthorizedError("Authorization required. Use authorize_connector or Connect to start consent.");
|
|
669
674
|
const value = randomState();
|
|
670
675
|
await this.writeValue("oauth:state", value, (raw) => raw);
|
|
671
676
|
return value;
|
|
@@ -684,6 +689,8 @@ export class KvOAuthProvider {
|
|
|
684
689
|
return matches;
|
|
685
690
|
}
|
|
686
691
|
async saveCodeVerifier(verifier) {
|
|
692
|
+
if (!this.allowAuthorization)
|
|
693
|
+
throw new UnauthorizedError("Authorization required. Use authorize_connector or Connect to start consent.");
|
|
687
694
|
await this.writeValue("oauth:verifier", verifier, (raw) => raw);
|
|
688
695
|
}
|
|
689
696
|
async codeVerifier() {
|
|
@@ -694,6 +701,8 @@ export class KvOAuthProvider {
|
|
|
694
701
|
return stored.value;
|
|
695
702
|
}
|
|
696
703
|
async redirectToAuthorization(authorizationUrl) {
|
|
704
|
+
if (!this.allowAuthorization)
|
|
705
|
+
throw new UnauthorizedError("Authorization required. Use authorize_connector or Connect to start consent.");
|
|
697
706
|
try {
|
|
698
707
|
await this.writeValue("oauth:pending", authorizationUrl.toString(), (raw) => raw);
|
|
699
708
|
this.failRefreshFlight(new Error("OAuth refresh required reauthorization before tokens were saved."));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ConnectaBranding, UiAuthConfig } from "./types.js";
|
|
2
|
+
/** Connecta's default monochrome "C" mark. */
|
|
3
|
+
export declare const CONNECTA_FAVICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\">\n <style>\n .fg { fill: #000 }\n @media (prefers-color-scheme: dark) { .fg { fill: #fff } }\n </style>\n <path class=\"fg\" d=\"M27 9.4A13 13 0 1 0 27 22.6l-4.4-2.5a8 8 0 1 1 0-8.2z\"/>\n</svg>";
|
|
4
|
+
interface ResolvedBranding {
|
|
5
|
+
productName: string;
|
|
6
|
+
productUrl?: string;
|
|
7
|
+
ownerName?: string;
|
|
8
|
+
ownerUrl?: string;
|
|
9
|
+
description: string;
|
|
10
|
+
/** Browser tab title and page meta name. */
|
|
11
|
+
pageTitle: string;
|
|
12
|
+
/** href for the page's icon link. */
|
|
13
|
+
faviconHref: string;
|
|
14
|
+
themeColor: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveBranding(branding?: ConnectaBranding): ResolvedBranding;
|
|
17
|
+
/**
|
|
18
|
+
* Names of the branding URLs the operator set that failed their gate and were
|
|
19
|
+
* replaced by a default. Lives beside the gates so the startup warning cannot
|
|
20
|
+
* drift from them, and takes `unknown` fields for the same reason
|
|
21
|
+
* `resolveBranding` does — a warning helper must never throw.
|
|
22
|
+
*/
|
|
23
|
+
export declare function droppedBrandingUrls(branding?: ConnectaBranding): string[];
|
|
24
|
+
export declare function isSafeHttpUrl(url: unknown): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* True for values allowed in the page's `<link rel="icon" href>`: an absolute
|
|
27
|
+
* `http(s)` URL (an icon the operator hosts elsewhere) or a path rooted at this
|
|
28
|
+
* origin. The relative carve-out is deliberate rather than accidental — the
|
|
29
|
+
* default href is the relative `/favicon.svg`, which `isSafeHttpUrl` alone would
|
|
30
|
+
* reject — and it is kept narrow on both ends.
|
|
31
|
+
*
|
|
32
|
+
* Root-relative only, because operator and OAuth callback pages sit at
|
|
33
|
+
* different depths and a document-relative path would resolve differently.
|
|
34
|
+
*
|
|
35
|
+
* "Root-relative" is enforced structurally: exactly one leading `/` followed by
|
|
36
|
+
* a character that is neither `/` nor `\`. Both of those would make the value an
|
|
37
|
+
* authority (`//host`, and `/\host` because the URL parser folds `\` to `/` in
|
|
38
|
+
* special schemes), pointing at an origin this server does not control. The test
|
|
39
|
+
* runs on a copy with tab/newline/CR removed, since the parser strips those
|
|
40
|
+
* anywhere and `/\t/host` would otherwise slip through as single-slash. The
|
|
41
|
+
* origin comparison that follows is defense in depth, not the authority check —
|
|
42
|
+
* on its own it would accept an authority that happened to equal the probe host.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isSafeIconHref(href: unknown): boolean;
|
|
45
|
+
/** Absolute HTTPS gate for the `UiAuthConfig` URL fields documented in types.ts. */
|
|
46
|
+
export declare function isSafeHttpsUrl(url: unknown): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Names of the `uiAuth` URLs an inbound-auth provider supplied that failed their
|
|
49
|
+
* gate. Lives beside the gate for the same reason `droppedBrandingUrls` does: the
|
|
50
|
+
* startup warning cannot then drift from what rendering actually drops. Every
|
|
51
|
+
* field is read defensively rather than trusted, because a custom `InboundAuth`
|
|
52
|
+
* is untyped at a JS call site — `isSafeHttpsUrl` takes `unknown`, and a
|
|
53
|
+
* `uiAuth` that is not the clerk shape is reported as nothing to warn about.
|
|
54
|
+
*
|
|
55
|
+
* `frontendApiUrl` is required, so anything that fails its gate is a drop.
|
|
56
|
+
* `signInUrl` and `signUpUrl` are optional, so only a value the operator
|
|
57
|
+
* *supplied* and the gate then rejected is worth a warning — an unset field
|
|
58
|
+
* took no default away from anyone. `isSetUrlValue` decides that, the same way
|
|
59
|
+
* and for the same reasons it decides it for the branding URLs: a warning that
|
|
60
|
+
* fires for one and not the other would be reporting on the field rather than
|
|
61
|
+
* on the operator's intent. Rendering is not consulted for this: it drops on
|
|
62
|
+
* the gate alone, and a blank string fails that gate too — it is simply not
|
|
63
|
+
* *reported*, because a blank is indistinguishable from leaving the field
|
|
64
|
+
* alone.
|
|
65
|
+
*/
|
|
66
|
+
export declare function droppedUiAuthUrls(uiAuth?: UiAuthConfig): string[];
|
|
67
|
+
export {};
|
package/dist/branding.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/** Connecta's default monochrome "C" mark. */
|
|
2
|
+
export const CONNECTA_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
3
|
+
<style>
|
|
4
|
+
.fg { fill: #000 }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .fg { fill: #fff } }
|
|
6
|
+
</style>
|
|
7
|
+
<path class="fg" d="M27 9.4A13 13 0 1 0 27 22.6l-4.4-2.5a8 8 0 1 1 0-8.2z"/>
|
|
8
|
+
</svg>`;
|
|
9
|
+
const DEFAULT_FAVICON_HREF = "/favicon.svg";
|
|
10
|
+
/**
|
|
11
|
+
* Branding arrives from operator config, which is untyped at a JS call site, so
|
|
12
|
+
* every field is treated as `unknown`: a non-string is read as unset rather than
|
|
13
|
+
* throwing on `.trim()`. Rendering must degrade to defaults for a malformed
|
|
14
|
+
* value, never fail — `createConnecta` calls this during construction.
|
|
15
|
+
*/
|
|
16
|
+
function trimmedString(value) {
|
|
17
|
+
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
18
|
+
}
|
|
19
|
+
export function resolveBranding(branding) {
|
|
20
|
+
const productName = trimmedString(branding?.productName) ?? "Connecta";
|
|
21
|
+
const ownerName = trimmedString(branding?.ownerName);
|
|
22
|
+
// Operator branding URLs become masthead/callback hrefs, so a non-http(s)
|
|
23
|
+
// scheme (javascript:, data:) is dropped the same as an unset URL — the
|
|
24
|
+
// callers already render a <span> instead of an <a> when it is absent.
|
|
25
|
+
const productUrl = trimmedString(branding?.productUrl);
|
|
26
|
+
const ownerUrl = trimmedString(branding?.ownerUrl);
|
|
27
|
+
const faviconHref = trimmedString(branding?.favicon?.href);
|
|
28
|
+
return {
|
|
29
|
+
productName,
|
|
30
|
+
...(productUrl && isSafeHttpUrl(productUrl) ? { productUrl } : {}),
|
|
31
|
+
...(ownerName ? { ownerName } : {}),
|
|
32
|
+
...(ownerUrl && isSafeHttpUrl(ownerUrl) ? { ownerUrl } : {}),
|
|
33
|
+
description: trimmedString(branding?.description) ??
|
|
34
|
+
`Manage the services this ${productName} instance makes available to agents.`,
|
|
35
|
+
pageTitle: trimmedString(branding?.pageTitle) ??
|
|
36
|
+
(ownerName ? `${productName} — ${ownerName}` : productName),
|
|
37
|
+
faviconHref: faviconHref && isSafeIconHref(faviconHref)
|
|
38
|
+
? faviconHref
|
|
39
|
+
: DEFAULT_FAVICON_HREF,
|
|
40
|
+
themeColor: trimmedString(branding?.themeColor) ?? "#ffffff",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether the operator meant to supply a value here — the question every
|
|
45
|
+
* dropped-URL warning asks before naming a field, and one definition so the
|
|
46
|
+
* branding and `uiAuth` warnings cannot answer it differently. A non-string
|
|
47
|
+
* counts as set: the intent was there and is exactly what the warning reports
|
|
48
|
+
* on. A blank or whitespace-only string does not; that is indistinguishable
|
|
49
|
+
* from leaving the field alone, and both take the default silently.
|
|
50
|
+
*/
|
|
51
|
+
function isSetUrlValue(value) {
|
|
52
|
+
return typeof value === "string"
|
|
53
|
+
? trimmedString(value) !== undefined
|
|
54
|
+
: value !== undefined && value !== null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Names of the branding URLs the operator set that failed their gate and were
|
|
58
|
+
* replaced by a default. Lives beside the gates so the startup warning cannot
|
|
59
|
+
* drift from them, and takes `unknown` fields for the same reason
|
|
60
|
+
* `resolveBranding` does — a warning helper must never throw.
|
|
61
|
+
*/
|
|
62
|
+
export function droppedBrandingUrls(branding) {
|
|
63
|
+
if (!branding)
|
|
64
|
+
return [];
|
|
65
|
+
const resolved = resolveBranding(branding);
|
|
66
|
+
const faviconHref = branding.favicon?.href;
|
|
67
|
+
return [
|
|
68
|
+
...(isSetUrlValue(branding.productUrl) && !resolved.productUrl
|
|
69
|
+
? ["productUrl"]
|
|
70
|
+
: []),
|
|
71
|
+
...(isSetUrlValue(branding.ownerUrl) && !resolved.ownerUrl
|
|
72
|
+
? ["ownerUrl"]
|
|
73
|
+
: []),
|
|
74
|
+
...(isSetUrlValue(faviconHref) &&
|
|
75
|
+
trimmedString(faviconHref) !== resolved.faviconHref
|
|
76
|
+
? ["favicon.href"]
|
|
77
|
+
: []),
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* True only for absolute `http:`/`https:` URLs. Downstream connectors control
|
|
82
|
+
* their `authorizationUrl`, so a hostile/misconfigured one could hand back a
|
|
83
|
+
* `javascript:` (or other) scheme; gate it before it can become an href.
|
|
84
|
+
*/
|
|
85
|
+
function safeUrl(url, schemes) {
|
|
86
|
+
if (typeof url !== "string")
|
|
87
|
+
return false;
|
|
88
|
+
try {
|
|
89
|
+
return schemes.includes(new URL(url).protocol);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function isSafeHttpUrl(url) {
|
|
96
|
+
return safeUrl(url, ["http:", "https:"]);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Only the second check's base; any origin works because the check is whether
|
|
100
|
+
* the href stays on whatever origin it is resolved against. It is deliberately
|
|
101
|
+
* never the sole gate: a value whose own authority equals this host (say
|
|
102
|
+
* `//connecta.invalid/x`) would resolve to this exact origin and pass, so the
|
|
103
|
+
* structural check below runs first and is what actually rejects `//host`.
|
|
104
|
+
*/
|
|
105
|
+
const SAME_ORIGIN_PROBE = "https://connecta.invalid";
|
|
106
|
+
/** Removed anywhere in a URL by the parser, so a gate must ignore them too. */
|
|
107
|
+
const URL_STRIPPED_CHARS = /[\t\n\r]/g;
|
|
108
|
+
/**
|
|
109
|
+
* True for values allowed in the page's `<link rel="icon" href>`: an absolute
|
|
110
|
+
* `http(s)` URL (an icon the operator hosts elsewhere) or a path rooted at this
|
|
111
|
+
* origin. The relative carve-out is deliberate rather than accidental — the
|
|
112
|
+
* default href is the relative `/favicon.svg`, which `isSafeHttpUrl` alone would
|
|
113
|
+
* reject — and it is kept narrow on both ends.
|
|
114
|
+
*
|
|
115
|
+
* Root-relative only, because operator and OAuth callback pages sit at
|
|
116
|
+
* different depths and a document-relative path would resolve differently.
|
|
117
|
+
*
|
|
118
|
+
* "Root-relative" is enforced structurally: exactly one leading `/` followed by
|
|
119
|
+
* a character that is neither `/` nor `\`. Both of those would make the value an
|
|
120
|
+
* authority (`//host`, and `/\host` because the URL parser folds `\` to `/` in
|
|
121
|
+
* special schemes), pointing at an origin this server does not control. The test
|
|
122
|
+
* runs on a copy with tab/newline/CR removed, since the parser strips those
|
|
123
|
+
* anywhere and `/\t/host` would otherwise slip through as single-slash. The
|
|
124
|
+
* origin comparison that follows is defense in depth, not the authority check —
|
|
125
|
+
* on its own it would accept an authority that happened to equal the probe host.
|
|
126
|
+
*/
|
|
127
|
+
export function isSafeIconHref(href) {
|
|
128
|
+
if (typeof href !== "string")
|
|
129
|
+
return false;
|
|
130
|
+
if (isSafeHttpUrl(href))
|
|
131
|
+
return true;
|
|
132
|
+
if (!/^\/(?![/\\])/.test(href.replace(URL_STRIPPED_CHARS, "")))
|
|
133
|
+
return false;
|
|
134
|
+
try {
|
|
135
|
+
return new URL(href, SAME_ORIGIN_PROBE).origin === SAME_ORIGIN_PROBE;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Absolute HTTPS gate for the `UiAuthConfig` URL fields documented in types.ts. */
|
|
142
|
+
export function isSafeHttpsUrl(url) {
|
|
143
|
+
return safeUrl(url, ["https:"]);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Names of the `uiAuth` URLs an inbound-auth provider supplied that failed their
|
|
147
|
+
* gate. Lives beside the gate for the same reason `droppedBrandingUrls` does: the
|
|
148
|
+
* startup warning cannot then drift from what rendering actually drops. Every
|
|
149
|
+
* field is read defensively rather than trusted, because a custom `InboundAuth`
|
|
150
|
+
* is untyped at a JS call site — `isSafeHttpsUrl` takes `unknown`, and a
|
|
151
|
+
* `uiAuth` that is not the clerk shape is reported as nothing to warn about.
|
|
152
|
+
*
|
|
153
|
+
* `frontendApiUrl` is required, so anything that fails its gate is a drop.
|
|
154
|
+
* `signInUrl` and `signUpUrl` are optional, so only a value the operator
|
|
155
|
+
* *supplied* and the gate then rejected is worth a warning — an unset field
|
|
156
|
+
* took no default away from anyone. `isSetUrlValue` decides that, the same way
|
|
157
|
+
* and for the same reasons it decides it for the branding URLs: a warning that
|
|
158
|
+
* fires for one and not the other would be reporting on the field rather than
|
|
159
|
+
* on the operator's intent. Rendering is not consulted for this: it drops on
|
|
160
|
+
* the gate alone, and a blank string fails that gate too — it is simply not
|
|
161
|
+
* *reported*, because a blank is indistinguishable from leaving the field
|
|
162
|
+
* alone.
|
|
163
|
+
*/
|
|
164
|
+
export function droppedUiAuthUrls(uiAuth) {
|
|
165
|
+
if (!uiAuth || uiAuth.kind !== "clerk")
|
|
166
|
+
return [];
|
|
167
|
+
return [
|
|
168
|
+
...(isSafeHttpsUrl(uiAuth.frontendApiUrl) ? [] : ["uiAuth.frontendApiUrl"]),
|
|
169
|
+
...(isSetUrlValue(uiAuth.signInUrl) && !isSafeHttpsUrl(uiAuth.signInUrl)
|
|
170
|
+
? ["uiAuth.signInUrl"]
|
|
171
|
+
: []),
|
|
172
|
+
...(isSetUrlValue(uiAuth.signUpUrl) && !isSafeHttpsUrl(uiAuth.signUpUrl)
|
|
173
|
+
? ["uiAuth.signUpUrl"]
|
|
174
|
+
: []),
|
|
175
|
+
];
|
|
176
|
+
}
|
|
@@ -181,12 +181,6 @@ export declare class CatalogService {
|
|
|
181
181
|
private catalogLoadFailure;
|
|
182
182
|
private unknownToolFailure;
|
|
183
183
|
resolveTool(address: string, callOptions?: ConnectorOperationOptions): Promise<CatalogResolution>;
|
|
184
|
-
/**
|
|
185
|
-
* Resolve the JavaScript-safe property used by a lazy code-mode namespace
|
|
186
|
-
* back to exactly one catalog tool. Ambiguous aliases fail with an explicit
|
|
187
|
-
* escape hatch instead of silently choosing the first tool.
|
|
188
|
-
*/
|
|
189
|
-
resolveToolAlias(connectorId: string, alias: string, aliasFor: (toolName: string) => string, callOptions?: ConnectorOperationOptions): Promise<CatalogResolution>;
|
|
190
184
|
search(args: CatalogSearchArgs): Promise<CatalogSearchPage>;
|
|
191
185
|
describe(args: CatalogDescribeArgs): Promise<CatalogDescription[]>;
|
|
192
186
|
}
|
|
@@ -247,6 +241,7 @@ export declare function flatSearchResult(page: CatalogSearchPage): {
|
|
|
247
241
|
tools: {
|
|
248
242
|
guideSummary?: string;
|
|
249
243
|
guide?: string;
|
|
244
|
+
connectorTitle?: string;
|
|
250
245
|
name: string;
|
|
251
246
|
address: string;
|
|
252
247
|
description?: string;
|