@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.
Files changed (94) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +82 -0
  3. package/README.md +19 -11
  4. package/dist/activity-friction.d.ts +3 -0
  5. package/dist/activity-friction.js +19 -0
  6. package/dist/activity.d.ts +11 -2
  7. package/dist/activity.js +15 -19
  8. package/dist/auth/downstream-oauth.d.ts +2 -1
  9. package/dist/auth/downstream-oauth.js +10 -1
  10. package/dist/branding.d.ts +67 -0
  11. package/dist/branding.js +176 -0
  12. package/dist/catalog-service.d.ts +1 -6
  13. package/dist/catalog-service.js +3 -53
  14. package/dist/connectors/remote-mcp.js +3 -5
  15. package/dist/credential-contract.d.ts +24 -0
  16. package/dist/credential-contract.js +1 -0
  17. package/dist/credential-rules.d.ts +85 -0
  18. package/dist/credential-rules.js +107 -0
  19. package/dist/credentials.d.ts +4 -100
  20. package/dist/credentials.js +3 -107
  21. package/dist/errors.d.ts +1 -1
  22. package/dist/execute.d.ts +5 -52
  23. package/dist/execute.js +40 -347
  24. package/dist/executors/quickjs-protocol.d.ts +0 -7
  25. package/dist/executors/quickjs-protocol.js +2 -10
  26. package/dist/executors/quickjs.js +1 -1
  27. package/dist/index.d.ts +23 -56
  28. package/dist/index.js +30 -58
  29. package/dist/invocation.d.ts +0 -33
  30. package/dist/invocation.js +56 -124
  31. package/dist/meta-tools.d.ts +7 -6
  32. package/dist/meta-tools.js +14 -21
  33. package/dist/module-contracts.d.ts +19 -0
  34. package/dist/module-contracts.js +1 -0
  35. package/dist/operator-ui/generated.js +2 -2
  36. package/dist/operator-ui/model.d.ts +6 -3
  37. package/dist/operator-ui/view.d.ts +2 -18
  38. package/dist/operator-ui/view.js +3 -20
  39. package/dist/registry.d.ts +4 -1
  40. package/dist/registry.js +8 -6
  41. package/dist/routes/activity.js +1 -1
  42. package/dist/routes/credentials.js +5 -2
  43. package/dist/routes/mcp.js +7 -51
  44. package/dist/routes/oauth-management.d.ts +2 -0
  45. package/dist/routes/oauth-management.js +108 -0
  46. package/dist/routes/oauth.d.ts +0 -1
  47. package/dist/routes/oauth.js +21 -121
  48. package/dist/routes/shared.d.ts +19 -17
  49. package/dist/routes/shared.js +48 -44
  50. package/dist/routes/ui.js +36 -33
  51. package/dist/server.d.ts +1 -2
  52. package/dist/server.js +7 -45
  53. package/dist/skills.d.ts +1 -1
  54. package/dist/skills.js +55 -19
  55. package/dist/types.d.ts +3 -15
  56. package/dist/ui.d.ts +15 -70
  57. package/dist/ui.js +176 -317
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/documentation/architecture.md +31 -26
  61. package/documentation/auth.md +65 -111
  62. package/documentation/call-admission.md +10 -11
  63. package/documentation/cloudflare.md +1 -1
  64. package/documentation/code-mode.md +65 -260
  65. package/documentation/connectors.md +8 -6
  66. package/documentation/linear.md +1 -1
  67. package/documentation/meta-tools.md +37 -18
  68. package/documentation/mixpanel.md +1 -1
  69. package/documentation/notion.md +2 -2
  70. package/documentation/operations.md +19 -23
  71. package/documentation/operator-ui.md +82 -104
  72. package/documentation/optional-modules-upgrade.md +243 -0
  73. package/documentation/provider-conventions.md +7 -5
  74. package/documentation/revenuecat.md +1 -1
  75. package/documentation/storage-and-credentials.md +59 -40
  76. package/documentation/stripe.md +1 -1
  77. package/documentation/upgrading.md +83 -8
  78. package/ethos.md +36 -43
  79. package/examples/worker/AGENTS.md +3 -1
  80. package/examples/worker/README.md +68 -84
  81. package/examples/worker/src/d1-activity.ts +1 -1
  82. package/examples/worker/src/index.ts +11 -6
  83. package/package.json +18 -2
  84. package/templates/node/AGENTS.md +8 -6
  85. package/templates/node/README.md +56 -67
  86. package/templates/node/package.json +1 -1
  87. package/templates/node/src/file-activity.ts +1 -1
  88. package/templates/node/src/index.ts +11 -12
  89. package/dist/access-tokens.d.ts +0 -31
  90. package/dist/access-tokens.js +0 -236
  91. package/dist/apps-shell.d.ts +0 -37
  92. package/dist/apps-shell.js +0 -174
  93. package/dist/routes/access-tokens.d.ts +0 -6
  94. 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({ state: "started" });
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
- There is also an operator surface, off until you turn it on: sign-in, an
82
- encrypted credential vault with rotation, revocable per-client tokens, and a
83
- payload-free activity log. Worker deployments can use Cloudflare Access for
84
- both MCP and operator identity; Node deployments and existing Workers can use
85
- Clerk.
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
- Configuration can derive connector visibility from the admitted identity, and
89
- each connector may keep one shared downstream grant or a separate encrypted
90
- grant per human. Connecta does not own accounts or groups; Clerk or Cloudflare
91
- Access remains the identity provider. See [inbound auth](./documentation/auth.md#principals-visibility-and-operators)
92
- and [shared and personal auth](./documentation/storage-and-credentials.md#shared-and-personal-auth).
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,3 @@
1
+ import type { AgentFriction } from "./activity.js";
2
+ /** Coarse recovery class derived without inspecting payloads or error prose. */
3
+ export declare function agentFrictionForCode(code: string | undefined): AgentFriction | undefined;
@@ -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
+ }
@@ -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
- /** Coarse recovery class derived without inspecting payloads or error prose. */
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
- /** Coarse recovery class derived without inspecting payloads or error prose. */
20
- export function agentFrictionForCode(code) {
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 {};
@@ -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;