@zackbart/connecta 0.33.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 (86) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +99 -128
  3. package/SECURITY.md +1 -1
  4. package/bin/version.mjs +1 -1
  5. package/dist/activity.d.ts +3 -0
  6. package/dist/branding.d.ts +7 -0
  7. package/dist/branding.js +76 -9
  8. package/dist/catalog-service.js +15 -1
  9. package/dist/config-value-sources.js +6 -1
  10. package/dist/config.d.ts +13 -2
  11. package/dist/config.js +36 -2
  12. package/dist/connector-caller.js +2 -1
  13. package/dist/describe-config.d.ts +3 -1
  14. package/dist/describe-config.js +1 -0
  15. package/dist/execute.js +1 -11
  16. package/dist/fix-prompt.js +2 -2
  17. package/dist/index.js +1 -0
  18. package/dist/membership.d.ts +2 -0
  19. package/dist/oauth-callback-outcome.js +2 -2
  20. package/dist/operator-ui/agents.d.ts +1 -1
  21. package/dist/operator-ui/agents.js +16 -16
  22. package/dist/operator-ui/config-summary.js +562 -0
  23. package/dist/operator-ui/connection-map.js +375 -0
  24. package/dist/operator-ui/endpoints.js +44 -15
  25. package/dist/operator-ui/fix-prompts.js +1 -1
  26. package/dist/operator-ui/generated.js +4 -4
  27. package/dist/operator-ui/member-page.js +13 -28
  28. package/dist/operator-ui/overview.js +134 -0
  29. package/dist/operator-ui/view.d.ts +27 -22
  30. package/dist/operator-ui/view.js +104 -88
  31. package/dist/page-styles.js +5 -3
  32. package/dist/providers/_shared/google/workspace.d.ts +6 -4
  33. package/dist/providers/_shared/google/workspace.js +6 -2
  34. package/dist/providers/ccb/index.js +1 -1
  35. package/dist/providers/docs/index.js +1 -1
  36. package/dist/providers/docs/skill.generated.js +2 -2
  37. package/dist/providers/drive/index.js +1 -1
  38. package/dist/providers/drive/skill.generated.js +2 -2
  39. package/dist/providers/forms/index.js +1 -1
  40. package/dist/providers/forms/skill.generated.js +2 -2
  41. package/dist/providers/gmail/index.js +1 -1
  42. package/dist/providers/gmail/skill.generated.js +2 -2
  43. package/dist/providers/mixpanel/skill.generated.js +3 -3
  44. package/dist/providers/notion/skill.generated.js +1 -1
  45. package/dist/providers/overflow/index.js +1 -1
  46. package/dist/providers/overflow/skill.generated.js +2 -2
  47. package/dist/providers/planning-center/index.js +1 -1
  48. package/dist/providers/planning-center/skill.generated.js +4 -4
  49. package/dist/providers/revenuecat/skill.generated.js +2 -2
  50. package/dist/providers/sheets/index.js +1 -1
  51. package/dist/providers/sheets/skill.generated.js +2 -2
  52. package/dist/providers/slides/index.js +1 -1
  53. package/dist/providers/slides/skill.generated.js +2 -2
  54. package/dist/providers/tithely/index.js +1 -1
  55. package/dist/providers/vercel/index.js +1 -1
  56. package/dist/providers/vercel/skill.generated.js +1 -1
  57. package/dist/registry.js +15 -0
  58. package/dist/routes/mcp.js +20 -13
  59. package/dist/routes/oauth.js +2 -2
  60. package/dist/skills.js +10 -0
  61. package/dist/storage/sql.js +5 -1
  62. package/dist/types.d.ts +18 -7
  63. package/dist/ui.js +9 -4
  64. package/dist/usage-guide.js +1 -1
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/documentation/README.md +2 -2
  68. package/documentation/architecture.md +18 -13
  69. package/documentation/auth.md +183 -67
  70. package/documentation/code-mode.md +23 -5
  71. package/documentation/deploying.md +5 -1
  72. package/documentation/integrating.md +9 -6
  73. package/documentation/meta-tools.md +36 -22
  74. package/documentation/migration-0.33.md +21 -13
  75. package/documentation/migration-0.34.md +67 -0
  76. package/documentation/operator-tests.md +46 -20
  77. package/documentation/operator-ui.md +294 -104
  78. package/examples/worker/AGENTS.md +15 -9
  79. package/examples/worker/README.md +41 -14
  80. package/examples/worker/src/connecta.config.ts +5 -4
  81. package/examples/worker/wrangler.jsonc +1 -1
  82. package/package.json +19 -19
  83. package/templates/node/AGENTS.md +13 -7
  84. package/templates/node/README.md +14 -11
  85. package/templates/node/package.json +1 -1
  86. package/templates/node/src/connecta.config.ts +2 -2
