@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
@@ -14,36 +14,29 @@ const REGISTRATION_LABELS = {
14
14
  export function registrationPathLabel(path) {
15
15
  return REGISTRATION_LABELS[path];
16
16
  }
17
- /**
18
- * A connector's source column: the maintained provider with the
19
- * implementation it uses, since a dual provider's `auth` selects either the
20
- * vendor's hosted MCP server or Connecta's REST connector.
21
- */
22
- export function connectorSourceLabel(source) {
23
- if (!source.provider)
24
- return source.kind;
25
- const implementation = source.kind === "remote-mcp" ? "hosted MCP" : source.kind === "api" ? "REST" : source.kind;
26
- return `${source.provider} · ${implementation}`;
27
- }
28
- /** Pages the nav lists. */
17
+ /** Pages the nav lists, in dashboard order. */
29
18
  export const OPERATOR_PAGES = [
30
19
  "overview",
20
+ "endpoints",
31
21
  "connections",
32
- "tools",
33
- "access",
34
22
  "activity",
35
- "config",
23
+ "tokens",
24
+ "settings",
36
25
  ];
37
26
  export const PAGE_META = {
38
27
  overview: { path: "/", label: "Overview" },
28
+ endpoints: { path: "/endpoints", label: "Endpoints" },
39
29
  connector: { path: "/connectors", label: "Connector" },
40
- tools: { path: "/tools", label: "Tools" },
41
- access: { path: "/access", label: "Access" },
42
- config: { path: "/config", label: "Config" },
43
- tokens: { path: "/tokens", label: "Access tokens" },
30
+ settings: { path: "/settings", label: "Settings" },
31
+ tokens: { path: "/tokens", label: "Tokens" },
44
32
  connections: { path: "/connectors", label: "Connectors" },
45
33
  activity: { path: "/activity", label: "Activity" },
46
34
  };
35
+ /** Legacy links remain valid, with the browser replacing their URL. */
36
+ export function canonicalOperatorPath(path) {
37
+ return ({ "/access": "/endpoints", "/tools": "/connectors", "/config": "/settings" }[path] ??
38
+ path);
39
+ }
47
40
  /**
48
41
  * What each page is for, in one line under its heading — on the gate as well
49
42
  * as once signed in, so the page an operator lands on reads the same before
@@ -51,10 +44,18 @@ export const PAGE_META = {
51
44
  */
52
45
  export function pageDescription(page, productDescription) {
53
46
  if (page === "activity") {
54
- return "Every connector tool call, by who made it and how it ended. Arguments and results are never stored.";
47
+ return "Tool calls by caller and result. Arguments and results are not stored.";
55
48
  }
56
49
  return productDescription;
57
50
  }
51
+ /**
52
+ * Signed in, but neither a maintainer nor a member of any endpoint: who to
53
+ * ask, and that this is not an error to retry.
54
+ */
55
+ export function noAccessCopy(kind, productName) {
56
+ const who = kind === "clerk" ? "This account" : kind === "cloudflare-access" ? "This Cloudflare Access identity" : "This token";
57
+ return `${who} has no access to ${productName}. Ask a maintainer to add you to an endpoint, then reload.`;
58
+ }
58
59
  /** What the gate says while the session is checked, page by page. */
59
60
  export function checkingCopy() {
60
61
  return "Checking your session…";
@@ -64,7 +65,7 @@ export function pageForPath(path) {
64
65
  return "connector";
65
66
  if (path === "/tokens")
66
67
  return "tokens";
67
- const match = OPERATOR_PAGES.find((page) => PAGE_META[page].path === path);
68
+ const match = OPERATOR_PAGES.find((page) => PAGE_META[page].path === canonicalOperatorPath(path));
68
69
  return match ?? "connections";
69
70
  }
70
71
  export function info(message) {
@@ -74,8 +75,8 @@ export function failure(message, fix) {
74
75
  return fix ? { message, tone: "error", fix } : { message, tone: "error" };
75
76
  }
76
77
  const REFUSED_COPY = {
77
- oauth_disconnect: "Disconnect didn't finish. If the service refused it, the deployment's log has its reply.",
78
- oauth_reconnect: "Authorization couldn't start. If the service refused it, the deployment's log has its reply.",
78
+ oauth_disconnect: "Disconnect failed. The deployment log has the reason.",
79
+ oauth_reconnect: "Authorization couldn't start. The deployment log has the reason.",
79
80
  credential_test: "The credential test couldn't run.",
80
81
  };
81
82
  /**
@@ -85,20 +86,21 @@ const REFUSED_COPY = {
85
86
  export function oauthDoneNotice(action, answer, opened = true) {
86
87
  if (action === "oauth_disconnect") {
87
88
  if (answer?.code === "oauth_revocation_failed")
88
- return info("Disconnected locally. Provider revocation could not be confirmed. Revoke the grant in the provider's console.");
89
- return info("Disconnected. Connect again whenever you are ready.");
89
+ return info("Disconnected here. Revoke the grant in the provider's console too.");
90
+ return info("Disconnected.");
90
91
  }
91
92
  if (answer?.state === "ok")
92
93
  return info("Connected.");
93
- return info(opened
94
- ? "Finish authorizing in the new tab. This page updates when you come back."
95
- : "Your browser blocked the new tab. Open the authorization page from the link here.");
94
+ return info(opened ? "Finish in the new tab." : "The new tab was blocked. Open the link here.");
96
95
  }
97
96
  /** A credential Test's result. `ok` is the only part of the answer it reads. */
98
97
  export function credentialTestNotice(connectorId, answer) {
99
98
  return answer?.ok === true
100
99
  ? info("Credential is valid.")
101
- : failure("Credential test failed: the service rejected the stored credential, or the test couldn't reach it. The deployment's log has the service's reply.", { kind: "credential_test_failed", connectorId });
100
+ : failure("Credential test failed. The deployment log has the reason.", {
101
+ kind: "credential_test_failed",
102
+ connectorId,
103
+ });
102
104
  }
103
105
  /**
104
106
  * A route that refused one of these actions. A credential Test that found
@@ -127,17 +129,19 @@ function credentialRefusedCopy(action, status, problem) {
127
129
  const verb = action === "credential_save" ? "saved" : "removed";
128
130
  if (status === 400 || status === 413 || status === 415) {
129
131
  return action === "credential_save"
130
- ? "The credential wasn't saved: a value is empty or not in the expected format. Check it and save again."
131
- : "The credential wasn't removed. Refresh the page and try again.";
132
+ ? "Not saved. A value is empty or in the wrong format."
133
+ : "Not removed. Refresh and try again.";
132
134
  }
133
135
  if (status === 404) {
134
- return "This connector no longer has a credential slot. Refresh the page to see its current setup.";
136
+ return "This connector no longer has a credential slot. Refresh the page.";
135
137
  }
136
138
  if (status === 503) {
137
- return `Credential storage isn't configured on this deployment, so nothing was ${verb}.`;
139
+ return `No credential storage is configured, so nothing was ${verb}.`;
138
140
  }
139
- return `The credential wasn't ${verb}. Try again; if it keeps failing, the deployment's log has the reason.`;
141
+ return `Not ${verb}. Try again, or check the deployment log.`;
140
142
  }
143
+ /** An action whose permission or target changed before it was sent. */
144
+ export const STALE_COPY = "This changed before sending, so nothing was sent. Try again.";
141
145
  /**
142
146
  * Every row action's failure, in one voice. The page's own failures — a
143
147
  * lapsed session, a denied identity, a dropped connection — read the same for
@@ -145,68 +149,112 @@ function credentialRefusedCopy(action, status, problem) {
145
149
  */
146
150
  export function actionFailedNotice(action, connectorId, facts, productName) {
147
151
  if (facts.kind === "session") {
148
- return failure("Your session has ended. Sign in again, then retry.");
152
+ return failure("Session ended. Sign in again.");
149
153
  }
150
154
  if (facts.kind === "forbidden") {
151
- return failure("You don't have permission to change this connection's authentication.");
155
+ return failure("You can't change this connection's authentication.");
152
156
  }
153
157
  if (facts.kind === "network") {
154
- return failure(`Couldn't reach ${productName}. Check your connection and try again.`);
158
+ return failure(`Couldn't reach ${productName}. Try again.`);
155
159
  }
160
+ if (facts.kind === "stale")
161
+ return failure(STALE_COPY);
156
162
  if (action === "credential_save" || action === "credential_remove") {
157
163
  return failure(credentialRefusedCopy(action, facts.status, facts.problem));
158
164
  }
159
165
  return refusedNotice(action, connectorId, facts.problem);
160
166
  }
161
167
  export function connectorLoadFailureCopy(failure, productName) {
168
+ if (failure === "unavailable")
169
+ return "Session identity is unavailable. Retry.";
162
170
  return failure === "session"
163
- ? "Your session wasn't accepted while loading this connector. Sign in again to see its status."
164
- : `Couldn't reach ${productName} to load this connector. Check your connection, then refresh it.`;
171
+ ? "Session not accepted. Retry, or sign in again."
172
+ : `Couldn't reach ${productName}. Refresh to retry.`;
165
173
  }
166
174
  /** The signed-in page's answer when `/ui/data` could not be read. */
167
175
  export function loadFailureCopy(failure, productName) {
168
176
  return {
169
177
  title: `Couldn't reach ${productName}`,
170
178
  body: failure === "network"
171
- ? "Your browser couldn't connect. Check your connection, then retry."
172
- : "The deployment answered with an error. Retry in a moment; if it keeps failing, the deployment's log has the reason.",
179
+ ? "Your browser couldn't connect. Retry."
180
+ : "The deployment returned an error. Retry, or check its log.",
173
181
  };
174
182
  }
175
183
  /** A collection page's load failure: fixed words, whatever the route said. */
176
184
  export function collectionFailureCopy(_collection, facts, productName) {
177
185
  if (facts.kind === "network") {
178
- return `Couldn't reach ${productName}. Check your connection, then retry.`;
186
+ return `Couldn't reach ${productName}. Retry.`;
179
187
  }
180
188
  if (facts.kind === "session")
181
- return "Your session has ended. Sign in again to see this page.";
189
+ return "Session ended. Sign in again.";
182
190
  if (facts.kind === "forbidden" || facts.status === 404) {
183
- return "Activity isn't available to this identity.";
191
+ return "Activity isn't available to you.";
184
192
  }
185
- return "The deployment answered with an error. Retry in a moment; if it keeps failing, the deployment's log has the reason.";
193
+ return "The deployment returned an error. Retry, or check its log.";
186
194
  }
187
195
  /** The confirm's question, naming the connector by its title. */
188
196
  export function confirmCopy(action, name) {
189
197
  if (action === "oauth_disconnect") {
190
198
  return {
191
- question: `Disconnect ${name}? Its stored grant and any pending authorization are removed, and its tools stop working until it is connected again.`,
199
+ question: `Disconnect ${name}? Its tools stop working until you connect again.`,
192
200
  confirm: "Disconnect",
193
201
  };
194
202
  }
195
203
  if (action === "oauth_restart") {
196
204
  return {
197
- question: `Reconnect ${name}? Its current grant stops working until you finish authorizing in the new tab.`,
205
+ question: `Reconnect ${name}? It stops working until you finish in the new tab.`,
198
206
  confirm: "Reconnect",
199
207
  };
200
208
  }
201
209
  return {
202
- question: `Remove ${name}'s credential? The connector stops authenticating until a replacement is added.`,
210
+ question: `Remove ${name}'s credential? Its tools stop working until you add a new one.`,
203
211
  confirm: "Remove",
204
212
  };
205
213
  }
206
214
  export function accessTokenUnavailableCopy(capability) {
207
215
  return capability === undefined
208
- ? "Access tokens are not configured for this deployment."
209
- : "Token management requires an interactive sign-in and explicit permission.";
216
+ ? "Agent tokens aren't configured."
217
+ : "Managing tokens needs an interactive sign-in with permission.";
218
+ }
219
+ /** Who the contract says this viewer is, or null before it loads. */
220
+ export function operatorRole(state) {
221
+ return state.contract?.role ?? null;
222
+ }
223
+ /**
224
+ * The role pages and navigation render for. The contract's, or, while a
225
+ * refresh that failed has left no contract on screen, maintainer when the
226
+ * last good contract confirmed one and that maintainer's data is still here:
227
+ * their page stays mounted on its own retry state instead of being torn down
228
+ * mid-recovery. Never a request's permission; the store sends maintainer
229
+ * reads only while the contract itself says maintainer.
230
+ */
231
+ export function displayRole(state) {
232
+ if (state.contract)
233
+ return state.contract.role;
234
+ return state.role === "maintainer" && state.data ? "maintainer" : null;
235
+ }
236
+ /**
237
+ * A confirmed maintainer's page is mounted without a contract behind it: a
238
+ * refresh failed, or its read ran past the deadline. The shell shows one
239
+ * recovery banner with Retry above whichever page is open, and that page is
240
+ * stale until a contract confirms the role again.
241
+ */
242
+ export function contractRecovering(state) {
243
+ return !state.contract && displayRole(state) === "maintainer";
244
+ }
245
+ /**
246
+ * Whether Activity is open to this viewer: maintainers only, on a deployment
247
+ * that records history. Members always read "forbidden".
248
+ */
249
+ export function activityReadable(state) {
250
+ return Boolean(state.data?.activityEnabled) && activityAllowed(state);
251
+ }
252
+ /** The contract's half of `activityReadable`: a maintainer, on a deployment that records. */
253
+ export function activityAllowed(state) {
254
+ const contract = state.contract;
255
+ return (contract?.role === "maintainer" &&
256
+ contract.live.activity !== "forbidden" &&
257
+ contract.live.activity !== "unconfigured");
210
258
  }
211
259
  export function initialState(page) {
212
260
  return {
@@ -228,8 +276,10 @@ export function initialState(page) {
228
276
  */
229
277
  function identityScopedState() {
230
278
  return {
279
+ recoveryPending: false,
231
280
  data: null,
232
281
  contract: null,
282
+ role: null,
233
283
  contractPhase: "idle",
234
284
  health: "loading",
235
285
  connectorFilter: "",
@@ -237,6 +287,7 @@ function identityScopedState() {
237
287
  oauthNoticeFor: null,
238
288
  oauthBusy: null,
239
289
  oauthBlocked: null,
290
+ oauthBlockedUrl: null,
240
291
  confirming: null,
241
292
  connectorFailures: {},
242
293
  credentialNotice: null,
@@ -247,7 +298,9 @@ function identityScopedState() {
247
298
  tokenNotice: null,
248
299
  tokens: [],
249
300
  createdToken: null,
250
- tokenRenaming: null,
301
+ createdTokenFor: null,
302
+ tokenConfirm: null,
303
+ unresolvedSlots: [],
251
304
  tokenBusy: false,
252
305
  activityPhase: "idle",
253
306
  activityNotice: null,
@@ -283,7 +336,8 @@ export function withPage(state, page) {
283
336
  ...state,
284
337
  page,
285
338
  createdToken: null,
286
- tokenRenaming: null,
339
+ createdTokenFor: null,
340
+ tokenConfirm: null,
287
341
  tokenNotice: null,
288
342
  credentialEditing: null,
289
343
  credentialNotice: null,
@@ -293,12 +347,12 @@ export function withPage(state, page) {
293
347
  }
294
348
  export function credentialUnavailableCopy(capability) {
295
349
  if (capability === "no_slots") {
296
- return "No connectors declare operator-managed credential slots. Connector credentials remain configuration-as-code until a slot is declared.";
350
+ return "No connector declares a credential slot. Credentials stay in code.";
297
351
  }
298
352
  if (capability === "vault_not_configured") {
299
- return "Credential storage is not configured. Configure a vault before managing connector credentials here.";
353
+ return "No credential vault is configured.";
300
354
  }
301
- return "Credential management requires an interactive user. Bearer-authenticated sessions can inspect connections but cannot manage stored credentials.";
355
+ return "Managing credentials needs an interactive sign-in. A bearer token can only view them.";
302
356
  }
303
357
  /**
304
358
  * The status badge. An auth-needed connector is named by what it needs, so a
@@ -335,12 +389,12 @@ export function connectorStatusTone(status) {
335
389
  * the same kind.
336
390
  */
337
391
  const PROBLEM_COPY = {
338
- connector_unavailable: "Unavailable: its status check or catalog load failed, or did not finish in time. The deployment's log has the downstream error.",
339
- oauth_required: "Needs OAuth authorization: no grant is stored, or the stored grant expired or was revoked.",
340
- credential_required: "Needs a credential: nothing usable is stored in its credential slot.",
341
- auth_required: "Needs authorization. Its secret lives in deployment configuration, not on this page.",
342
- credential_mismatch: "The stored credential does not match the fields this connector declares, so it cannot be used.",
343
- catalog_failed: "Connected, but its tool catalog could not be loaded, so none of its tools are served.",
392
+ connector_unavailable: "Unavailable. Its status check or catalog load failed or timed out. The deployment log has the error.",
393
+ oauth_required: "Needs OAuth authorization. No grant is stored, or it expired or was revoked.",
394
+ credential_required: "Needs a credential. Nothing usable is stored.",
395
+ auth_required: "Needs authorization. Its secret is set in deployment config, not here.",
396
+ credential_mismatch: "The stored credential doesn't match this connector's fields.",
397
+ catalog_failed: "Connected, but its catalog failed to load, so no tools are served.",
344
398
  };
345
399
  export function problemCopy(problem) {
346
400
  return problem ? (PROBLEM_COPY[problem] ?? null) : null;
@@ -355,19 +409,43 @@ export function problemTone(problem) {
355
409
  ? "warn"
356
410
  : "danger";
357
411
  }
412
+ const PROBLEM_LABEL = {
413
+ connector_unavailable: "Unavailable",
414
+ oauth_required: "Authorization needed",
415
+ credential_required: "Credential needed",
416
+ auth_required: "Authorization needed",
417
+ credential_mismatch: "Credential mismatch",
418
+ catalog_failed: "Catalog failed",
419
+ };
420
+ function problemAction(problem) {
421
+ if (problem === "credential_mismatch")
422
+ return "Replace key";
423
+ return problemTone(problem) === "warn" ? "Connect" : "Inspect";
424
+ }
358
425
  /**
359
- * What this identity may do with a connector, in one line. Being able to use it
360
- * is implied by seeing it at all, so the sentence covers authentication, which
361
- * is the part that differs between operators.
426
+ * The verdict the Connectors list, the Overview, Endpoints and a connector's
427
+ * page all show. The server's problem code comes first, then a failed detail
428
+ * read on this page, then a non-ok status, then catalog drift. A problem
429
+ * outranks the status, so a connector whose catalog failed is never
430
+ * "Connected". A failed read marks its connector `connector_unavailable` in
431
+ * the store, so that one code beside a failed read is the page's own and
432
+ * reads "Couldn't load" instead of blaming the downstream.
362
433
  */
363
- export function permissionLabel(connector) {
364
- if (connector.permissions?.manageSharedAuth) {
365
- return "You can manage shared authentication for this connection.";
434
+ export function connectorVerdict(live, signals = {}) {
435
+ if (live.problem && !(signals.failed && live.problem === "connector_unavailable")) {
436
+ return { tone: problemTone(live.problem), label: PROBLEM_LABEL[live.problem], action: problemAction(live.problem) };
366
437
  }
367
- if (connector.permissions?.connectPersonal) {
368
- return "You can connect your own account to this connection.";
438
+ if (signals.failed)
439
+ return { tone: "warn", label: "Couldn't load", action: "Inspect" };
440
+ if (live.status === "loading")
441
+ return { tone: "neutral", label: connectorStatusLabel(live.status), action: null };
442
+ if (live.status !== "ok") {
443
+ const tone = connectorStatusTone(live.status) === "warn" ? "warn" : "danger";
444
+ return { tone, label: connectorStatusLabel(live.status), action: tone === "warn" ? "Connect" : "Inspect" };
369
445
  }
370
- return "Authentication for this connection is managed by your deployment.";
446
+ if (signals.drift)
447
+ return { tone: "warn", label: "Catalog drift", action: "Review" };
448
+ return { tone: "ok", label: "Connected", action: null };
371
449
  }
372
450
  /**
373
451
  * The categories, in the order an operator should read them: what the
@@ -407,7 +485,7 @@ export function driftCounts(drift) {
407
485
  export function driftSummary(drift) {
408
486
  const state = driftState(drift);
409
487
  if (state === "unavailable") {
410
- return "No catalog refresh observed yet in this runtime.";
488
+ return "No catalog refresh yet.";
411
489
  }
412
490
  const observed = formatDate(drift?.observedAt);
413
491
  const when = observed ? ` · observed ${observed}` : "";
@@ -449,6 +527,7 @@ const ACTOR_KINDS = {
449
527
  bearer: "Bearer token",
450
528
  "cloudflare-access": "Cloudflare Access",
451
529
  anonymous: "Anonymous",
530
+ access_token: "Agent token",
452
531
  };
453
532
  /** How a caller signed in, as a person reads it. An unknown kind is shown as sent. */
454
533
  function actorKindLabel(kind) {
@@ -506,9 +585,10 @@ export function activitySummary(events) {
506
585
  if (events.length === 0)
507
586
  return "";
508
587
  const calls = events.filter((event) => event.kind !== "catalog_drift");
588
+ const failed = calls.filter((event) => ["error", "timeout"].includes(activityOutcomeClass(event.outcome))).length;
509
589
  const changes = events.length - calls.length;
510
590
  const tools = new Set(calls.map((event) => event.address)).size;
511
- return `${calls.length} loaded call${calls.length === 1 ? "" : "s"} · ${tools} tool${tools === 1 ? "" : "s"}${changes ? ` · ${changes} catalog change${changes === 1 ? "" : "s"}` : ""}`;
591
+ return `${calls.length} loaded call${calls.length === 1 ? "" : "s"} · ${failed} failed · ${tools} tool${tools === 1 ? "" : "s"}${changes ? ` · ${changes} catalog change${changes === 1 ? "" : "s"}` : ""}`;
512
592
  }
513
593
  // A pause and an approval are neither success nor failure: each gets its own
514
594
  // class, and the stylesheet paints neither as an error. Nothing has emitted
@@ -554,12 +634,17 @@ const REASON_LABELS = {
554
634
  read_only_endpoint: "endpoint is read-only",
555
635
  approval_required: "needed approval",
556
636
  auth_required: "needed authorization",
557
- result_too_large: "result too large to return inline",
637
+ result_too_large: "result too large",
558
638
  timeout: "timed out",
559
639
  };
560
640
  function reasonLabel(code) {
561
641
  return REASON_LABELS[code] ?? code.replaceAll("_", " ");
562
642
  }
643
+ /** Why a call stalled or failed, in words; "" when nothing was recorded. */
644
+ export function activityReason(event) {
645
+ const reasons = [event.friction, event.errorCode].filter((code) => Boolean(code)).map(reasonLabel);
646
+ return [...new Set(reasons)].join(" · ");
647
+ }
563
648
  /** The one-line detail under an address: where it ran, retries, and why it stalled. */
564
649
  export function activityDetail(event) {
565
650
  if (event.kind === "catalog_drift")
@@ -586,25 +671,17 @@ export function credentialProblemCopy(problem) {
586
671
  if (problem === "credential_mismatch")
587
672
  return PROBLEM_COPY.credential_mismatch;
588
673
  if (problem === "credential_unreadable") {
589
- return "The stored credential can't be read, so it can't be used. Replace it, or remove it and add a new one.";
674
+ return "Credential status is unavailable. Retry before replacing or removing it.";
590
675
  }
591
- return "The stored credential can't be used. Replace it, or remove it and add a new one.";
592
- }
593
- export function credentialStateLabel(credential) {
594
- if (!credential.configured)
595
- return "not configured";
596
- const masked = credential.fields?.length ? "configured" : `configured · ••••${credential.lastFour ?? ""}`;
597
- return credential.updatedAt ? `${masked} · updated ${formatDate(credential.updatedAt)}` : masked;
676
+ return "The stored credential can't be used. Replace it.";
598
677
  }
599
678
  /** Gate copy for each browser-auth shape, so the sign-in state is never a blank page. */
600
679
  export function gateCopy(kind, signedIn) {
601
680
  if (kind === "cloudflare-access") {
602
- return "Cloudflare Access admitted this browser, but the current identity cannot open deployment-wide operator pages.";
681
+ return "Couldn't check your Cloudflare Access identity. Retry.";
603
682
  }
604
683
  if (kind !== "clerk") {
605
- return "Paste an operator bearer token to open this page. Nothing is requested until you do.";
684
+ return "Paste an operator token.";
606
685
  }
607
- return signedIn
608
- ? "Signed in with Clerk, but this account cannot open deployment-wide operator pages."
609
- : "Sign in with Clerk to open this operator page.";
686
+ return signedIn ? "This Clerk account can't open operator pages." : "Sign in with Clerk.";
610
687
  }
@@ -1,6 +1,8 @@
1
1
  // Generated by scripts/build-operator-ui.mjs. Do not edit.
2
- // Source: src/operator-ui/tokens.css and src/operator-ui/page.css.
2
+ // Source: src/operator-ui/tokens.css, src/operator-ui/page.css, and the hashed font assets.
3
3
  /** Custom properties and both palettes; styles no element. */
4
- export const TOKENS_CSS = ":root{color-scheme:light;--accent: #5263e8;--radius: 6px;--sans: \"Inter Variable\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--mono: ui-monospace, \"SF Mono\", Menlo, Monaco, \"Cascadia Code\", Consolas, monospace;--bg: #ffffff;--surface: #ffffff;--surface-2: #f7f8fa;--border: #e8eaee;--border-strong: #cdd2da;--text: #20232b;--muted: #69707e;--ok: #12734a;--warn: #9a5b06;--danger: #bb3226;--on-accent: #ffffff;--link: var(--accent);--tint: color-mix(in srgb, var(--accent) 8%, var(--surface));--shell: 68rem;--pad: 1.25rem;--gap: 1rem;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-6: 1.5rem;--space-8: 2rem;--text-xs: .75rem;--text-sm: .8125rem;--text-base: .875rem;--sidebar-width: 14rem}@media(prefers-color-scheme:dark){html:not([data-scheme=light]){color-scheme:dark}}html[data-scheme=dark]{color-scheme:dark}@media(prefers-color-scheme:dark){html:not([data-scheme=light]){--bg: #111216;--surface: #18191e;--surface-2: #202127;--border: #2a2b33;--border-strong: #3b3d48;--text: #e6eaf1;--muted: #97a1b2;--ok: #4cc48c;--warn: #e2a33f;--danger: #f4776c;--link: color-mix(in srgb, var(--accent) 55%, #ffffff);--tint: color-mix(in srgb, var(--accent) 16%, var(--surface))}}html[data-scheme=dark]{--bg: #111216;--surface: #18191e;--surface-2: #202127;--border: #2a2b33;--border-strong: #3b3d48;--text: #e6eaf1;--muted: #97a1b2;--ok: #4cc48c;--warn: #e2a33f;--danger: #f4776c;--link: color-mix(in srgb, var(--accent) 55%, #ffffff);--tint: color-mix(in srgb, var(--accent) 16%, var(--surface))}";
4
+ export const TOKENS_CSS = ":root{color-scheme:light;--sans: \"Hanken Grotesk\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--mono: \"JetBrains Mono\", ui-monospace, \"SF Mono\", Menlo, Monaco, \"Cascadia Code\", Consolas, monospace;--text-2xs: .6875rem;--text-xs: .78125rem;--text-sm: .84375rem;--text-base: .9375rem;--text-md: 1.0625rem;--text-lg: 1.125rem;--text-xl: 1.25rem;--text-2xl: 1.75rem;--text-3xl: 2.125rem;--weight-medium: 500;--weight-semibold: 620;--weight-bold: 700;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.25rem;--space-6: 1.5rem;--space-8: 2rem;--space-10: 2.5rem;--shell: 73.75rem;--pad: 1.5rem;--gap: 1rem;--topbar: 3.875rem;--radius: 14px;--radius-md: min(var(--radius), max(calc(var(--radius) - 4px), 2px));--radius-sm: min(var(--radius), max(calc(var(--radius) - 7px), 2px));--radius-pill: 999px;--bg: #f6f4ef;--surface: #ffffff;--surface-2: #fbfaf7;--sunk: #f0ede6;--hover: #f5f2ec;--line: #e9e4da;--line-strong: #dcd5c8;--line-field: #938b7d;--text: #1d1a16;--text-2: #5c564c;--text-3: #756e61;--scrim: rgb(20 16 8 / .38);--avatar-fill: var(--text);--avatar-ink: var(--bg);--default-accent: #4c44e0;--accent: var(--default-accent);--accent-strong: color-mix(in srgb, var(--accent) 84%, #000000);--accent-soft: color-mix(in srgb, var(--accent) 9%, var(--surface));--accent-line: color-mix(in srgb, var(--accent) 26%, var(--surface));--on-accent: var(--custom-on-accent, #ffffff);--link: var(--accent);--ok: #16774b;--ok-soft: #e4f3ea;--ok-line: #bfe2cd;--warn: #9a5800;--warn-soft: #fcf0da;--warn-line: #f1d7a6;--danger: #bc3a27;--danger-soft: #fbe8e4;--danger-line: #f2c5bc;--shadow-1: 0 1px 2px rgb(40 30 10 / .05), 0 1px 1px rgb(40 30 10 / .03);--shadow-2: 0 1px 2px rgb(40 30 10 / .04), 0 6px 20px -6px rgb(40 30 10 / .1);--shadow-3: 0 24px 60px -16px rgb(30 20 0 / .28), 0 2px 6px rgb(30 20 0 / .06)}@media(prefers-color-scheme:dark){html:not([data-scheme=light]){color-scheme:dark}}html[data-scheme=dark]{color-scheme:dark}@media(prefers-color-scheme:dark){html:not([data-scheme=light]){--bg: #12110f;--surface: #1b1a17;--surface-2: #1f1d1a;--sunk: #16150f;--hover: #24221e;--line: #2c2925;--line-strong: #3a3631;--line-field: #757064;--text: #f3efe7;--text-2: #b5aea2;--text-3: #8a8378;--scrim: rgb(0 0 0 / .6);--avatar-fill: #34312c;--avatar-ink: #e9e4da;--default-accent: #8c86ff;--accent-strong: color-mix(in srgb, var(--accent) 80%, #ffffff);--accent-soft: color-mix(in srgb, var(--accent) 17%, var(--surface));--accent-line: color-mix(in srgb, var(--accent) 38%, var(--surface));--on-accent: var(--custom-on-accent, #0f0e1f);--link: var(--custom-accent-text, color-mix(in srgb, var(--accent) 80%, #ffffff));--ok: #5bd08f;--ok-soft: #173326;--ok-line: #24513a;--warn: #f0b354;--warn-soft: #352812;--warn-line: #5a4319;--danger: #ff8a75;--danger-soft: #3a1c16;--danger-line: #5e2c22;--shadow-1: 0 1px 2px rgb(0 0 0 / .4);--shadow-2: 0 1px 2px rgb(0 0 0 / .3), 0 8px 24px -8px rgb(0 0 0 / .5);--shadow-3: 0 30px 80px -20px rgb(0 0 0 / .7)}}html[data-scheme=dark]{--bg: #12110f;--surface: #1b1a17;--surface-2: #1f1d1a;--sunk: #16150f;--hover: #24221e;--line: #2c2925;--line-strong: #3a3631;--line-field: #757064;--text: #f3efe7;--text-2: #b5aea2;--text-3: #8a8378;--scrim: rgb(0 0 0 / .6);--avatar-fill: #34312c;--avatar-ink: #e9e4da;--default-accent: #8c86ff;--accent-strong: color-mix(in srgb, var(--accent) 80%, #ffffff);--accent-soft: color-mix(in srgb, var(--accent) 17%, var(--surface));--accent-line: color-mix(in srgb, var(--accent) 38%, var(--surface));--on-accent: var(--custom-on-accent, #0f0e1f);--link: var(--custom-accent-text, color-mix(in srgb, var(--accent) 80%, #ffffff));--ok: #5bd08f;--ok-soft: #173326;--ok-line: #24513a;--warn: #f0b354;--warn-soft: #352812;--warn-line: #5a4319;--danger: #ff8a75;--danger-soft: #3a1c16;--danger-line: #5e2c22;--shadow-1: 0 1px 2px rgb(0 0 0 / .4);--shadow-2: 0 1px 2px rgb(0 0 0 / .3), 0 8px 24px -8px rgb(0 0 0 / .5);--shadow-3: 0 30px 80px -20px rgb(0 0 0 / .7)}";
5
5
  /** Base typography, the shell, masthead, buttons, badges, and messages. */
6
- export const PAGE_CSS = "*{box-sizing:border-box}html{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}body{margin:0;min-height:100vh}:is(h1,h2,h3,p,ul,ol){margin:0;padding:0}:is(h1,h2,h3){font-weight:600;line-height:1.3}h1{font-size:1.5rem;letter-spacing:-.01em}h2{font-size:1rem}:is(ul,ol){list-style:none}a{color:var(--link);text-decoration-thickness:1px;text-underline-offset:2px}button,input{font:inherit}:is(a,button,input,summary):focus-visible{border-radius:calc(var(--radius) / 2);outline:2px solid var(--link);outline-offset:2px}.skip-link{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);left:var(--pad);padding:.5rem .75rem;position:fixed;top:-4rem;z-index:10}.skip-link:focus{top:var(--pad)}.shell{margin:0 auto;max-width:var(--shell);padding-left:var(--pad);padding-right:var(--pad)}.cap,.meta{color:var(--muted);font-size:.875rem}.mono{font-family:var(--mono);font-size:.8125rem}.visually-hidden{clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.masthead{align-items:center;background:var(--surface);border-bottom:1px solid var(--border);display:flex;gap:var(--gap);justify-content:space-between;max-width:none;padding-bottom:.75rem;padding-top:.75rem;position:sticky;top:0;z-index:5}.masthead-inner{align-items:center;display:flex;gap:var(--gap);justify-content:space-between;margin:0 auto;max-width:var(--shell);width:100%}.mast-nav,.mast-actions,.page-nav,.session-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.mast-nav,.mast-actions{gap:var(--gap)}.navlink{background:none;border:0;border-radius:calc(var(--radius) - 2px);color:var(--muted);cursor:pointer;font-size:.9375rem;padding:.35rem .6rem;text-decoration:none}.navlink:hover{background:var(--surface-2);color:var(--text)}.navlink[aria-current=page]{background:var(--tint);color:var(--link);font-weight:500}.brand,.product{padding-left:0;padding-right:0;text-decoration:none}.brand{color:var(--text);font-weight:600}.brand:hover,.product:hover{background:none}.product{color:var(--muted)}.page{padding-bottom:4rem;padding-top:2rem}.lead{margin-bottom:1.5rem}.lead-copy{color:var(--muted);display:grid;gap:.5rem;margin-top:.35rem}.lead-copy p{max-width:60ch}.btn{background:var(--surface);border:1px solid var(--border-strong);border-radius:calc(var(--radius) - 2px);color:var(--text);cursor:pointer;display:inline-flex;align-items:center;font-size:.875rem;gap:.35rem;padding:.35rem .7rem;text-decoration:none;white-space:nowrap}.btn:hover{background:var(--surface-2)}.btn:disabled{cursor:not-allowed;opacity:.55}.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}.btn.primary:hover{background:color-mix(in srgb,var(--accent) 88%,black)}.btn.danger{color:var(--danger)}.btn.danger:hover{background:color-mix(in srgb,var(--danger) 10%,var(--surface))}.btn.quiet{background:none;border-color:var(--border);color:var(--muted)}.btn.quiet:hover{background:var(--surface-2);color:var(--text)}.badge{align-items:center;background:var(--surface-2);border-radius:999px;color:var(--muted);display:inline-flex;font-size:.75rem;gap:.3rem;line-height:1.6;padding:.1rem .5rem;white-space:nowrap}.badge.ok{background:color-mix(in srgb,var(--ok) 12%,var(--surface));color:var(--ok)}.badge.warn{background:color-mix(in srgb,var(--warn) 14%,var(--surface));color:var(--warn)}.badge.danger{background:color-mix(in srgb,var(--danger) 12%,var(--surface));color:var(--danger)}.badge.accent{background:var(--tint);color:var(--link)}.msg{background:color-mix(in srgb,var(--danger) 8%,var(--surface));border:1px solid color-mix(in srgb,var(--danger) 28%,var(--surface));border-radius:calc(var(--radius) - 2px);color:var(--danger);font-size:.875rem;padding:.5rem .75rem}.status-page{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);display:grid;gap:.75rem;margin:1.5rem auto 0;max-width:34rem;padding:1.5rem}.status-head{align-items:center;display:flex;gap:.6rem}.status-mark{align-items:center;background:var(--surface-2);border-radius:50%;color:var(--muted);display:inline-flex;flex:none;height:2rem;justify-content:center;width:2rem}.status-mark svg{height:1.25rem;width:1.25rem}.status-mark.ok{background:color-mix(in srgb,var(--ok) 14%,var(--surface));color:var(--ok)}.status-mark.danger{background:color-mix(in srgb,var(--danger) 12%,var(--surface));color:var(--danger)}.status-label{color:var(--muted);font-size:.875rem;font-weight:600}.status-label.ok{color:var(--ok)}.status-label.danger{color:var(--danger)}.status-page h1{overflow-wrap:anywhere}.status-copy{color:var(--muted)}.status-actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.25rem}.status-details{border-top:1px solid var(--border);color:var(--muted);font-size:.875rem;margin-top:.25rem;padding-top:.75rem}.status-details>summary{cursor:pointer;width:fit-content}.status-details>p{margin-top:.5rem}.status-details pre{background:var(--surface-2);border:1px solid var(--border);border-radius:calc(var(--radius) / 2);color:var(--text);font-family:var(--mono);font-size:.75rem;line-height:1.55;margin:.5rem 0 0;max-height:18rem;overflow:auto;overflow-wrap:anywhere;padding:.5rem .6rem;white-space:pre-wrap}@media(max-width:40rem){:root{--pad: 1rem}.status-page{margin-top:0;padding:1.25rem var(--pad)}}";
6
+ export const PAGE_CSS = "*{box-sizing:border-box}html{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}body{background:var(--bg);font-size:var(--text-base);letter-spacing:-.005em;margin:0;min-height:100vh}:is(h1,h2,h3,p,ul,ol){margin:0;padding:0}:is(h1,h2,h3){font-weight:var(--weight-semibold);line-height:1.25}h1{font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:-.03em;line-height:1.12}h2{font-size:var(--text-lg);letter-spacing:-.015em}h3{font-size:var(--text-base);letter-spacing:-.01em}:is(ul,ol){list-style:none}a{color:var(--link);text-decoration-thickness:1px;text-underline-offset:3px}button,input,select,textarea{color:inherit;font:inherit;letter-spacing:inherit}code,pre,kbd{font-family:var(--mono);letter-spacing:0}::selection{background:var(--accent-soft)}:is(a,button,input,select,textarea,summary,[tabindex]):focus-visible{border-radius:var(--radius-sm);outline:2px solid var(--link);outline-offset:2px}.skip-link{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-md);box-shadow:var(--shadow-2);color:var(--text);font-weight:var(--weight-semibold);left:var(--pad);padding:.5rem .875rem;position:fixed;text-decoration:none;top:-4rem;z-index:60}.skip-link:focus{top:.75rem}.shell{margin:0 auto;max-width:var(--shell);padding-left:var(--pad);padding-right:var(--pad)}.cap,.meta{color:var(--text-2);font-size:var(--text-sm)}.cap{color:var(--text-3);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:.08em;text-transform:uppercase}.mono{font-family:var(--mono);font-size:.86em}.visually-hidden{clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.masthead{backdrop-filter:saturate(1.4) blur(14px);background:color-mix(in srgb,var(--bg) 84%,transparent);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:30}.masthead.shell{max-width:none}.masthead-inner{align-items:center;display:flex;gap:var(--space-4);margin:0 auto;max-width:calc(var(--shell) - 2 * var(--pad));min-height:var(--topbar);width:100%}.mast-nav{align-items:center;display:flex;gap:var(--space-2);min-width:0}.brand-mark{border-radius:var(--radius-sm);display:block;flex:none;height:1.625rem;margin-right:.125rem;object-fit:contain;width:1.625rem}span.brand-mark{background:conic-gradient(from 45deg,var(--text) 0 90deg,transparent 0),radial-gradient(circle closest-side,transparent 0 34%,var(--bg) 38% 58.5%,transparent 62.5%),var(--text)}.navlink{background:none;border:0;border-radius:var(--radius-md);color:var(--text-2);cursor:pointer;font-size:var(--text-base);font-weight:var(--weight-medium);padding:.4375rem .75rem;text-decoration:none}.navlink:hover{background:var(--hover);color:var(--text)}.brand,.product{font-size:var(--text-base);overflow-wrap:anywhere;padding-left:0;padding-right:0;text-decoration:none}.brand{color:var(--text);font-size:1rem;font-weight:var(--weight-bold);letter-spacing:-.02em}.brand:hover,.product:hover{background:none}.product{color:var(--text-2);font-weight:var(--weight-medium)}.product:before{color:var(--line-strong);content:\"/\";font-weight:400;margin-right:.5rem}.page{padding-bottom:6rem;padding-top:var(--space-10)}.lead{margin-bottom:var(--space-8)}.lead-copy{color:var(--text-2);display:grid;font-size:var(--text-md);gap:var(--space-2);margin-top:var(--space-2)}.lead-copy p{max-width:60ch}.btn{align-items:center;background:var(--surface);border:1px solid var(--line-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-1);color:var(--text);cursor:pointer;display:inline-flex;font-size:var(--text-sm);font-weight:var(--weight-semibold);gap:.4375rem;justify-content:center;line-height:1.2;min-height:2.25rem;padding:.375rem .875rem;text-decoration:none;transition:background-color .15s,border-color .15s,color .15s;white-space:nowrap}.btn:hover{background:var(--hover)}.btn:active{translate:0 1px}.btn:disabled{cursor:not-allowed;opacity:.5;translate:none}.btn.primary{background:var(--accent);border-color:var(--accent);box-shadow:0 1px 2px color-mix(in srgb,var(--accent) 30%,transparent),inset 0 1px #ffffff24;color:var(--on-accent)}.btn.primary:hover{background:var(--accent-strong);border-color:var(--accent-strong)}.btn.danger{border-color:var(--danger-line);color:var(--danger)}.btn.danger:hover{background:var(--danger-soft)}.btn.quiet{background:transparent;border-color:transparent;box-shadow:none;color:var(--text-2)}.btn.quiet:hover{background:var(--hover);color:var(--text)}.badge{align-items:center;background:var(--sunk);border-radius:var(--radius-pill);color:var(--text-2);display:inline-flex;font-size:var(--text-xs);font-weight:var(--weight-semibold);gap:.375rem;line-height:1.2;min-height:1.5rem;padding:.125rem .5625rem;white-space:nowrap}.badge.ok{background:var(--ok-soft);color:var(--ok)}.badge.warn{background:var(--warn-soft);color:var(--warn)}.badge.danger{background:var(--danger-soft);color:var(--danger)}.badge.accent{background:var(--accent-soft);color:var(--link)}.msg{background:var(--danger-soft);border:1px solid var(--danger-line);border-radius:var(--radius-md);color:var(--text);font-size:var(--text-sm);padding:.75rem 1rem}.msg.warn{background:var(--warn-soft);border-color:var(--warn-line)}.status-page{background:var(--surface);border:1px solid var(--line);border-radius:min(calc(var(--radius) + 4px),calc(var(--radius) * 2));box-shadow:var(--shadow-2);display:grid;gap:var(--space-3);margin:8vh auto 0;max-width:30rem;padding:var(--space-8)}.status-head{align-items:center;display:flex;gap:.625rem}.status-mark{align-items:center;background:var(--sunk);border-radius:var(--radius-md);color:var(--text-2);display:inline-flex;flex:none;height:2.25rem;justify-content:center;width:2.25rem}.status-mark svg{height:1.25rem;width:1.25rem}.status-mark.ok{background:var(--ok-soft);color:var(--ok)}.status-mark.danger{background:var(--danger-soft);color:var(--danger)}.status-label{color:var(--text-3);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:.08em;text-transform:uppercase}.status-label.ok{color:var(--ok)}.status-label.danger{color:var(--danger)}.status-page h1{font-size:var(--text-2xl);overflow-wrap:anywhere}.status-page h1+p,.status-copy{color:var(--text-2)}.status-actions{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-2)}.status-details{border-top:1px solid var(--line);color:var(--text-2);font-size:var(--text-sm);margin-top:var(--space-2);padding-top:var(--space-4)}.status-details>summary{align-items:center;cursor:pointer;display:flex;font-weight:var(--weight-semibold);gap:.5rem;list-style:none;width:fit-content}.status-details>summary::-webkit-details-marker{display:none}.status-details>summary:before{border-bottom:1.5px solid currentColor;border-right:1.5px solid currentColor;content:\"\";height:.375rem;rotate:-45deg;transition:rotate .15s;width:.375rem}.status-details[open]>summary:before{rotate:45deg}.status-details>p{margin-top:var(--space-2)}.status-details pre{background:var(--sunk);border:1px solid var(--line);border-radius:var(--radius-md);color:var(--text-2);font-size:var(--text-xs);line-height:1.6;margin:var(--space-2) 0 0;max-height:18rem;overflow:auto;overflow-wrap:anywhere;padding:.75rem .875rem;white-space:pre-wrap}@media(max-width:51.25rem){:root{--pad: 1rem}h1{font-size:var(--text-2xl)}.page{padding-top:var(--space-8)}.status-page{margin-top:var(--space-4);padding:var(--space-6) var(--space-5)}}";
7
+ /** The operator fonts' hashed paths, which only a deployment with the UI serves. */
8
+ export const PAGE_FONTS = [{ "family": "Hanken Grotesk", "weight": "100 900", "path": "/ui/assets/hanken-grotesk-latin-wght-normal-ZZ3K7K5Y.woff2" }, { "family": "JetBrains Mono", "weight": "100 800", "path": "/ui/assets/jetbrains-mono-latin-wght-normal-VBS632QH.woff2" }];
@@ -22,19 +22,71 @@ export interface ProviderOptions {
22
22
  /** Optional per-runtime downstream call-admission policy. */
23
23
  callAdmission?: ConnectorCallAdmissionPolicy;
24
24
  }
25
- export declare const CALL_ADMISSION: Field<unknown, unknown, true> & {
25
+ declare const admissionPolicy: Field<{
26
+ maxPartitions?: number | undefined;
27
+ rules: {
28
+ maxConcurrency?: number | undefined;
29
+ maxQueueSize?: number | undefined;
30
+ queueTimeoutMs?: number | undefined;
31
+ retryAfterMs?: number | undefined;
32
+ partitionKey?: ((input: Readonly<import("./types.js").ConnectorCallAdmissionInput>) => string) | undefined;
33
+ budget?: {
34
+ kind: "rolling-window";
35
+ maxCalls: number;
36
+ windowMs: number;
37
+ } | undefined;
38
+ }[];
39
+ }, {
40
+ readonly maxPartitions: number | undefined;
41
+ readonly rules: readonly {
42
+ readonly maxConcurrency: number | undefined;
43
+ readonly maxQueueSize: number | undefined;
44
+ readonly queueTimeoutMs: number | undefined;
45
+ readonly retryAfterMs: number | undefined;
46
+ readonly partitionKey: ((input: Readonly<import("./types.js").ConnectorCallAdmissionInput>) => string) | undefined;
47
+ readonly budget: {
48
+ readonly kind: "rolling-window";
49
+ readonly maxCalls: number;
50
+ readonly windowMs: number;
51
+ } | undefined;
52
+ }[];
53
+ }, true> & {
26
54
  readonly shape: {
27
- rules: Field<unknown[], readonly unknown[] | undefined, true>;
28
- maxPartitions: Field<unknown, unknown, true>;
55
+ maxPartitions: Field<number, number | undefined, true>;
56
+ rules: Field<{
57
+ maxConcurrency?: number | undefined;
58
+ maxQueueSize?: number | undefined;
59
+ queueTimeoutMs?: number | undefined;
60
+ retryAfterMs?: number | undefined;
61
+ partitionKey?: ((input: Readonly<import("./types.js").ConnectorCallAdmissionInput>) => string) | undefined;
62
+ budget?: {
63
+ kind: "rolling-window";
64
+ maxCalls: number;
65
+ windowMs: number;
66
+ } | undefined;
67
+ }[], readonly {
68
+ readonly maxConcurrency: number | undefined;
69
+ readonly maxQueueSize: number | undefined;
70
+ readonly queueTimeoutMs: number | undefined;
71
+ readonly retryAfterMs: number | undefined;
72
+ readonly partitionKey: ((input: Readonly<import("./types.js").ConnectorCallAdmissionInput>) => string) | undefined;
73
+ readonly budget: {
74
+ readonly kind: "rolling-window";
75
+ readonly maxCalls: number;
76
+ readonly windowMs: number;
77
+ } | undefined;
78
+ }[], false>;
29
79
  };
30
80
  };
81
+ export declare const CALL_ADMISSION: Omit<Field<unknown, ConnectorCallAdmissionPolicy | undefined, true>, "parse"> & {
82
+ parse(value: unknown, path: string): ConnectorCallAdmissionPolicy;
83
+ readonly shape: typeof admissionPolicy.shape;
84
+ };
31
85
  /** Options every maintained provider shares. */
32
86
  export declare const PROVIDER_COMMON: {
33
- callAdmission: Field<unknown, unknown, true> & {
34
- readonly shape: {
35
- rules: Field<unknown[], readonly unknown[] | undefined, true>;
36
- maxPartitions: Field<unknown, unknown, true>;
37
- };
87
+ callAdmission: Omit<Field<unknown, ConnectorCallAdmissionPolicy | undefined, true>, "parse"> & {
88
+ parse(value: unknown, path: string): ConnectorCallAdmissionPolicy;
89
+ readonly shape: typeof admissionPolicy.shape;
38
90
  };
39
91
  instructions: Field<unknown, unknown, true>;
40
92
  title: Field<unknown, unknown, true>;
@@ -141,3 +193,4 @@ export declare function defineProvider<O extends ProviderOptions>(definition: Pr
141
193
  export declare function asProviderFactory<O extends ProviderOptions>(definition: Omit<ProviderDefinition<O>, "create"> & {
142
194
  create(id: string, options: Readonly<O>): Connector;
143
195
  }): ProviderFactory<O>;
196
+ export {};