@zackbart/connecta 0.32.0 → 0.34.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 (158) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +95 -116
  3. package/SECURITY.md +1 -1
  4. package/bin/connecta.mjs +24 -6
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +167 -23
  7. package/dist/access-tokens.js +676 -140
  8. package/dist/activity.d.ts +3 -0
  9. package/dist/agent-identity.js +47 -0
  10. package/dist/auth/cloudflare-access.js +13 -5
  11. package/dist/branding.d.ts +7 -0
  12. package/dist/branding.js +76 -9
  13. package/dist/call-admission-context.js +15 -0
  14. package/dist/call-admission.js +30 -43
  15. package/dist/call-recovery.js +6 -2
  16. package/dist/catalog-service.js +15 -1
  17. package/dist/config-value-sources.js +16 -9
  18. package/dist/config.d.ts +48 -75
  19. package/dist/config.js +144 -36
  20. package/dist/connector-access.d.ts +4 -34
  21. package/dist/connector-access.js +15 -99
  22. package/dist/connector-caller.d.ts +7 -1
  23. package/dist/connector-caller.js +4 -2
  24. package/dist/connectors/api-connector.d.ts +27 -4
  25. package/dist/connectors/api-connector.js +2 -0
  26. package/dist/connectors/remote-mcp.js +2 -0
  27. package/dist/describe-config.d.ts +31 -7
  28. package/dist/describe-config.js +8 -17
  29. package/dist/execute.js +1 -11
  30. package/dist/fix-prompt.js +2 -2
  31. package/dist/index.d.ts +4 -3
  32. package/dist/index.js +30 -10
  33. package/dist/invocation.js +5 -3
  34. package/dist/membership.d.ts +53 -0
  35. package/dist/membership.js +121 -0
  36. package/dist/module-contracts.d.ts +23 -6
  37. package/dist/oauth-callback-outcome.js +2 -2
  38. package/dist/operator-record.d.ts +2 -2
  39. package/dist/operator-record.js +2 -4
  40. package/dist/operator-ui/agents.d.ts +74 -0
  41. package/dist/operator-ui/agents.js +104 -0
  42. package/dist/operator-ui/config-summary.js +562 -0
  43. package/dist/operator-ui/connection-map.js +375 -0
  44. package/dist/operator-ui/contract.d.ts +149 -28
  45. package/dist/operator-ui/endpoints.js +221 -0
  46. package/dist/operator-ui/fix-prompts.js +5 -5
  47. package/dist/operator-ui/generated.js +5 -4
  48. package/dist/operator-ui/member-page.js +97 -0
  49. package/dist/operator-ui/model.d.ts +14 -6
  50. package/dist/operator-ui/overview.js +134 -0
  51. package/dist/operator-ui/view.d.ts +107 -37
  52. package/dist/operator-ui/view.js +163 -86
  53. package/dist/page-styles.js +5 -3
  54. package/dist/provider.d.ts +61 -8
  55. package/dist/provider.js +46 -7
  56. package/dist/providers/_shared/google/workspace.d.ts +56 -17
  57. package/dist/providers/_shared/google/workspace.js +34 -8
  58. package/dist/providers/_shared/rest/tools.js +2 -2
  59. package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
  60. package/dist/providers/_shared/rest/value-safety.js +699 -7
  61. package/dist/providers/ccb/index.js +2 -2
  62. package/dist/providers/cloudflare/index.js +3 -3
  63. package/dist/providers/cloudflare/rest.js +25 -3
  64. package/dist/providers/cloudflare/skill.generated.js +1 -1
  65. package/dist/providers/cloudflare/value-safety.js +32 -0
  66. package/dist/providers/docs/index.js +4 -4
  67. package/dist/providers/docs/skill.generated.js +2 -2
  68. package/dist/providers/drive/index.js +4 -4
  69. package/dist/providers/drive/skill.generated.js +2 -2
  70. package/dist/providers/forms/index.js +4 -4
  71. package/dist/providers/forms/skill.generated.js +2 -2
  72. package/dist/providers/gmail/index.js +11 -9
  73. package/dist/providers/gmail/skill.generated.js +2 -2
  74. package/dist/providers/mixpanel/index.js +1 -1
  75. package/dist/providers/mixpanel/skill.generated.js +3 -3
  76. package/dist/providers/notion/index.d.ts +7 -0
  77. package/dist/providers/notion/index.js +100 -11
  78. package/dist/providers/notion/rest.js +45 -5
  79. package/dist/providers/notion/skill.generated.js +2 -2
  80. package/dist/providers/notion/value-safety.js +30 -5
  81. package/dist/providers/overflow/index.js +2 -2
  82. package/dist/providers/overflow/skill.generated.js +2 -2
  83. package/dist/providers/planning-center/index.js +2 -2
  84. package/dist/providers/planning-center/skill.generated.js +4 -4
  85. package/dist/providers/revenuecat/skill.generated.js +2 -2
  86. package/dist/providers/sheets/index.d.ts +3 -0
  87. package/dist/providers/sheets/index.js +7 -4
  88. package/dist/providers/sheets/skill.generated.js +2 -2
  89. package/dist/providers/slides/index.js +4 -4
  90. package/dist/providers/slides/skill.generated.js +2 -2
  91. package/dist/providers/stripe/rest.js +9 -2
  92. package/dist/providers/stripe/skill.generated.js +1 -1
  93. package/dist/providers/stripe/value-safety.js +22 -0
  94. package/dist/providers/tithely/index.js +1 -1
  95. package/dist/providers/vercel/index.js +1 -1
  96. package/dist/providers/vercel/rest.js +20 -2
  97. package/dist/providers/vercel/skill.generated.js +2 -2
  98. package/dist/providers/vercel/value-safety.js +22 -0
  99. package/dist/registry.d.ts +2 -3
  100. package/dist/registry.js +23 -9
  101. package/dist/routes/access-tokens.js +82 -75
  102. package/dist/routes/activity-disclosure.js +17 -18
  103. package/dist/routes/activity.js +34 -54
  104. package/dist/routes/actor-labels.js +33 -4
  105. package/dist/routes/agent-path.js +43 -0
  106. package/dist/routes/connect.js +16 -12
  107. package/dist/routes/credentials.js +16 -40
  108. package/dist/routes/mcp.js +161 -104
  109. package/dist/routes/oauth-management.js +11 -5
  110. package/dist/routes/oauth.js +5 -11
  111. package/dist/routes/operator.js +24 -23
  112. package/dist/routes/shared.d.ts +60 -20
  113. package/dist/routes/shared.js +187 -96
  114. package/dist/routes/ui-config.js +437 -97
  115. package/dist/routes/ui-data.js +2 -2
  116. package/dist/routes/ui.js +9 -16
  117. package/dist/runtime/call-admission.js +9 -0
  118. package/dist/server.js +5 -0
  119. package/dist/skills.js +10 -0
  120. package/dist/storage/keys.js +9 -1
  121. package/dist/storage/sql.js +5 -1
  122. package/dist/types.d.ts +29 -7
  123. package/dist/ui.d.ts +1 -1
  124. package/dist/ui.js +23 -16
  125. package/dist/usage-guide.js +1 -1
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/dist/workspace-address.js +9 -0
  129. package/dist/workspace-subject.js +23 -0
  130. package/documentation/README.md +47 -23
  131. package/documentation/architecture.md +371 -61
  132. package/documentation/auth.md +897 -249
  133. package/documentation/code-mode.md +37 -16
  134. package/documentation/deploying.md +126 -25
  135. package/documentation/integrating.md +97 -23
  136. package/documentation/meta-tools.md +50 -31
  137. package/documentation/migration-0.33.md +352 -0
  138. package/documentation/migration-0.34.md +67 -0
  139. package/documentation/operating.md +3 -1
  140. package/documentation/operator-tests.md +147 -34
  141. package/documentation/operator-ui.md +519 -64
  142. package/documentation/provider-auth-migration.md +37 -23
  143. package/documentation/provider-migration-0.29.md +2 -2
  144. package/examples/worker/AGENTS.md +37 -9
  145. package/examples/worker/README.md +127 -61
  146. package/examples/worker/src/connecta.config.ts +40 -23
  147. package/examples/worker/wrangler.jsonc +4 -1
  148. package/package.json +19 -18
  149. package/templates/node/.env.example +3 -0
  150. package/templates/node/AGENTS.md +33 -6
  151. package/templates/node/Dockerfile +2 -2
  152. package/templates/node/README.md +80 -52
  153. package/templates/node/docker-compose.yml +3 -2
  154. package/templates/node/package.json +4 -3
  155. package/templates/node/src/adopt-token.ts +23 -0
  156. package/templates/node/src/connecta.config.ts +28 -12
  157. package/templates/node/src/rotate-token.ts +17 -0
  158. package/templates/node/src/provision-token.ts +0 -17