package/dist/config.js CHANGED
@@ -184,6 +184,31 @@ function assertFlag(value, path) {
184
184
  if (typeof value !== "boolean")
185
185
  throw new ConfigError(`${path} must be true or false.`);
186
186
  }
187
+ /** Longest pool `description`, in characters, after trimming. */
188
+ const MAX_POOL_DESCRIPTION_LENGTH = 500;
189
+ /**
190
+ * A pool description is one line of prose for the model. C0 and C1 controls
191
+ * (including NEL), plus Unicode line and paragraph separators, are refused.
192
+ * A scan, as in remote-mcp.ts,
193
+ * because a character class over the control range trips `no-control-regex`.
194
+ */
195
+ function hasLineBreakOrControlCharacter(value) {
196
+ for (let index = 0; index < value.length; index++) {
197
+ const code = value.charCodeAt(index);
198
+ if (code <= 0x1f || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029)
199
+ return true;
200
+ }
201
+ return false;
202
+ }
203
+ function assertPoolDescription(value, path) {
204
+ const trimmed = typeof value === "string" ? value.trim() : undefined;
205
+ if (trimmed === undefined ||
206
+ trimmed.length === 0 ||
207
+ [...trimmed].length > MAX_POOL_DESCRIPTION_LENGTH ||
208
+ hasLineBreakOrControlCharacter(trimmed)) {
209
+ throw new ConfigError(`${path} must be text of 1 to ${MAX_POOL_DESCRIPTION_LENGTH} characters after trimming, with no line breaks or control characters.`);
210
+ }
211
+ }
187
212
  /** Shape only; each reference resolves against auth and agents in createConnecta. */
188
213
  function assertMemberList(value, path) {
189
214
  if (!Array.isArray(value)) {
@@ -217,8 +242,9 @@ const pool = {
217
242
  * The Google Workspace address agents in this pool act as, on a Workspace
218
243
  * connector whose `subject` is a mapping function. Humans always act as
219
244
  * themselves through that mapping, and a fixed `subject` address applies to
220
- * everyone. Without an owner, an agent's Workspace calls fail
221
- * `auth_required`.
245
+ * everyone. Without an owner, a mapping-subject Workspace connector is
246
+ * absent for this pool's agents, as a personal connector is; people in the
247
+ * pool still see it.
222
248
  */
223
249
  owner: opaque({
224
250
  check: (value, path) => {
@@ -227,6 +253,14 @@ const pool = {
227
253
  }
228
254
  },
229
255
  }),
256
+ /**
257
+ * What this pool is for, in plain words for the model: appended to the MCP
258
+ * `initialize` instructions at `/mcp/<name>` and `/agent/<name>`, so a
259
+ * caller learns its scope ("Team tier: shared tools only."). Trimmed, 1 to
260
+ * 500 characters, no line breaks or control characters. It grants and hides nothing; plain
261
+ * `/mcp` never shows it.
262
+ */
263
+ description: opaque({ check: assertPoolDescription }),
230
264
  };
231
265
  const icon = {
232
266
  src: required(text()),
@@ -9,7 +9,8 @@
9
9
  // Workspace through domain-wide delegation (`src/providers/_shared/google/`) — which
10
10
  // hand the identity to a deployment-config function that names the
11
11
  // downstream subject (PRINCIPLES.md INV-3, #678), or, for an agent, use the
12
- // admitted pool's configured owner (decisions/0006). The provider never
12
+ // admitted pool's configured owner (decisions/0006), and the scoped registry
13
+ // view hides the connector from an agent whose pool has none. The provider never
13
14
  // decides whose account to open; config does, from this.
14
15
  //
15
16
  // Core sets it from the authorization that admitted the request, on the scoped
@@ -115,7 +115,8 @@ export interface ConnectaConfigDescription {
115
115
  /**
116
116
  * `readOnly` is resolved: the pool's own value, else the deployment's.
117
117
  * `members` are resolved references in declaration order. `owner` is the
118
- * Workspace address agents act as, or null.
118
+ * Workspace address agents act as, or null. `description` is the trimmed
119
+ * text the pool's endpoints add to their MCP instructions, or null.
119
120
  */
120
121
  pools: Array<{
121
122
  name: string;
@@ -124,6 +125,7 @@ export interface ConnectaConfigDescription {
124
125
  members: DescribedMember[];
125
126
  readOnly: boolean;
126
127
  owner: string | null;
128
+ description: string | null;
127
129
  }>;
128
130
  modules: {
129
131
  ui: {
@@ -250,6 +250,7 @@ export function describeConfig(input) {
250
250
  ? { kind: "agent", name: member.name }
251
251
  : { kind: "human", namespace: member.namespace, id: member.id }),
252
252
  owner: input.pools.get(name)?.owner ?? null,
253
+ description: input.pools.get(name)?.description ?? null,
253
254
  })),
254
255
  modules: {
255
256
  ui: { enabled: config.ui !== undefined },
package/dist/execute.js CHANGED
@@ -798,18 +798,8 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
798
798
  ...args,
799
799
  includeSchemas: args.includeSchemas ?? "json",
800
800
  includeSchemaKeys: args.includeSchemaKeys !== false,
801
- }));
801
+ }, catalogDependency));
802
802
  boundedDiscoveryText(result, "Request a smaller limit, omit fullDescriptions, use compact schemas, or pass includeSchemaKeys: false.");
