@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
@@ -11,7 +11,7 @@ const OUTCOMES = {
11
11
  status: 400,
12
12
  tone: "declined",
13
13
  heading: "Authorization was declined",
14
- message: "Consent was declined at the provider, so nothing was stored. Start authorization again whenever you are ready.",
14
+ message: "Consent was declined, so nothing was stored. Start again when ready.",
15
15
  fix: {
16
16
  problem: "Consent for a downstream OAuth connector was declined at the provider, so no grant was stored.",
17
17
  steps: [
@@ -72,7 +72,7 @@ const OUTCOMES = {
72
72
  fix: {
73
73
  problem: "A provider returned an authorization code, but exchanging it for tokens failed.",
74
74
  steps: [
75
- "Check the OAuth client credentials the connector uses — client id and secret in the deployment's environment — and that they belong to the same OAuth app that showed the consent screen.",
75
+ "Check the OAuth client id and secret the connector reads from the deployment's environment, and that they belong to the OAuth app that showed the consent screen.",
76
76
  "Check that the token endpoint and redirect URI the connector uses match the provider's registration exactly.",
77
77
  "Find the [connecta] warning for this callback in the deployment logs; it records the exchange error the page withholds.",
78
78
  ],
@@ -8,7 +8,7 @@ type FailureStep = "MCP handshake" | "tools/list" | "tools/call" | "resources/re
8
8
  */
9
9
  export declare const OAUTH_ERROR_CODES: ReadonlySet<string>;
10
10
  /** Inbound denial facts, never identity text or provider diagnostics. */
11
- export type AuthDenialReason = "authentication_failed" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "default_grant_denied" | "default_grant_threw" | "pool_not_declared" | "pool_grant_denied" | "pool_grant_threw";
11
+ export type AuthDenialReason = "authentication_failed" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "not_maintainer" | "pool_not_declared" | "not_pool_member";
12
12
  /**
13
13
  * Name `ctor`'s instances `label` in records and fixed text. Call once, at
14
14
  * module scope, beside the class. An error whose prototype chain reaches no
@@ -104,7 +104,7 @@ export declare function failureRecord(subject: FailureSubject, failure?: unknown
104
104
  * The events an operator log reports failures as. Closed: a new one is a
105
105
  * reviewed line here, never a string assembled at the call site.
106
106
  */
107
- export type FailureEvent = "negotiation cache read failed" | "negotiation cache write failed" | "connectorAccess grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "result paging unavailable" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
107
+ export type FailureEvent = "negotiation cache read failed" | "negotiation cache write failed" | "pool tool grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "MCP agent endpoint request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "operator agent slots read failed" | "result paging unavailable" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
108
108
  /**
109
109
  * Log one failure as `[connecta] <event>` and its record. A record
110
110
  * `failureRecord` did not build is replaced, whatever its shape, by a fixed
@@ -112,11 +112,9 @@ const AUTH_DENIAL_REASONS = new Set([
112
112
  "email_domain_denied",
113
113
  "gate_denied",
114
114
  "gate_failed",
115
- "default_grant_denied",
116
- "default_grant_threw",
115
+ "not_maintainer",
117
116
  "pool_not_declared",
118
- "pool_grant_denied",
119
- "pool_grant_threw",
117
+ "not_pool_member",
120
118
  ]);
121
119
  /** The registry's connector id grammar (src/registry.ts). */
122
120
  const CONNECTOR_ID_RE = /^[a-z0-9_-]{1,64}$/;
@@ -0,0 +1,74 @@
1
+ import type { OperatorUiContractV2 } from "./contract.js";
2
+ import { type Notice, type RequestFailureFacts, type Tone } from "./view.js";
3
+ /**
4
+ * The Tokens page's rows, computed from the slot list and contract v2 alone.
5
+ * Agents are declared in code: a row's only controls are its token's issue,
6
+ * rotate and revoke, so nothing here creates, renames or rebinds one.
7
+ */
8
+ /** One declared agent's token slot, as `GET /ui/access-tokens` returns it. */
9
+ export type AgentSlot = ({
10
+ name: string;
11
+ state: "none";
12
+ } | {
13
+ name: string;
14
+ state: "issued";
15
+ issuedAt: string;
16
+ } | {
17
+ name: string;
18
+ state: "revoked";
19
+ revokedAt: string;
20
+ }) & {
21
+ /**
22
+ * The route's opaque slot revision; it changes on every slot commit (P3b,
23
+ * #837). A slot read without one is unresolved and never acted on.
24
+ */
25
+ revision?: string;
26
+ };
27
+ /** Issue and rotate are the same request; the slot's state names it. */
28
+ export type SlotAction = "issue" | "rotate" | "revoke";
29
+ export interface AgentRow {
30
+ name: string;
31
+ /** Pools it belongs to, from `agents[].pools`; empty when the contract does not list it. */
32
+ pools: string[];
33
+ slot: AgentSlot;
34
+ /**
35
+ * Whether `slot` comes from the live slot list. Only a live slot can be
36
+ * acted on: one known only from the contract may be stale, so its controls
37
+ * are disabled until a refresh lists it.
38
+ */
39
+ live: boolean;
40
+ /** Why its controls are off, or null when they work. */
41
+ blocked: string | null;
42
+ /** Its latest activity row; null is "never" (or history that is off). */
43
+ lastSeenAt: string | null;
44
+ actions: SlotAction[];
45
+ }
46
+ export declare function slotActions(slot: Pick<AgentSlot, "state">): SlotAction[];
47
+ /**
48
+ * One agent's slot, resolved once for both what the row shows and what an
49
+ * action may do. The live slot list is authoritative; the contract's copy is
50
+ * shown only for an agent the list does not have, and is never acted on.
51
+ */
52
+ export declare function resolveSlot(contract: OperatorUiContractV2 | null, slots: AgentSlot[], name: string): {
53
+ slot: AgentSlot;
54
+ live: boolean;
55
+ } | null;
56
+ /**
57
+ * One row per declared agent, in the slot list's order, then any agent only
58
+ * the contract lists. The contract adds pools and last seen.
59
+ */
60
+ export declare function agentRows(contract: OperatorUiContractV2 | null, slots: AgentSlot[], unresolved?: readonly string[]): AgentRow[];
61
+ export declare function slotBadge(slot: Pick<AgentSlot, "state">): {
62
+ label: string;
63
+ tone: Tone;
64
+ };
65
+ /** When the slot last changed, issued or revoked (its badge says which); a dash when it never has. */
66
+ export declare function slotSince(slot: AgentSlot, format: (value: string) => string): string;
67
+ export declare function lastSeenLabel(lastSeenAt: string | null, format: (value: string) => string): string;
68
+ /**
69
+ * Why a token operation did not land, in fixed sentences keyed by status.
70
+ * `outcome_unknown` (503) and `aborted` (499) are failures like any other and
71
+ * are never retried for the operator: an unknown rotation returned no secret,
72
+ * and only a new, deliberate rotation produces one.
73
+ */
74
+ export declare function tokenFailureNotice(action: SlotAction, name: string, facts: RequestFailureFacts, productName: string): Notice;
@@ -0,0 +1,104 @@
1
+ import { failure } from "./view.js";
2
+ /** Why a row's controls are disabled when its slot is not in the live list. */
3
+ const SLOT_UNAVAILABLE = "Token state unknown. Refresh.";
4
+ /** Why a row's controls are disabled until a fresh list read resolves its slot. */
5
+ const SLOT_UNRESOLVED = "Refresh to continue";
6
+ export function slotActions(slot) {
7
+ return slot.state === "issued" ? ["rotate", "revoke"] : ["issue"];
8
+ }
9
+ function contractSlot(agent) {
10
+ return { name: agent.name, ...agent.token };
11
+ }
12
+ function maintainerAgents(contract) {
13
+ return contract?.role === "maintainer" ? contract.agents : [];
14
+ }
15
+ /**
16
+ * One agent's slot, resolved once for both what the row shows and what an
17
+ * action may do. The live slot list is authoritative; the contract's copy is
18
+ * shown only for an agent the list does not have, and is never acted on.
19
+ */
20
+ export function resolveSlot(contract, slots, name) {
21
+ const listed = slots.find((slot) => slot.name === name);
22
+ if (listed)
23
+ return { slot: listed, live: true };
24
+ const agent = maintainerAgents(contract).find((candidate) => candidate.name === name);
25
+ return agent ? { slot: contractSlot(agent), live: false } : null;
26
+ }
27
+ /**
28
+ * One row per declared agent, in the slot list's order, then any agent only
29
+ * the contract lists. The contract adds pools and last seen.
30
+ */
31
+ export function agentRows(contract, slots, unresolved = []) {
32
+ const agents = maintainerAgents(contract);
33
+ const declared = new Map(agents.map((agent) => [agent.name, agent]));
34
+ const names = [...slots.map((slot) => slot.name), ...agents.map((agent) => agent.name)];
35
+ return [...new Set(names)].map((name) => {
36
+ const { slot, live } = resolveSlot(contract, slots, name);
37
+ const agent = declared.get(name);
38
+ return {
39
+ name,
40
+ pools: agent?.pools ?? [],
41
+ slot,
42
+ live,
43
+ blocked: !live ? SLOT_UNAVAILABLE : unresolved.includes(name) || !slot.revision ? SLOT_UNRESOLVED : null,
44
+ lastSeenAt: agent?.lastSeenAt ?? null,
45
+ actions: slotActions(slot),
46
+ };
47
+ });
48
+ }
49
+ export function slotBadge(slot) {
50
+ if (slot.state === "issued")
51
+ return { label: "Issued", tone: "ok" };
52
+ if (slot.state === "revoked")
53
+ return { label: "Revoked", tone: "warn" };
54
+ return { label: "No token", tone: "neutral" };
55
+ }
56
+ /** When the slot last changed, issued or revoked (its badge says which); a dash when it never has. */
57
+ export function slotSince(slot, format) {
58
+ if (slot.state === "issued")
59
+ return format(slot.issuedAt);
60
+ if (slot.state === "revoked")
61
+ return format(slot.revokedAt);
62
+ return "—";
63
+ }
64
+ export function lastSeenLabel(lastSeenAt, format) {
65
+ return lastSeenAt ? format(lastSeenAt) : "never";
66
+ }
67
+ /**
68
+ * Why a token operation did not land, in fixed sentences keyed by status.
69
+ * `outcome_unknown` (503) and `aborted` (499) are failures like any other and
70
+ * are never retried for the operator: an unknown rotation returned no secret,
71
+ * and only a new, deliberate rotation produces one.
72
+ */
73
+ export function tokenFailureNotice(action, name, facts, productName) {
74
+ if (facts.kind === "session")
75
+ return failure("Session ended. Sign in again.");
76
+ if (facts.kind === "forbidden")
77
+ return failure("You can't manage agent tokens.");
78
+ if (facts.kind === "stale") {
79
+ return failure(`${name}'s token changed, so nothing was sent. Try again.`);
80
+ }
81
+ if (facts.kind === "network") {
82
+ return failure(`Couldn't reach ${productName}. Refresh to check ${name}'s token.`);
83
+ }
84
+ if (facts.status === 503) {
85
+ return failure(action === "revoke"
86
+ ? `Revoke result unknown for ${name}. Revoke again to be sure.`
87
+ : `${action === "issue" ? "Issue" : "Rotate"} result unknown for ${name}. No secret came back, so rotate again.`);
88
+ }
89
+ if (facts.status === 499) {
90
+ return failure(action === "revoke"
91
+ ? `The request ended before ${name}'s revoke finished. Refresh to check its token.`
92
+ : `The request ended before ${name}'s token arrived. Refresh to check its token.`);
93
+ }
94
+ // The route's compare-and-swap refused it: the slot moved after the list was read.
95
+ if (facts.status === 409 && facts.code === "slot_changed")
96
+ return failure("Changed since you looked. Refresh.");
97
+ if (facts.status === 409)
98
+ return failure(`${name}'s token changed. Reload and try again.`);
99
+ if (facts.status === 404)
100
+ return failure(`${name} is no longer declared in code. Reload the page.`);
101
+ return action === "revoke"
102
+ ? failure(`Couldn't revoke ${name}'s token. Reload and try again.`)
103
+ : failure(`Couldn't issue a token for ${name}. Reload and try again.`);
104
+ }