@@ -137,8 +137,8 @@ export function uiData(registry, baseUrl, options) {
137
137
  const fields = credentialFields();
138
138
  return Effect.succeed({
139
139
  ...card,
140
- ...(fields?.length ? { fields } : {}),
141
- configured: false,
140
+ ...(fields?.length ? { fields: fields.map((field) => ({ ...field, configured: null })) } : {}),
141
+ configured: null,
142
142
  removable: true,
143
143
  testable: testRule.mode !== null,
144
144
  error: "Stored credential could not be read.",
package/dist/routes/ui.js CHANGED
@@ -9,7 +9,7 @@ import { CONNECTA_VERSION } from "../version.js";
9
9
  import { CONNECTA_FAVICON_ICO } from "../favicon.js";
10
10
  import { CONNECTA_FAVICON_SVG, operatorPageForPath, renderUiHtml } from "../ui.js";
11
11
  import { authorized, refuse, scopeFor, serveOperator, visibleRegistry, } from "./operator.js";
12
- import { mayManageConnector, privateJson } from "./shared.js";
12
+ import { managesAgentTokens, mayManageConnector, privateJson, readsActivity } from "./shared.js";
13
13
  import { routeUiConfig } from "./ui-config.js";
14
14
  import { uiData } from "./ui-data.js";
15
15
  /**
@@ -84,7 +84,8 @@ export async function routeUi(context) {
84
84
  ? opts.config.auth.find((provider) => provider.uiAuth?.kind === "cloudflare-access")?.uiAuth
85
85
  : undefined;
86
86
  const uiAuth = ambient ??
87
- opts.config.auth.find((provider) => provider.uiAuth && provider.uiAuth.kind !== "cloudflare-access")?.uiAuth;
87
+ opts.config.auth.find((provider) => provider.uiAuth && provider.uiAuth.kind !== "cloudflare-access")?.uiAuth ??
88
+ opts.config.auth.find((provider) => provider.uiAuth?.kind === "cloudflare-access")?.uiAuth;
88
89
  const mcpUrl = new URL("/mcp", baseUrl).toString();
89
90
  const clerkOrigin = uiAuth?.kind === "clerk" && isSafeHttpsUrl(uiAuth.frontendApiUrl)
90
91
  ? new URL(uiAuth.frontendApiUrl).origin
@@ -115,14 +116,14 @@ function operatorView({ opts }, authz, registry) {
115
116
  const visible = registry.listConnectors();
116
117
  const mayManage = (id) => {
117
118
  const connector = registry.getConnector(id);
118
- return Boolean(connector && mayManageConnector(authz, connector));
119
+ return Boolean(connector && mayManageConnector(opts, authz, connector));
119
120
  };
120
121
  const permissions = (connector) => ({
121
122
  use: true,
122
123
  manageSharedAuth: connector.authScope !== "personal" && mayManage(connector.id),
123
124
  connectPersonal: connector.authScope === "personal" && mayManage(connector.id),
124
125
  });
125
- const activityEnabled = Boolean(opts.config.activity?.store.list) && authz.operator;
126
+ const activityEnabled = Boolean(opts.config.activity?.store.list) && readsActivity(opts, authz);
126
127
  const credentialManagement = visible.some((c) => c.credential && mayManage(c.id))
127
128
  ? opts.config.vault
128
129
  ? "available"
@@ -169,17 +170,9 @@ function summary(context) {
169
170
  const authz = yield* authorized(context);
170
171
  const registry = yield* visibleRegistry(context, authz);
171
172
  const { visible, mayManage, permissions, activityEnabled, credentialManagement } = operatorView(context, authz, registry);
172
- // Setup commands are offered per pool, but only for pools this identity's
173
- // grant admits: `/mcp/<name>` answers every other name with one flat 404 so
174
- // a credential cannot enumerate them, and the page must not undo that.
175
- const pools = [];
176
- for (const [name, pool] of opts.pools ?? []) {
177
- const granted = yield* Effect.tryPromise(async () => (await pool.grant(authz.identity)) === true).pipe(
178
- // A throwing grant is a refusal at the endpoint, and so a refusal here.
179
- Effect.orElseSucceed(() => false));
180
- if (granted)
181
- pools.push(name);
182
- }
173
+ // Only maintainers reach here (#837); setup commands are offered for every
174
+ // declared pool, since they administer them all.
175
+ const pools = [...opts.pools.keys()];
183
176
  return privateJson({
184
177
  ...(pools.length ? { pools } : {}),
185
178
  serverInfo: opts.config.serverInfo,
@@ -187,7 +180,7 @@ function summary(context) {
187
180
  activityEnabled,
188
181
  ...(opts.config.accessTokens
189
182
  ? {
190
- accessTokenManagement: authz.accessTokenManagement && authz.identity.principal ? "available" : "requires_operator",
183
+ accessTokenManagement: managesAgentTokens(opts, authz) ? "available" : "requires_operator",
191
184
  }
192
185
  : {}),
193
186
  credentialManagement,
@@ -12,6 +12,15 @@
12
12
  // shared queue between them was refused.
13
13
  import { Effect } from "effect";
14
14
  let program;
15
+ const budgetCharges = new WeakMap();
16
+ /** Bind budget-only admission to the partition already owned by this permit. */
17
+ export function provideCallBudgetCharge(permit, charge) {
18
+ budgetCharges.set(permit, charge);
19
+ }
20
+ /** Charge an internal request without acquiring another concurrency permit. */
21
+ export function chargeCallBudget(permit, signal) {
22
+ return Effect.clockWith((clock) => budgetCharges.get(permit)?.(clock.currentTimeMillisUnsafe(), signal) ?? Effect.void);
23
+ }
15
24
  // What the live Clock reads, looked up on every call rather than bound once,
16
25
  // so a Date faked after a caller queued (vitest's setSystemTime) still counts.
17
26
  const liveNow = () => Date.now();
package/dist/server.js CHANGED
@@ -2,6 +2,7 @@ import { Effect } from "effect";
2
2
  import { notFoundResponse } from "./branding.js";
3
3
  import { aggregateCallAdmissionSnapshots } from "./call-admission.js";
4
4
  import { isAdmittingExecutor } from "./executor-admission.js";
5
+ import { isAgentPath } from "./routes/agent-path.js";
5
6
  import { createMcpRoute, MCP_CORS_HEADERS } from "./routes/mcp.js";
6
7
  import { routeOAuthCallback } from "./routes/oauth.js";
7
8
  import { routeOAuthClientMetadata } from "./routes/oauth-client-metadata.js";
@@ -59,6 +60,10 @@ export function createFetchHandler(opts) {
59
60
  };
60
61
  const route = (context) => Effect.gen(function* () {
61
62
  const { request, path, baseUrl } = context;
63
+ // The agent namespace is routed before anything else, so no path
64
+ // Cloudflare Access bypasses as `/agent/*` can reach a human route.
65
+ if (isAgentPath(path))
66
+ return (yield* routeMcp.handle(context)) ?? notFoundResponse(request, opts.config);
62
67
  const clientMetadata = routeOAuthClientMetadata(context);
63
68
  if (clientMetadata)
64
69
  return clientMetadata;
package/dist/skills.js CHANGED
@@ -13,6 +13,16 @@ const RECOVERY = 'When an auth error includes a handoff, give it to the user or
13
13
  * and describe and refuses every write call. Guides may still name them.
14
14
  */
15
15
  export const CONNECTA_INSTRUCTIONS = `${ROUTE} On a writable endpoint, programs and call_destructive_tool may write. On a read-only endpoint, search omits write tools and every write call is refused; guides may still name them. The execute_code description says which this endpoint is. Never repeat a write to recover its output. ${RECOVERY}`;
16
+ /**
17
+ * The instructions one endpoint serves: {@link CONNECTA_INSTRUCTIONS}, then
18
+ * the admitted pool's `description` after a fixed lead-in. Plain `/mcp` and a
19
+ * pool without a description serve the constant unchanged. The description
20
+ * comes from deployment config for the pool the request was admitted through;
21
+ * nothing a request sends reaches it.
22
+ */
23
+ export function endpointInstructions(description) {
24
+ return description === undefined ? CONNECTA_INSTRUCTIONS : `${CONNECTA_INSTRUCTIONS} This endpoint: ${description}`;
25
+ }
16
26
  export { USAGE_SKILL } from "./usage-guide.js";
17
27
  /** True when at least one of `connectors` carries a usage guide. */
18
28
  export function hasConnectorGuides(connectors) {
@@ -164,7 +164,12 @@ export const oauthHandoffKeys = {
164
164
  },
165
165
  handoff: (connectorId, stateHash) => validateStorageKey(`oauth-handoff:v1:${connectorId}:${stateHash}`),
166
166
  };
167
- /** Connecta-issued `cta_` access tokens: records, digest lookups, capacity. */
167
+ /**
168
+ * Connecta-issued `cta_` access tokens: token records, digest lookups, one
169
+ * slot per declared agent naming its live record, and a never-deleted claim per
170
+ * legacy record an adoption has taken. `active` is the capacity
171
+ * ledger releases before 0.33 wrote; nothing writes it now.
172
+ */
168
173
  export const accessTokenKeys = {
169
174
  family: {
170
175
  name: "access-token",
@@ -178,6 +183,9 @@ export const accessTokenKeys = {
178
183
  recordPrefix: "access-token:v1:record:",
179
184
  record: (id) => validateStorageKey(`access-token:v1:record:${id}`),
180
185
  lookup: (tokenHash) => validateStorageKey(`access-token:v1:lookup:${tokenHash}`),
186
+ agent: (name) => validateStorageKey(`access-token:v1:agent:${name}`),
187
+ /** Write-once claim that a legacy token record has been taken by one agent's adoption. */
188
+ adopted: (legacyRecordId) => validateStorageKey(`access-token:v1:adopted:${legacyRecordId}`),
181
189
  active: "access-token:v1:active",
182
190
  };
183
191
  /**
@@ -492,7 +492,11 @@ export function sqlActivityStore(driver, kind, options = {}) {
492
492
  }
493
493
  });
494
494
  return {
495
- describe: () => ({ kind, retentionDays }),
495
+ describe: () => ({
496
+ kind,
497
+ retentionDays,
498
+ optionSources: { retentionDays: options.retentionDays === undefined ? "default" : "config" },
499
+ }),
496
500
  async record(event) {
497
501
  await ensure();
498
502
  const occurredAtMs = Date.parse(event.occurredAt);
package/dist/types.d.ts CHANGED
@@ -727,6 +727,11 @@ export type AuthResult = {
727
727
  sessionCookies?: readonly string[];
728
728
  /** Human owner represented by a non-interactive access credential. */
729
729
  principal?: IdentityReference;
730
+ /**
731
+ * Declared agent name. Only the built-in `accessTokens()` adapter may
732
+ * set it; a result from any other provider that carries it is refused.
733
+ */
734
+ agent?: string;
730
735
  } | {
731
736
  ok: false;
732
737
  response: Response;
@@ -747,6 +752,12 @@ export interface AuthenticatedIdentity {
747
752
  subject?: IdentityReference;
748
753
  /** Human owner of personal connector authentication. */
749
754
  principal?: IdentityReference;
755
+ /**
756
+ * Name of the code-declared agent this caller is, from `agents`. Set only
757
+ * when the built-in access-token adapter admitted a `cta_` token bound to
758
+ * a currently declared agent; an agent never has a `principal`.
759
+ */
760
+ agent?: string;
750
761
  interactive: boolean;
751
762
  }
752
763
  /** Public browser-auth configuration exposed to connecta's status UI. */
@@ -811,7 +822,7 @@ export interface ConnectaBranding {
811
822
  description?: string;
812
823
  /**
813
824
  * Browser tab title and page meta name. Defaults to
814
- * `"<productName> — <ownerName>"`, or just `productName` when no owner is set.
825
+ * `"<productName> · <ownerName>"`, or just `productName` when no owner is set.
815
826
  */
816
827
  pageTitle?: string;
817
828
  /**
@@ -821,14 +832,18 @@ export interface ConnectaBranding {
821
832
  * elsewhere (it replaces the `/favicon.svg` link in the page head; the
822
833
  * `/favicon.*` routes still serve whatever `svg`/`ico` provide). `href` must
823
834
  * be an absolute `http(s)` URL or a root-relative path; anything else falls
824
- * back to the default mark.
835
+ * back to the default mark. With `svg` set, the top bar shows it beside the
836
+ * name; the drawn mark shows only when no branding is set.
825
837
  */
826
838
  favicon?: {
827
839
  svg?: string;
828
840
  ico?: Uint8Array;
829
841
  href?: string;
830
842
  };
831
- /** `theme-color` meta value. Defaults to "#ffffff". */
843
+ /**
844
+ * `theme-color` meta value. Unset, pages follow the page background:
845
+ * `#f6f4ef` in light and `#12110f` in dark.
846
+ */
832
847
  themeColor?: string;
833
848
  /**
834
849
  * Operator-page appearance. Every field is optional, and a rejected value
@@ -849,18 +864,25 @@ export interface ConnectaBranding {
849
864
  export interface ConnectaTheme {
850
865
  /**
851
866
  * The brand color: links, focus rings, primary actions, the active nav item.
852
- * Hex only (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Defaults to `#2f5fe0`.
867
+ * Hex only (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Defaults to `#4c44e0`
868
+ * (`#8c86ff` in dark). Text on it and accent text in dark derive from it.
853
869
  */
854
870
  accent?: string;
855
871
  /**
856
872
  * Corner rounding for cards, inputs, and buttons. A CSS length (`10px`,
857
873
  * `0.5rem`) or a bare number read as pixels. `0` restores square corners.
858
- * Defaults to `10px`.
874
+ * Defaults to `14px`.
859
875
  */
860
876
  radius?: string | number;
861
- /** Body font stack. A plain CSS font-family list. Defaults to a system stack. */
877
+ /**
878
+ * Body font stack. A plain CSS font-family list. Defaults to Hanken
879
+ * Grotesk, served with the operator UI, then a system stack.
880
+ */
862
881
  fontFamily?: string;
863
- /** Monospace font stack for addresses, ids, and the endpoint URL. */
882
+ /**
883
+ * Monospace font stack for addresses, ids, and the endpoint URL. Defaults
884
+ * to JetBrains Mono, served with the operator UI, then a system stack.
885
+ */
864
886
  monoFamily?: string;
865
887
  /**
866
888
  * `"system"` (the default) follows the operator's OS setting; `"light"` and
package/dist/ui.d.ts CHANGED
@@ -49,4 +49,4 @@ _nonce?: string, page?: OperatorPage): string;
49
49
  export declare function operatorUi(options?: {
50
50
  branding?: ConnectaBranding;
51
51
  }): OperatorSurface;
52
- export type { OperatorUiContract, OperatorConnectorOverlay, OperatorTool, OperatorViewer, OperatorGrant, OperatorPool, OperatorLastCall, } from "./operator-ui/contract.js";
52
+ export type { OperatorUiContract, OperatorUiContractV2, OperatorRole, OperatorMember, OperatorPoolV2, OperatorRootEndpoint, OperatorMemberConnector, OperatorMemberManage, OperatorAgent, OperatorSharedSource, OperatorRunsAs, OperatorConnectorPoolCell, OperatorConnectorOverlay, OperatorTool, OperatorGrant, OperatorLastCall, } from "./operator-ui/contract.js";
package/dist/ui.js CHANGED
@@ -4,33 +4,34 @@ import { routeCredentials } from "./routes/credentials.js";
4
4
  import { routeOAuthManagement } from "./routes/oauth-management.js";
5
5
  import { uiData } from "./routes/ui-data.js";
6
6
  import { runEdge } from "./runtime/run.js";
7
- import { OPERATOR_UI_STYLE_PATH, OPERATOR_UI_SCRIPT_PATH, OPERATOR_UI_NOTICES_PATH } from "./operator-ui/generated.js";
7
+ import { OPERATOR_UI_STYLE_PATH, OPERATOR_UI_SCHEME_PATH, OPERATOR_UI_SCRIPT_PATH, OPERATOR_UI_NOTICES_PATH, } from "./operator-ui/generated.js";
8
8
  export { filterUiConnectors, } from "./operator-ui/model.js";
9
- import { escapeHtml, isSafeHttpsUrl, renderPage, resolveBranding } from "./branding.js";
9
+ import { brandMark, escapeHtml, isSafeHttpsUrl, renderPage, resolveBranding } from "./branding.js";
10
10
  export { CONNECTA_FAVICON_SVG, resolveBranding, isSafeHttpUrl, isSafeHttpsUrl, isSafeIconHref } from "./branding.js";
11
11
  const OPERATOR_PAGE_LABELS = {
12
12
  overview: "Overview",
13
+ endpoints: "Endpoints",
13
14
  connections: "Connectors",
14
15
  connector: "Connector",
15
- tools: "Tools",
16
- access: "Access",
17
- config: "Config",
18
- tokens: "Access tokens",
16
+ settings: "Settings",
17
+ tokens: "Tokens",
19
18
  activity: "Activity",
20
19
  };
21
20
  export function operatorPageForPath(path) {
22
21
  if (path === "/")
23
22
  return "overview";
23
+ if (path === "/endpoints")
24
+ return "endpoints";
24
25
  if (path === "/connectors")
25
26
  return "connections";
26
27
  if (/^\/connectors\/[a-z0-9_-]+$/.test(path))
27
28
  return "connector";
28
29
  if (path === "/tools")
29
- return "tools";
30
+ return "connections";
30
31
  if (path === "/access")
31
- return "access";
32
- if (path === "/config")
33
- return "config";
32
+ return "endpoints";
33
+ if (path === "/config" || path === "/settings")
34
+ return "settings";
34
35
  if (path === "/tokens")
35
36
  return "tokens";
36
37
  if (path === "/activity")
@@ -38,7 +39,7 @@ export function operatorPageForPath(path) {
38
39
  return undefined;
39
40
  }
40
41
  export function operatorPageTitle(page, configuredTitle) {
41
- return `${OPERATOR_PAGE_LABELS[page]} — ${configuredTitle}`;
42
+ return `${OPERATOR_PAGE_LABELS[page]} · ${configuredTitle}`;
42
43
  }
43
44
  /** Report the verdict the registry stored, independent of whether an endpoint is read-only. */
44
45
  export function uiToolSafety(definition) {
@@ -101,7 +102,7 @@ function jsonForInlineScript(value) {
101
102
  */
102
103
  export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding,
103
104
  /** @deprecated Scripts load from same-origin assets; ignored. */
104
- _nonce, page = "overview") {
105
+ _nonce, page = "endpoints") {
105
106
  const clerk = uiAuth?.kind === "clerk" ? uiAuth : undefined;
106
107
  // The Clerk loader's origin. A value that fails the gate is dropped rather
107
108
  // than escaped into the page: the loader tag is simply not emitted, the gate
@@ -130,13 +131,15 @@ _nonce, page = "overview") {
130
131
  : "";
131
132
  // The shared layout owns the head, scheme, theme, and masthead; the shell
132
133
  // adds its own bundle (which already imports the shared tokens), the nav
133
- // mount, and the app.
134
+ // mount, and the app. The scheme initializer blocks, ahead of the
135
+ // stylesheet, so a saved color scheme holds from the first paint.
134
136
  return renderPage(branding, {
135
137
  title: operatorPageTitle(page, brand.pageTitle),
136
138
  uiMounted: true,
137
139
  operatorShell: true,
138
140
  styles: "",
139
- head: `<link rel="stylesheet" href="${OPERATOR_UI_STYLE_PATH}"><link rel="license" href="${OPERATOR_UI_NOTICES_PATH}">` +
141
+ head: `<script src="${OPERATOR_UI_SCHEME_PATH}"></script>` +
142
+ `<link rel="stylesheet" href="${OPERATOR_UI_STYLE_PATH}"><link rel="license" href="${OPERATOR_UI_NOTICES_PATH}">` +
140
143
  clerkScript,
141
144
  skipTo: { id: "operatorContent", label: "Skip to operator page" },
142
145
  mastheadEnd: '<div id="operatorNav"></div>',
@@ -146,8 +149,8 @@ _nonce, page = "overview") {
146
149
  <h1>${OPERATOR_PAGE_LABELS[page]}</h1>
147
150
  <div class="lead-copy">
148
151
  <p>${escapeHtml(brand.description)}</p>
149
- <noscript><p class="msg">The operator pages need JavaScript. Nothing else here
150
- does — agents reach this deployment through <span class="mono">/mcp</span>.</p></noscript>
152
+ <noscript><p class="msg">Operator pages need JavaScript. Agents connect at
153
+ <span class="mono">/mcp</span>.</p></noscript>
151
154
  </div>
152
155
  </div>
153
156
  </main>
@@ -156,8 +159,10 @@ _nonce, page = "overview") {
156
159
  auth,
157
160
  mcpUrl,
158
161
  initialPage: page,
162
+ schemePinned: brand.theme.colorScheme !== "system",
159
163
  titleSuffix: brand.pageTitle,
160
164
  productName: brand.productName,
165
+ brandMark: brandMark(branding, true),
161
166
  productDescription: brand.description,
162
167
  productOperatorLabel: brand.productName + " operator",
163
168
  })}</script><script defer src="${OPERATOR_UI_SCRIPT_PATH}"></script>`,
@@ -181,11 +186,13 @@ export function operatorUi(options = {}) {
181
186
  options = assertKnownOptions(options, "operatorUi()", OPERATOR_UI_OPTIONS);
182
187
  const reservedPaths = [
183
188
  "/",
189
+ "/endpoints",
184
190
  "/connectors",
185
191
  "/connectors/*",
186
192
  "/tools",
187
193
  "/access",
188
194
  "/config",
195
+ "/settings",
189
196
  "/ui",
190
197
  "/ui/*",
191
198
  "/favicon.svg",
@@ -77,7 +77,7 @@ async () => {
77
77
 
78
78
  Host-call budget exhaustion ends the run with budget_exceeded. Catch and
79
79
  allSettled cannot recover it or return partial output. Bound batches before
80
- starting them; search, describe, call, result, and skill spend host calls.
80
+ starting them; every guest call except emit spends host calls.
81
81
  Split remaining reads. Never replay writes for their output.
82
82
 
83
83
  ## Per-connector guides
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const CONNECTA_VERSION = "0.32.0";
1
+ export declare const CONNECTA_VERSION = "0.34.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs.
2
2
  // Workers-safe: no runtime filesystem read or deployment override.
3
- export const CONNECTA_VERSION = "0.32.0";
3
+ export const CONNECTA_VERSION = "0.34.0";
@@ -0,0 +1,9 @@
1
+ // The one rule for a Google Workspace address, shared by the Workspace
2
+ // providers' fixed `subject` and a pool's `owner` (#837, decisions/0006), so
3
+ // config accepts exactly the addresses a delegated token can be minted for.
4
+ /** A plausible mailbox: one `@`, no whitespace or control characters. */
5
+ const ADDRESS = /^[^\s@<>()",;:\\[\]]+@[^\s@<>()",;:\\[\]]+\.[^\s@<>()",;:\\[\]]+$/;
6
+ /** Whether `value`, already trimmed, is a Workspace email address. */
7
+ export function isWorkspaceAddress(value) {
8
+ return typeof value === "string" && ADDRESS.test(value);
9
+ }
@@ -0,0 +1,23 @@
1
+ // Whose account a Google Workspace connector acts as, for the operator
2
+ // matrix (#837, decisions/0006). The Workspace providers tag the connector
3
+ // they build; nothing else can, and nothing a request sends reaches this.
4
+ // Read only by the operator contract, which never shows a mapping function.
5
+ const tagged = new WeakMap();
6
+ /** Tag a Workspace connector with its subject kind, returning it unchanged. */
7
+ export function tagWorkspaceSubject(connector, subject) {
8
+ tagged.set(connector, typeof subject === "string" ? { kind: "fixed", subject } : { kind: "mapping" });
9
+ return connector;
10
+ }
11
+ /**
12
+ * The subject kind of a tagged connector, or undefined for any other. Walks
13
+ * the prototype chain, since provider construction may wrap a connector it
14
+ * cannot restamp in an object that inherits from it.
15
+ */
16
+ export function workspaceSubjectOf(connector) {
17
+ for (let current = connector; current; current = Object.getPrototypeOf(current)) {
18
+ const subject = tagged.get(current);
19
+ if (subject)
20
+ return subject;
21
+ }
22
+ return undefined;
23
+ }
@@ -1,34 +1,58 @@
1
1
  # Agent documentation
2
2
 
3
- Choose a route by the work you are doing. Each route identifies the task guide
4
- and contracts needed to operate an endpoint, set up a deployment, connect a
5
- service, or change Connecta itself.
3
+ Start here when operating a deployment installed with `@zackbart/connecta`.
4
+ All operator procedures below are available in the installed package; paths in
5
+ this directory are relative to `node_modules/@zackbart/connecta/documentation/`.
6
+ For an existing endpoint, start with [Operating an endpoint](./operating.md).
7
+ For a new deployment, start with [Deploying Connecta](./deploying.md).
6
8
 
7
- | Your task | Start here | Read next when needed |
8
- | --------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
9
- | Use an existing endpoint to answer a question or perform a task | [Operating an endpoint](./operating.md) | [Meta-tools](./meta-tools.md), [guest API](./code-mode.md) |
10
- | Set up, configure, upgrade, or diagnose a deployment | [Deploying Connecta](./deploying.md) | [Auth](./auth.md), [operator UI](./operator-ui.md) |
11
- | Add a maintained integration, remote MCP, or HTTP API to a deployment | [Integrating services](./integrating.md) | Provider skill, [classification](./architecture.md#providers-and-reviewed-classification), [downstream auth](./auth.md#shared-and-personal-auth) |
12
- | Change Connecta's source, tests, or published contracts | [AGENTS.md](https://github.com/zackbart/connecta/blob/main/AGENTS.md) | [Principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md), [architecture](./architecture.md), then the affected contract |
9
+ ## Operator tasks
10
+
11
+ | Task | Self-contained procedure |
12
+ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
13
+ | Add or remove a connector | [Change the connector roster](./integrating.md#add-or-remove-a-connector) |
14
+ | Organize an agent's pools and tiers | [Organizing an agent's pools](./auth.md#organizing-an-agents-pools) |
15
+ | Add a person (maintainer or pool member) | [Add a person](./auth.md#add-a-person), [member page](./operator-ui.md#use-the-member-page) |
16
+ | Connect a shared credential | [Connect a shared credential](./auth.md#connect-a-shared-credential) |
17
+ | Issue, rotate, or revoke an agent token | [Manage an agent token](./auth.md#issue-rotate-or-revoke-an-agent-token) |
18
+ | Upgrade between versions | [Upgrade an existing deployment](./deploying.md#upgrade-an-existing-deployment), including [migration to 0.33](./migration-0.33.md) |
19
+ | Diagnose with `connecta doctor` | [Verify and diagnose](./deploying.md#verify-and-diagnose) |
20
+
21
+ ## Audience rule
22
+
23
+ Shipped docs (`documentation/`, `templates/`, `examples/worker/`, and the root
24
+ `README.md`) are written first for an agent operating a deployment from the
25
+ installed package. Operator conventions must be self-contained rules plus a
26
+ neutral example. Decision records and issues explain why; no operator task
27
+ requires them, `spec/`, `.claude/`, or the Connecta source checkout. Those
28
+ three directories do not ship. Contributor source maps, INV citations, and
29
+ implementation/test evidence belong under clearly labeled contributor
30
+ headings and are optional for operators. Use `assistant`, `alice`, `bob`,
31
+ `crm`, `tickets`, `notes_alice`, and `alice@example.com` in examples; never
32
+ real deployment, client, person, or organization names. Maintained provider
33
+ product names stay.
13
34
 
14
35
  ## Contract reference
15
36
 
16
- | Guide | Owns |
17
- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
18
- | [Meta-tools](./meta-tools.md) | Six top-level tools, discovery, direct-call results, paging, skills, and recovery |
19
- | [Code mode](./code-mode.md) | JavaScript guest API, executor requirements, output, retries, cancellation, limits, and writes |
20
- | [Auth](./auth.md) | Inbound identity, grants, pools, machine tokens, credential ownership, and downstream OAuth |
21
- | [Architecture](./architecture.md) | Request lifetimes, routes, storage, configuration, import boundaries, and Effect implementation |
22
- | [Operator UI](./operator-ui.md) | Optional UI setup, scoped configuration and live-data contract, activity disclosure, and UI implementation |
23
- | [Operator UI tests](./operator-tests.md) | Browser state matrix, real-server flows, and snapshot maintenance |
24
- | [Provider migration for 0.29](./provider-migration-0.29.md) | Upgrade actions for Notion, Vercel, and Cloudflare; provider reconciliation tables supply exact name mappings |
25
- | [Provider auth migration](./provider-auth-migration.md) | Upgrade actions when `auth` selects hosted MCP or Connecta's REST connector, Stripe and Vercel |
37
+ | Guide | Owns |
38
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
39
+ | [Meta-tools](./meta-tools.md) | Six top-level tools, discovery, direct-call results, paging, skills, and recovery |
40
+ | [Code mode](./code-mode.md) | JavaScript guest API, executor requirements, output, retries, cancellation, limits, and writes |
41
+ | [Auth](./auth.md) | Inbound identity, maintainers, pools and members, agents, credential ownership, and downstream OAuth |
42
+ | [Architecture (contributor reference)](./architecture.md) | Request lifetimes, routes, storage, configuration, import boundaries, and Effect implementation |
43
+ | [Operator UI](./operator-ui.md) | Optional UI setup, scoped configuration and live-data contract, activity disclosure, and UI implementation |
44
+ | [Operator UI tests (contributor reference)](./operator-tests.md) | Browser state matrix, real-server flows, and snapshot maintenance |
45
+ | [Migration to 0.33](./migration-0.33.md) | Upgrade actions from 0.32 options to maintainers, members, agents, agent endpoints, and pool owners |
46
+ | [Provider migration for 0.29](./provider-migration-0.29.md) | Upgrade actions for Notion, Vercel, and Cloudflare; provider reconciliation tables supply exact name mappings |
47
+ | [Provider auth migration](./provider-auth-migration.md) | Upgrade actions when `auth` selects hosted MCP or Connecta's REST connector, Stripe and Vercel |
26
48
 
27
49
  Each long contract starts with task routes. Follow those section links before
28
- reading the whole guide. Source and test pointers identify where to check a
29
- claim or implement a change.
50
+ reading the whole guide. Contributor references at the end are optional
51
+ implementation evidence.
52
+
53
+ ## Contributor reference
30
54
 
31
- ## Documentation ownership
55
+ ### Documentation ownership
32
56
 
33
57
  The root [README](../README.md) explains the product for people. It owns what
34
58
  Connecta offers, how the pieces connect, deployment choices, and the setup
@@ -47,7 +71,7 @@ own scoped change procedures. Each [provider folder](https://github.com/zackbart
47
71
  owns its maintained `SKILL.md`, options, tests, fixtures, and review data.
48
72
  Use those details instead of copying provider catalogs into general guides.
49
73
  The provider list in the human README is generated; preserve its marker pair
50
- and run `npm run check:providers-generated` after edits.
74
+ and run `pnpm run check:providers-generated` after edits.
51
75
 
52
76
  When changing behavior, update its owning contract and task guide in the same
53
77
  change. Check examples against current source and point to the tests that