803
- for (const tool of result.tools)
804
- catalogAddressDependency(tool.address);
805
- for (const error of result.catalogErrors)
806
- catalogDependency(error.connector);
807
- for (const connector of result.absence?.configuredConnectors ?? [])
808
- catalogDependency(connector);
809
- for (const connector of result.queryAnalysis?.configuredConnectors ?? [])
810
- catalogDependency(connector);
811
- if (result.queryAnalysis?.connectorScope)
812
- catalogDependency(result.queryAnalysis.connectorScope);
813
803
  return result;
814
804
  }),
815
805
  describe: (raw) => discovery("describe", async () => {
@@ -29,7 +29,7 @@ export function renderFixPrompt(spec, connectorId) {
29
29
  "own logs (lines prefixed [connecta]) or by reproducing the failure locally. Never put a secret in source, " +
30
30
  "a log line, or your reply: secrets belong in the deployment's environment or its credential vault.",
31
31
  "Make the smallest change that fixes it, then verify it by redeploying and refreshing the connection on the " +
32
- "operator Connections page. If the fix needs something you cannot do yourself — a provider console setting, " +
33
- "a secret only the operator holds — name that exact step instead.",
32
+ "operator Connectors page. If the fix needs a step you cannot do yourself, such as a provider console setting " +
33
+ "or a secret only the operator holds, name that exact step instead.",
34
34
  ].join("\n\n");
35
35
  }
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ function resolvePools(pools, readOnly, registry, members) {
67
67
  readOnly: pool.readOnly ?? readOnly,
68
68
  members: Object.freeze(resolveMembers(pool.members, path, members)),
69
69
  ...(pool.owner !== undefined ? { owner: pool.owner.trim() } : {}),
70
+ ...(pool.description !== undefined ? { description: pool.description.trim() } : {}),
70
71
  }));
71
72
  }
72
73
  return resolved;
@@ -25,6 +25,8 @@ export interface ResolvedPool {
25
25
  members: readonly Member[];
26
26
  /** Trimmed `owner`: the Workspace address agents in this pool act as. */
27
27
  owner?: string;
28
+ /** Trimmed `description`, appended to this pool's MCP instructions. */
29
+ description?: string;
28
30
  }
29
31
  /** What resolving a reference needs to know about the deployment. */
30
32
  export interface MemberContext {
@@ -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
  ],
@@ -62,7 +62,7 @@ export declare function slotBadge(slot: Pick<AgentSlot, "state">): {
62
62
  label: string;
63
63
  tone: Tone;
64
64
  };
65
- /** When the slot last changed, as "Issued <date>" or "Revoked <date>"; a dash when it never has. */
65
+ /** When the slot last changed, issued or revoked (its badge says which); a dash when it never has. */
66
66
  export declare function slotSince(slot: AgentSlot, format: (value: string) => string): string;
67
67
  export declare function lastSeenLabel(lastSeenAt: string | null, format: (value: string) => string): string;
68
68
  /**
@@ -1,6 +1,6 @@
1
1
  import { failure } from "./view.js";
2
2
  /** Why a row's controls are disabled when its slot is not in the live list. */
3
- const SLOT_UNAVAILABLE = "Token state unavailable, refresh";
3
+ const SLOT_UNAVAILABLE = "Token state unknown. Refresh.";
4
4
  /** Why a row's controls are disabled until a fresh list read resolves its slot. */
5
5
  const SLOT_UNRESOLVED = "Refresh to continue";
6
6
  export function slotActions(slot) {
@@ -53,12 +53,12 @@ export function slotBadge(slot) {
53
53
  return { label: "Revoked", tone: "warn" };
54
54
  return { label: "No token", tone: "neutral" };
55
55
  }
56
- /** When the slot last changed, as "Issued <date>" or "Revoked <date>"; a dash when it never has. */
56
+ /** When the slot last changed, issued or revoked (its badge says which); a dash when it never has. */
57
57
  export function slotSince(slot, format) {
58
58
  if (slot.state === "issued")
59
- return `Issued ${format(slot.issuedAt)}`;
59
+ return format(slot.issuedAt);
60
60
  if (slot.state === "revoked")
61
- return `Revoked ${format(slot.revokedAt)}`;
61
+ return format(slot.revokedAt);
62
62
  return "—";
63
63
  }
64
64
  export function lastSeenLabel(lastSeenAt, format) {
@@ -72,33 +72,33 @@ export function lastSeenLabel(lastSeenAt, format) {
72
72
  */
73
73
  export function tokenFailureNotice(action, name, facts, productName) {
74
74
  if (facts.kind === "session")
75
- return failure("Your session has ended. Sign in again, then retry.");
75
+ return failure("Session ended. Sign in again.");
76
76
  if (facts.kind === "forbidden")
77
- return failure("You don't have permission to manage agent tokens.");
77
+ return failure("You can't manage agent tokens.");
78
78
  if (facts.kind === "stale") {
79
- return failure(`${name}'s token changed before the request was sent, so nothing was sent. Check it and try again.`);
79
+ return failure(`${name}'s token changed, so nothing was sent. Try again.`);
80
80
  }
81
81
  if (facts.kind === "network") {
82
- return failure(`Couldn't reach ${productName}. Refresh to check ${name}'s token before trying again.`);
82
+ return failure(`Couldn't reach ${productName}. Refresh to check ${name}'s token.`);
83
83
  }
84
84
  if (facts.status === 503) {
85
85
  return failure(action === "revoke"
86
- ? `Revocation outcome unknown for ${name}: revoke again to be sure its token is stopped.`
87
- : `${action === "issue" ? "Issue" : "Rotation"} outcome unknown for ${name}: no secret was returned, so rotate again for a replacement.`);
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
88
  }
89
89
  if (facts.status === 499) {
90
90
  return failure(action === "revoke"
91
- ? `The request ended before ${name}'s revocation finished. Refresh to check its token before trying again.`
92
- : `The request ended before ${name}'s token was delivered, so no secret was returned. Refresh to check its token before trying again.`);
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
93
  }
94
94
  // The route's compare-and-swap refused it: the slot moved after the list was read.
95
95
  if (facts.status === 409 && facts.code === "slot_changed")
96
- return failure("Changed since you looked, refresh.");
96
+ return failure("Changed since you looked. Refresh.");
97
97
  if (facts.status === 409)
98
- return failure(`${name}'s token changed meanwhile. Reload and try again.`);
98
+ return failure(`${name}'s token changed. Reload and try again.`);
99
99
  if (facts.status === 404)
100
100
  return failure(`${name} is no longer declared in code. Reload the page.`);
101
101
  return action === "revoke"
102
- ? failure(`${name}'s token could not be revoked. Reload and try again.`)
103
- : failure(`A token could not be issued for ${name}. Reload and try again.`);
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
104
  }