@zackbart/connecta 0.33.0 → 0.34.1

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 (131) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +100 -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-drift.d.ts +30 -26
  9. package/dist/catalog-drift.js +169 -84
  10. package/dist/catalog-service.js +16 -2
  11. package/dist/config-value-sources.js +6 -1
  12. package/dist/config.d.ts +13 -2
  13. package/dist/config.js +36 -2
  14. package/dist/connector-caller.js +2 -1
  15. package/dist/connectors/api-connector.d.ts +3 -0
  16. package/dist/connectors/api-connector.js +5 -1
  17. package/dist/connectors/option-shapes.js +1 -1
  18. package/dist/connectors/remote-mcp.d.ts +2 -0
  19. package/dist/connectors/remote-mcp.js +5 -1
  20. package/dist/describe-config.d.ts +5 -1
  21. package/dist/describe-config.js +2 -0
  22. package/dist/execute.js +1 -11
  23. package/dist/fix-prompt.js +2 -2
  24. package/dist/index.js +1 -0
  25. package/dist/invocation.js +3 -2
  26. package/dist/membership.d.ts +2 -0
  27. package/dist/meta-tools.js +3 -2
  28. package/dist/oauth-callback-outcome.js +2 -2
  29. package/dist/operator-ui/agents.d.ts +1 -1
  30. package/dist/operator-ui/agents.js +16 -16
  31. package/dist/operator-ui/config-summary.js +565 -0
  32. package/dist/operator-ui/connection-map.js +376 -0
  33. package/dist/operator-ui/endpoints.js +45 -16
  34. package/dist/operator-ui/fix-prompts.js +1 -1
  35. package/dist/operator-ui/generated.js +4 -4
  36. package/dist/operator-ui/health.js +52 -0
  37. package/dist/operator-ui/member-page.js +13 -28
  38. package/dist/operator-ui/overview.js +123 -0
  39. package/dist/operator-ui/view.d.ts +26 -22
  40. package/dist/operator-ui/view.js +130 -96
  41. package/dist/page-styles.js +5 -3
  42. package/dist/provider.d.ts +6 -1
  43. package/dist/provider.js +18 -1
  44. package/dist/providers/_shared/google/workspace.d.ts +9 -4
  45. package/dist/providers/_shared/google/workspace.js +7 -3
  46. package/dist/providers/basecamp/index.d.ts +2 -0
  47. package/dist/providers/breeze/index.d.ts +2 -0
  48. package/dist/providers/ccb/index.d.ts +2 -0
  49. package/dist/providers/ccb/index.js +2 -2
  50. package/dist/providers/cloudflare/index.d.ts +2 -0
  51. package/dist/providers/cloudflare/index.js +5 -1
  52. package/dist/providers/cloudflare/openapi.generated.js +3296 -3206
  53. package/dist/providers/cloudflare/rest.js +19 -0
  54. package/dist/providers/cloudflare/skill.generated.js +2 -2
  55. package/dist/providers/cloudflare/value-safety.js +51 -18
  56. package/dist/providers/docs/index.js +1 -1
  57. package/dist/providers/docs/skill.generated.js +2 -2
  58. package/dist/providers/drive/index.js +1 -1
  59. package/dist/providers/drive/skill.generated.js +2 -2
  60. package/dist/providers/forms/index.js +1 -1
  61. package/dist/providers/forms/skill.generated.js +2 -2
  62. package/dist/providers/gmail/index.js +1 -1
  63. package/dist/providers/gmail/skill.generated.js +2 -2
  64. package/dist/providers/intercom/index.d.ts +24 -0
  65. package/dist/providers/intercom/index.js +121 -0
  66. package/dist/providers/intercom/skill.generated.js +8 -0
  67. package/dist/providers/linear/index.js +204 -19
  68. package/dist/providers/linear/skill.generated.js +2 -2
  69. package/dist/providers/mixpanel/index.d.ts +2 -0
  70. package/dist/providers/mixpanel/index.js +21 -17
  71. package/dist/providers/mixpanel/skill.generated.js +3 -3
  72. package/dist/providers/notion/index.d.ts +2 -0
  73. package/dist/providers/notion/index.js +1 -1
  74. package/dist/providers/notion/skill.generated.js +1 -1
  75. package/dist/providers/overflow/index.d.ts +2 -0
  76. package/dist/providers/overflow/index.js +1 -1
  77. package/dist/providers/overflow/skill.generated.js +2 -2
  78. package/dist/providers/planning-center/index.d.ts +2 -0
  79. package/dist/providers/planning-center/index.js +1 -1
  80. package/dist/providers/planning-center/skill.generated.js +4 -4
  81. package/dist/providers/revenuecat/index.d.ts +5 -3
  82. package/dist/providers/revenuecat/index.js +147 -7
  83. package/dist/providers/revenuecat/skill.generated.js +3 -3
  84. package/dist/providers/sheets/index.js +1 -1
  85. package/dist/providers/sheets/skill.generated.js +2 -2
  86. package/dist/providers/slides/index.js +1 -1
  87. package/dist/providers/slides/skill.generated.js +2 -2
  88. package/dist/providers/stripe/index.d.ts +2 -0
  89. package/dist/providers/stripe/index.js +50 -30
  90. package/dist/providers/stripe/skill.generated.js +2 -1
  91. package/dist/providers/tithely/index.d.ts +2 -0
  92. package/dist/providers/tithely/index.js +1 -1
  93. package/dist/providers/vercel/index.d.ts +2 -0
  94. package/dist/providers/vercel/index.js +2 -2
  95. package/dist/providers/vercel/openapi.generated.js +391 -374
  96. package/dist/providers/vercel/skill.generated.js +2 -2
  97. package/dist/providers/vercel/value-safety.js +14 -4
  98. package/dist/registry.d.ts +10 -14
  99. package/dist/registry.js +77 -62
  100. package/dist/routes/mcp.js +20 -13
  101. package/dist/routes/oauth.js +2 -2
  102. package/dist/routes/ui-data.js +8 -2
  103. package/dist/skills.js +18 -2
  104. package/dist/storage/sql.js +5 -1
  105. package/dist/types.d.ts +36 -11
  106. package/dist/ui.js +9 -4
  107. package/dist/usage-guide.js +1 -1
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/documentation/README.md +2 -2
  111. package/documentation/architecture.md +66 -18
  112. package/documentation/auth.md +183 -67
  113. package/documentation/code-mode.md +30 -10
  114. package/documentation/deploying.md +5 -1
  115. package/documentation/integrating.md +64 -7
  116. package/documentation/meta-tools.md +38 -24
  117. package/documentation/migration-0.33.md +21 -13
  118. package/documentation/migration-0.34.md +67 -0
  119. package/documentation/operating.md +1 -1
  120. package/documentation/operator-tests.md +52 -20
  121. package/documentation/operator-ui.md +341 -104
  122. package/documentation/provider-auth-migration.md +22 -0
  123. package/examples/worker/AGENTS.md +15 -9
  124. package/examples/worker/README.md +41 -14
  125. package/examples/worker/src/connecta.config.ts +5 -4
  126. package/examples/worker/wrangler.jsonc +1 -1
  127. package/package.json +23 -19
  128. package/templates/node/AGENTS.md +13 -7
  129. package/templates/node/README.md +14 -11
  130. package/templates/node/package.json +1 -1
  131. package/templates/node/src/connecta.config.ts +2 -2
@@ -3,6 +3,21 @@
3
3
  Use this guide to change connectors in deployment configuration.
4
4
  [All agent routes](./README.md) are in the index.
5
5
 
6
+ ## Make one connection read-only
7
+
8
+ Every connector accepts optional `readOnly?: boolean`, defaulting to `false`:
9
+ `remoteMcp("notes", { url: "https://notes.example.com/mcp", auth: { type: "headers", headers: {} }, readOnly: true })`,
10
+ `api("tickets", { readOnly: true, tools: [] })`, and maintained providers such as
11
+ `stripe("billing", { purpose: "Revenue reports", auth: { type: "oauth" }, readOnly: true })`.
12
+ A custom `Connector` sets `readOnly: true` on its connector object.
13
+
14
+ Only tools classified read appear in search and describe, and every write or
15
+ unclassified tool call is refused before dispatch on every endpoint, including
16
+ maintainers' plain `/mcp`, programs, `call_tool`, and `call_destructive_tool`.
17
+ Effective read-only is endpoint `readOnly` OR connector `readOnly`; connector
18
+ `false` never widens an endpoint. Existing pool/deployment inheritance is unchanged.
19
+ Guides may still name unavailable tools. The operator UI shows a Read-only badge.
20
+
6
21
  ## Add or remove a connector
7
22
 
8
23
  Connectors are code-owned. Edit the deployment's `src/connecta.config.ts`;
@@ -19,8 +34,10 @@ To add one:
19
34
  3. Add the id (all its tools) or exact `id.tool` addresses to the intended
20
35
  pools' `tools`. Maintainers at plain `/mcp` already see the whole catalog.
21
36
  Follow [the agent pool layout](./auth.md#organizing-an-agents-pools) for an
22
- agent: shared connectors go in its base constant, per-user connectors only
23
- in that user's pool.
37
+ agent: a shared connector goes in the tool list of the lowest tier whose
38
+ entire audience is authorized to use it (billing restricted to leads goes
39
+ in the leads tier); per-user connectors and Workspace connectors with a
40
+ mapping `subject` go only in personal pools.
24
41
  4. Run the deployment's `npm run typecheck` (Worker copies: `tsc --noEmit`),
25
42
  restart Node or redeploy the Worker, and
26
43
  [connect its credential](./auth.md#connect-a-shared-credential) if needed.
@@ -38,14 +55,15 @@ const crm = remoteMcp("crm", {
38
55
  authScope: "shared",
39
56
  auth: { type: "credential", credential: { label: "CRM API token" } },
40
57
  });
41
- const assistant = { tools: ["crm", "tickets"], members: ["agent:assistant"] };
42
- // connectors: [crm, tickets], agents: ["assistant"], pools: { assistant }
58
+ const base = ["crm", "tickets"]; // the agent's lowest tier
59
+ // connectors: [crm, tickets], agents: ["assistant"],
60
+ // pools: { assistant: { tools: base, members: ["agent:assistant"] } }
43
61
  ```
44
62
 
45
63
  To remove `crm`, first disconnect its OAuth grant or delete its saved
46
64
  credential while the connector still exists, using a signed-in maintainer
47
65
  for shared auth. Each person disconnects their own personal account. Remove
48
- `crm` and all `crm.*` addresses from every pool (including base constants),
66
+ `crm` and all `crm.*` addresses from every pool (including tier tool lists),
49
67
  then remove its entry from `connectors`, unused imports, and unused secret
50
68
  configuration. Remove any pools left with no tools (empty `tools` fails
51
69
  construction), and update or retire clients pointing at those pools. Typecheck, restart/redeploy, run doctor, and confirm discovery
@@ -89,10 +107,10 @@ const tracker = linear("tracker", {
89
107
  ```
90
108
 
91
109
  Linear defaults to OAuth; its read-only endpoint requests only the downstream
92
- read scope. That is separate from a Connecta `readOnly` endpoint. Read the installed `dist/providers/linear/index.d.ts` for API-key framing,
110
+ read scope. That is separate from Connecta endpoint or connector `readOnly`. Read the installed `dist/providers/linear/index.d.ts` for API-key framing,
93
111
  workspace instructions, and admission choices.
94
112
 
95
- The [human README](../README.md#what-you-can-do-with-it) lists available
113
+ The [human README](../README.md#what-you-can-connect) lists available
96
114
  integrations. In the installed package, `dist/providers/<name>/index.d.ts`
97
115
  and its imported declarations describe each factory's options. After adding
98
116
  a connector, use `search_tools` to discover its current tools and `connecta.skill("connector:<id>")` in `execute_code`
@@ -104,6 +122,34 @@ pool's `owner` instead ([auth guide](auth.md#pool-owners-for-google-workspace)).
104
122
  A request must not choose its own account. Gmail never sends mail, and Drive never permanently
105
123
  deletes files.
106
124
 
125
+ Intercom uses `intercom()` from `@zackbart/connecta/providers/intercom`:
126
+
127
+ ```ts
128
+ import { intercom } from "@zackbart/connecta/providers/intercom";
129
+
130
+ const support = intercom("support", {
131
+ purpose: "Customer support triage",
132
+ region: "eu",
133
+ });
134
+ // Add support to the deployment's connectors array.
135
+ ```
136
+
137
+ Set `region` explicitly to `"us"` or `"eu"` to match the workspace; AU is
138
+ unsupported. OAuth defaults to personal ownership because it acts as the
139
+ authorizing teammate. Non-loopback OAuth callbacks require an Intercom Customer
140
+ Support allowlist entry for the deployment's redirect URI before authorization
141
+ can complete. Alternatively, set `auth: { type: "credential" }` and connect an
142
+ API token through the [shared credential procedure](./auth.md#connect-a-shared-credential);
143
+ token auth defaults to shared ownership. Fin Operator tools require OAuth,
144
+ workspace availability and teammate permission. Their expected absence adds no
145
+ missing-tool drift; missing core tools still do. Schema coverage reports all
146
+ present reviewed tools as unpinned. Article operations, internal
147
+ notes and Operator actions can write; a note can reopen a snoozed conversation.
148
+ Use a read-only endpoint when writes are unwanted. Catalog review is based on
149
+ [Intercom's docs](https://developers.intercom.com/docs/guides/mcp), with unpinned
150
+ schemas; inspect live tools and the runtime guide before acting on sensitive
151
+ customer data.
152
+
107
153
  Wispr Flow uses OAuth only through `wisprFlow()` from
108
154
  `@zackbart/connecta/providers/wispr-flow`. It reaches the authorized account's
109
155
  Notetaker content, with personal and work accounts holding separate data.
@@ -113,6 +159,8 @@ reviewed classification includes task writes listed in Claude's directory even
113
159
  though Wispr's public help describes read-only access; discover the account's
114
160
  actual tools before assuming those writes exist.
115
161
 
162
+ For minimal Stripe OAuth/API-key examples with and without connector `readOnly`, see [Stripe setup](./provider-auth-migration.md#stripe). With an API key, prefer a restricted `rk_` key with only read permissions for read-only access; Stripe enforces it too.
163
+
116
164
  Stripe requires `auth`: `{ type: "oauth" }` reaches Stripe's hosted MCP
117
165
  server, and `{ type: "apiKey" }` with `mode` reaches Connecta's REST connector
118
166
  with an operator-managed key. Notion requires `auth` the same way:
@@ -218,6 +266,15 @@ for precedence, schema digests, and `unlisted: "hide"`. Review the behavior
218
266
  before declaring a read, and do not use classification to bypass provider target
219
267
  scopes or caller grants.
220
268
 
269
+ A reviewed name is required by default for drift comparison. Mark a tool
270
+ `optional: true` only when the downstream can omit it because of plan,
271
+ feature, or permission gating. For example, a remote `crm` connector can use
272
+ `classify: { tools: { list_accounts: "read", list_customers: { verdict: "read",
273
+ optional: true } } }`. Absence of `list_customers` then adds no unserved count;
274
+ when present, its verdict, annotations, and any pinned schema are still checked.
275
+ A missing schema pin means that tool's schema has not been verified against a
276
+ review. Zero schema changes alone does not establish a matching schema.
277
+
221
278
  ## A hand-written HTTP capability
222
279
 
223
280
  Define only the operations the deployment needs. Every `api()` tool needs a
@@ -35,7 +35,7 @@ is annotated `readOnlyHint: true, destructiveHint: false` on a read-only
35
35
  endpoint, and `readOnlyHint: false, destructiveHint: true` on a writable one.
36
36
  Approval belongs to the client. A read-only endpoint keeps all six tools, omits
37
37
  write tools from `search_tools` results and describe, and refuses every write
38
- call; `readOnly` never changes connector visibility or read grants. Guides
38
+ call. Connector `readOnly` applies the same restriction on every endpoint; effective read-only is endpoint OR connector. Neither setting changes connector visibility or read grants. Guides
39
39
  (`queryAnalysis.guideSummary`, `skills`, `connecta.skill`, skill
40
40
  `resources/read`) are documentation and may name tools the endpoint refuses. The four tools with structured-only success results
41
41
  advertise `outputSchema`. The two direct-call tools omit it because their default
@@ -43,14 +43,14 @@ mode returns native `content`. Default-mode successes declare
43
43
  `_meta["dev.connecta/format"]: "json" | "text"`; value mode places the value in
44
44
  `data` and declares `format` in its complete result envelope.
45
45
 
46
- | Tool | Arguments | Returns |
47
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
48
- | `execute_code` | `code`, `diagnostics?` | `{ result?, error?, hostCalls }`, plus diagnostics when asked |
49
- | `search_tools` | `query?`, `connector?`, `safety?`, `limit?`, `offset?`, `fullDescriptions?`, `includeSchemas?: "compact" \| "json" \| "typescript"` | `{ catalogErrors, tools, total, offset, limit, hasMore }`, plus `queryAnalysis` on a partial or failed search |
50
- | `call_tool` | `address`, `args?`, `resultMode?: "mcp" \| "value"`, `timeoutMs?`, `diagnostics?` | the downstream result, bounded as [result representation](#result-representation) describes |
51
- | `call_destructive_tool` | the same, plus `reason?` | the same |
52
- | `authorize_connector` | `connector`, `force?` | the class-specific handoff in [authorization recovery](#authorization-recovery) |
53
- | `skills` | `name?` | the listing when `name` is absent, that skill's markdown when it is present |
46
+ | Tool | Arguments | Returns |
47
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
48
+ | `execute_code` | `code`, `diagnostics?` | `{ result?, error?, hostCalls }`, plus `logs`, `emitted`, and `emittedDiscarded` when present and diagnostics when asked |
49
+ | `search_tools` | `query?`, `connector?`, `safety?`, `limit?`, `offset?`, `fullDescriptions?`, `includeSchemas?: "compact" \| "json" \| "typescript"` | `{ catalogErrors, tools, total, offset, limit, hasMore }`, plus `nextOffset` when more remain, `absence` when the query names a service with no configured connector, and `queryAnalysis` on a partial, failed, unsearchable, or absent-service search |
50
+ | `call_tool` | `address`, `args?`, `resultMode?: "mcp" \| "value"`, `timeoutMs?`, `diagnostics?` | the downstream result, bounded as [result representation](#result-representation) describes |
51
+ | `call_destructive_tool` | the same, plus `reason?` | the same |
52
+ | `authorize_connector` | `connector`, `force?` | the class-specific handoff in [authorization recovery](#authorization-recovery) |
53
+ | `skills` | `name?` | the listing when `name` is absent, that skill's markdown when it is present |
54
54
 
55
55
  `limit` defaults to 8 and is capped at 100, as is one `connecta.describe` batch.
56
56
  `connecta.result`'s `offset` is a whole number of bytes ≥ 0 defaulting to 0 and
@@ -66,8 +66,8 @@ refuse a consequential call.
66
66
 
67
67
  `diagnostics: true` adds compact request-local timing and serialized-size
68
68
  aggregates for a caller measuring a workflow: a `diagnostics` block from
69
- `execute_code`, a `timing` block on a call response that is already structured
70
- (value mode, or a failure carrying recovery). Normal calls pay nothing for it,
69
+ `execute_code`, a `timing` block on every structured call response: value-mode successes
70
+ and every `{ ok: false }` failure. Normal calls pay nothing for it,
71
71
  and the measurements never contain program source, arguments, values, addresses,
72
72
  credentials, logs, or raw error text.
73
73
 
@@ -221,7 +221,7 @@ declared schema exactly as they do for compact.
221
221
  A connector may attach a deployment-owned guide as markdown, keeping the
222
222
  original `usageGuide: string` configuration, or as
223
223
  `{ content, summary?, required? }` — which registers no connector and creates no
224
- shared runtime template. The registry prepends Agent Skills frontmatter to
224
+ shared runtime template. The skills catalog prepends Agent Skills frontmatter to
225
225
  `content` and preserves the guide body, including its whitespace.
226
226
  `summary` is normalized and refuses construction over 120 characters; absent,
227
227
  Connecta derives the same bounded fallback the skills listing uses from the
@@ -253,14 +253,22 @@ A connector-scoped lexical miss keeps that connector's guide metadata under
253
253
  contains no endpoint vocabulary: a required guide stays discoverable instead of
254
254
  disappearing with the zero-tool page.
255
255
 
256
- Two built-in skills are byte-identical across deployments. `usage` says to read
257
- it at most once per task and owns program selection detail, examples, runtime
256
+ Two built-in skills are byte-identical across deployments. `usage` is fetched
257
+ only for missing guidance or program repair, and owns program selection detail, examples, runtime
258
258
  differences, and repair guidance; `investigate` is on-demand guidance for
259
259
  purchase verification, experiment checks, and customer or deployment
260
260
  investigations — shared guidance, not a saved workflow and not a source of
261
261
  deployment-specific ids. The always-loaded instructions and tool definitions keep
262
262
  route selection, the fail-closed boundary, and the minimum guest syntax, so a
263
- client that never fetches a skill can still write a valid first program. Connector
263
+ client that never fetches a skill can still write a valid first program.
264
+ The instructions are the same on every endpoint except one addition: at
265
+ `/mcp/<pool>` and `/agent/<pool>`, a pool that sets a
266
+ [`description`](./auth.md#pools) has it appended after `This endpoint: `, so
267
+ the model learns that endpoint's scope, for example
268
+ `This endpoint: Team tier: shared tools only. For someone's own mail or files, ask them to message you one-on-one.`
269
+ Plain `/mcp` never carries one. The description changes no tool or tool
270
+ description, and `initialize` and `server/discover` stay `cacheScope: "private"`,
271
+ so a client keeps what it learned per endpoint. Connector
264
272
  guides stay scoped to the deployment that listed them even when two deployments
265
273
  use identical content, and a deployment with no connector guides receives none of
266
274
  the short conditional guide pointers in its tool definitions.
@@ -386,7 +394,11 @@ including dependencies inherited from pages read during the run. Downstream skil
386
394
  resource content requires a whole-connector grant on every page. Local guides and catalog
387
395
  metadata require the contributing connector or tool to remain visible, including
388
396
  caught catalog-load errors, unknown-tool failures, and recovery messages that
389
- list visible connectors or local guides. Returns with no downstream calls still
397
+ list visible connectors or local guides. No-match search guidance also retains
398
+ the connectors it names or summarizes by count, including matches to connector
399
+ titles; scoped guides and summaries, absence rosters, and catalog errors retain
400
+ their contributors. Search counts, ranking, and term analysis also retain every
401
+ searched connector, including matches outside the returned page. Returns with no downstream calls still
390
402
  require matching identity, origin and endpoint, current auth, and pool membership. A page must
391
403
  match those bindings and pass current auth, connector/tool grants, pool membership,
392
404
  and read-only checks. Old entries lacking bindings fail closed. A random UUID is a
@@ -394,7 +406,7 @@ handle, never an access grant.
394
406
 
395
407
  New entries store UTF-8 bytes in a base64 envelope split across storage keys,
396
408
  48 KiB of result text per chunk — widening past roughly 1.5 MB so no result
397
- occupies more than 33 keys, because every chunk is also a write. `connecta.result`
409
+ occupies more than 32 keys, because every chunk is also a write. `connecta.result`
398
410
  reads and decodes only the chunks a page covers plus a few boundary bytes, so
399
411
  paging a 1.2 MB result costs the same per page as paging a 300 KB one; it
400
412
  neither encodes nor reads the whole result per page. Entries without the required identity and invocation bindings fail closed,
@@ -426,7 +438,7 @@ JSON; everything after the first newline is the preview. `structuredContent`
426
438
  is absent, so the notice cannot hide the preview in clients that prefer it:
427
439
 
428
440
  ```text
429
- {"truncated":true,"resultId":"…","totalBytes":161420,"hint":"This write already ran: do not call it again to see its result. Bytes 0-24000 of 161420 follow; page the rest with connecta.result using nextAction.","nextOffset":24000,"nextAction":{"tool":"execute_code","arguments":{"code":"async () => await connecta.result(\"…\", { offset: 24000 })"}}}
441
+ {"truncated":true,"resultId":"…","totalBytes":161420,"hint":"This write already ran: do not call it again to see its result. Bytes 0-24000 of 161420 follow; page the rest with connecta.result using nextAction.","nextOffset":24000,"nextAction":{"tool":"execute_code","arguments":{"code":"async () => await connecta.result(\"…\", { offset: 24000, maxBytes: 3829 })"}}}
430
442
  {"ts":"2026-09-16T21:31:11.759Z","actor":"sam.ortiz@example.com",…
431
443
  ```
432
444
 
@@ -458,7 +470,7 @@ characters. One that rejects outright — Claude Code with
458
470
  `MAX_MCP_OUTPUT_TOKENS` set below 12,500 — needs a cap under twice its limit.
459
471
 
460
472
  The preview is the head of what `connecta.result` pages, cut on a character
461
- boundary, so `nextAction.arguments.offset` continues exactly where it stops. A
473
+ boundary, so the `offset` in `nextAction.arguments.code` continues exactly where it stops. A
462
474
  lone text block — including the one synthesized from `structuredContent` — is
463
475
  measured, stashed, previewed, and paged as its text: wrapping it in a content
464
476
  envelope added nothing but a second layer of JSON escaping, so a JSON payload
@@ -521,7 +533,7 @@ cap and a 32 KiB program bridge ceiling, with UTF-8 alignment and forward progre
521
533
  returning pages unchanged can hit the program result cap. The admitted subject
522
534
  and principal, endpoint/pool, request origin, connector, and tool bindings must
523
535
  match. The host rechecks current access and whether the endpoint is read-only
524
- before returning each page; a read-only endpoint cannot read a write stash.
536
+ before returning each page; a read-only endpoint or connector cannot read a write stash.
525
537
  Caller arguments select no partition. A read-only endpoint never runs a write,
526
538
  so every write result it could page came from a writable endpoint.
527
539
 
@@ -682,8 +694,8 @@ replay. Downstream payloads and input responses are capped at 64 KiB, with at
682
694
  most 16 inputs and 256 characters per downstream key. The combined private
683
695
  state history is also capped at 64 KiB. Sealed wire state is
684
696
  capped at 128 KiB. `input_required_invalid`, `input_required_limit`,
685
- `input_required_unsupported`, and `input_required_round_limit` are non-retryable
686
- typed failures.
697
+ and `input_required_unsupported` are non-retryable typed failures;
698
+ `input_required_round_limit` is an untyped failure with `retryable: false`.
687
699
 
688
700
  Programs and in-process meta-tools return `input_required_unsupported`, with a
689
701
  `nextAction` naming the equivalent direct call and bounded original arguments.
@@ -965,12 +977,14 @@ Handlers and schemas live in [meta-tools.ts](https://github.com/zackbart/connect
965
977
  [Catalog service](https://github.com/zackbart/connecta/blob/main/src/catalog-service.ts)
966
978
  and [invocation](https://github.com/zackbart/connecta/blob/main/src/invocation.ts)
967
979
  serve both direct tools and programs. [Skills](https://github.com/zackbart/connecta/blob/main/src/skills.ts)
968
- owns the supplied usage instructions and connector guides.
980
+ owns the supplied usage instructions, each endpoint's instructions
981
+ (`endpointInstructions`), and connector guides.
969
982
 
970
983
  [Schema tests](https://github.com/zackbart/connecta/blob/main/test/meta-tool-schemas.test.ts),
971
984
  [search tests](https://github.com/zackbart/connecta/blob/main/test/meta-tools-search.test.ts),
972
985
  [call tests](https://github.com/zackbart/connecta/blob/main/test/meta-tools-call.test.ts),
973
986
  [skills tests](https://github.com/zackbart/connecta/blob/main/test/skills-extension.test.ts),
974
- and [surface tests](https://github.com/zackbart/connecta/blob/main/test/code-first-surface.test.ts)
987
+ [surface tests](https://github.com/zackbart/connecta/blob/main/test/code-first-surface.test.ts),
988
+ and [pool description tests](https://github.com/zackbart/connecta/blob/main/test/pool-description.test.ts)
975
989
  check the published contract. Guest parity and clause evidence are in
976
990
  [code-mode verification](./code-mode.md#verification).
@@ -127,7 +127,12 @@ maintainer issues, rotates, and revokes; nothing outside code adds, renames,
127
127
  or rebinds one, and removing a name stops its token on the next request.
128
128
 
129
129
  1. Add `agents: ["<name>", …]` and keep `accessTokens(storage)` on the same
130
- storage and key namespace. Drop `maxActive`.
130
+ storage and key namespace. Drop `maxActive`. A Node template deployment
131
+ from before 0.33 also copies `src/rotate-token.ts` and `src/adopt-token.ts`
132
+ from `node_modules/@zackbart/connecta/templates/node/src/`, deletes
133
+ `src/provision-token.ts`, and replaces the `provision-token` and `doctor`
134
+ scripts with the installed template's `rotate-token`, `adopt-token`, and
135
+ `doctor` (`connecta doctor --pool <pool>`) scripts.
131
136
  2. Keep each machine's existing secret by adopting its token once:
132
137
  `npm run adopt-token` lists the tokens that can be adopted, and
133
138
  `npm run adopt-token -- <token-id> <agent>` binds one (Node template), or
@@ -205,7 +210,8 @@ membership contract.
205
210
  In 0.32 a `cta_` token reached a Workspace connector's `subject` mapping,
206
211
  which usually answered nothing for a caller without a principal. In 0.33 an
207
212
  agent never reaches the mapping. It acts as the `owner` of the pool it came
208
- through, and fails `auth_required` in a pool without one. People always act
213
+ through; in a pool without one the connector is absent for it, as a personal
214
+ connector is. People always act
209
215
  as themselves through the mapping, in any pool. A fixed `subject` address
210
216
  still applies to every caller.
211
217
 
@@ -303,24 +309,26 @@ pools: {
303
309
  // 0.33
304
310
  agents: ["assistant"],
305
311
  pools: {
306
- assistant,
307
- "assistant-alice": { ...assistant, tools: [...assistant.tools, "notes_alice", "gmail"], owner: "alice@example.com" },
308
- "assistant-bob": { ...assistant, tools: [...assistant.tools, "notes_bob", "gmail"], owner: "bob@example.com" },
312
+ assistant: { tools: base, members },
313
+ "assistant-alice": { tools: [...base, "notes_alice", "gmail"], owner: "alice@example.com", members },
314
+ "assistant-bob": { tools: [...base, "notes_bob", "gmail"], owner: "bob@example.com", members },
309
315
  },
310
- // with const assistant = { tools: ["crm", "tickets"], members: ["agent:assistant"] };
316
+ // with const base = ["crm", "tickets"], members = ["agent:assistant"];
311
317
  ```
312
318
 
313
319
  1. Declare the agent in `agents` and adopt its token (see
314
320
  [Agents and their tokens](#agents-and-their-tokens)).
315
- 2. Define the base pool `<agent>` once with the shared connectors and
316
- `members: ["agent:<agent>"]`, and spread it into one `<agent>-<user>` pool
317
- per person, adding that person's connectors and `owner`.
321
+ 2. Define the base pool's tool list once with the shared connectors, and
322
+ spread it into the base pool `<agent>` and one `<agent>-<user>` pool per
323
+ person, adding that person's connectors and `owner`. Each pool lists
324
+ `members: ["agent:<agent>"]`. If group chats should get different tools
325
+ depending on who is present, add tiers as the layout describes.
318
326
  3. Collapse per-person Workspace connectors into one connector with a mapping
319
327
  `subject`. Keep other per-person accounts as their own shared-auth
320
328
  connector ids (`notes_alice`) and list each only in its person's pool.
321
329
  4. Change the agent's client to one connection whose URL it sets per turn:
322
- `/agent/<agent>-<user>` in a one-on-one with that user, `/agent/<agent>`
323
- otherwise. Remove any second Connecta connection.
330
+ `/agent/<agent>-<user>` in a one-on-one with that user, otherwise the
331
+ layout's tier for who is present (`/agent/<agent>` with a single tier). Remove any second Connecta connection.
324
332
  5. Add this line to your deployment's own AGENTS.md (or its CLAUDE.md
325
333
  equivalent), so agents find all operator procedures after the upgrade:
326
334
 
@@ -339,6 +347,6 @@ pools: {
339
347
  5. For Workspace, call a read in an agent's personal pool and confirm it acts
340
348
  as the pool's owner. In the base pool, confirm Workspace connectors are
341
349
  absent: search finds none of their tools and a call fails
342
- `unknown_address`. `auth_required` is what an agent gets only in a pool
343
- without an owner that does list a Workspace connector.
350
+ `unknown_address`. The same holds in any pool without an owner, even one
351
+ that lists a Workspace connector with a subject mapping.
344
352
  6. Confirm each `<agent>-<user>` pool lists no other person in `members`.
@@ -0,0 +1,67 @@
1
+ # Migration to 0.34
2
+
3
+ Use this guide when upgrading an existing deployment from 0.33 to 0.34.
4
+ [Deployment upgrades](./deploying.md#upgrade-an-existing-deployment) cover the
5
+ general procedure; [the agent index](./README.md) routes other work. Upgrades
6
+ from earlier versions must also apply the intervening migrations.
7
+
8
+ No storage migration, token rotation or required configuration field is added.
9
+ Review the following changes before updating the package pin.
10
+
11
+ ## Operator links and browser titles
12
+
13
+ Maintainers now land on Overview at `/`. Endpoints is at `/endpoints`, and
14
+ `/access` redirects to `/endpoints`. Update saved links and browser automation
15
+ that expected Endpoints at `/`, including row links: `/#mcp/support` becomes
16
+ `/endpoints#mcp/support`. Members still get their member page on every operator
17
+ page path. MCP client URLs (`/mcp`, `/mcp/<pool>` and `/agent/<pool>`) do not change.
18
+
19
+ The default `branding.pageTitle` joins `productName` and `ownerName` with a
20
+ middle dot, for example `Assistant · Example team`. A title set explicitly in
21
+ `branding.pageTitle` is unchanged. If automation requires the previous title,
22
+ set that exact title explicitly; otherwise update its expectation. With no
23
+ owner name, the default remains the product name alone.
24
+
25
+ Activity links can use `failed` or `succeeded` for the outcome filter. Older
26
+ exact outcome values in saved links still work. See the
27
+ [operator UI guide](./operator-ui.md) for the new pages and controls.
28
+
29
+ ## Workspace connectors in agent pools
30
+
31
+ An agent in a pool without an `owner` no longer sees a Google Workspace
32
+ connector whose `subject` is a mapping function. Previously the connector
33
+ appeared but its calls failed `auth_required`; now discovery, search, describe,
34
+ skills and code mode omit it, and a call by address fails `unknown_address`.
35
+ Paging a saved program return also rechecks visibility, including contributions
36
+ to search counts and guidance.
37
+
38
+ Keep a shared or group pool owner-less when it must not act as one person.
39
+ If an agent needs to act as Alice, put that access in her personal pool and set
40
+ `owner: "alice@example.com"` there, using the Workspace account she is intended
41
+ to use. Confirm the pool's tools and members before adding an owner. Follow
42
+ [the agent pool layout](./auth.md#organizing-an-agents-pools) for the full
43
+ configuration. People in the same pool, agents in owned pools and connectors
44
+ with a fixed `subject` address keep their behavior.
45
+
46
+ ## Optional settings and development tooling
47
+
48
+ Pool `description` is optional; omit it to preserve the existing MCP
49
+ instructions. A custom activity store can optionally return
50
+ `describe().optionSources.retentionDays` as `"config"` or `"default"` to mark
51
+ whether code explicitly set retention. Existing implementations need no
52
+ change; Settings marks retention as explicitly set only when the store
53
+ reports `"config"`.
54
+
55
+ The repository now uses pnpm for development and CI. Installed deployments and
56
+ the Node template continue to use npm; keep their npm commands and lockfiles.
57
+ Deployments without the operator UI or mapping-subject Workspace connectors in
58
+ owner-less agent pools can skip the UI and discovery steps above.
59
+
60
+ ## Verify
61
+
62
+ After upgrading, open `/` as a maintainer and confirm Overview loads, then open
63
+ `/endpoints` and a saved endpoint row link. Check any automation that reads the
64
+ browser title. With an agent's existing credentials, discover tools in its base
65
+ and personal pools and perform a bounded Workspace read in the owned pool.
66
+ Owner-less pools must omit mapping-subject Workspace connectors. Run
67
+ `connecta doctor` as described in [deployment upgrades](./deploying.md#upgrade-an-existing-deployment).
@@ -24,7 +24,7 @@ descriptions. On a writable endpoint, programs and `call_destructive_tool` may
24
24
  write within the caller's grants, and `execute_code` is annotated as a write;
25
25
  the client controls approval. On a read-only endpoint, search and describe omit
26
26
  write tools and every write call is refused with `read_only_endpoint`, even one
27
- a connector guide names; tell the user rather than looking for another route. Never change pool, identity, or `readOnly` to work
27
+ a connector guide names; tell the user rather than looking for another route. A connector marked Read-only serves only read-classified tools even on a writable endpoint; writes and unclassified tools are omitted from search/describe and refused on every call path. Endpoint and connector restrictions compose by OR. Never change pool, identity, or `readOnly` to work
28
28
  around a refusal.
29
29
  [Routing between the call surfaces](./meta-tools.md#routing-between-the-call-surfaces)
30
30
  owns the full decision rules.
@@ -13,15 +13,19 @@ or browser evidence. Read the [operator UI contract](./operator-ui.md) first.
13
13
  `test/fixtures/operator-visual.ts` owns the visual data. It builds the
14
14
  maintainer's contract v2, live overlay, and legacy auth details through the
15
15
  current server routes, using the types in `src/operator-ui/contract.ts` and
16
- `model.ts`; its people carry labels from a fake directory. Its Tokens agents
16
+ `model.ts`; its people carry labels from a fake directory. It sets a few
17
+ limits, an activity retention, one classification override and a GitHub call
18
+ admission rule in code, so Settings and the connector Configuration card show
19
+ set values beside defaults; Slack describes OAuth facts (scope, endpoints, an
20
+ API origin, client secret, PKCE) for the card's auth row. Its Tokens agents
17
21
  (`claude-desktop` issued, `triage` none, `nightly` revoked) are real slots
18
22
  committed through the deployment's own store, so the contract's `agents` and
19
23
  `GET /ui/access-tokens` agree, revisions included; only their timestamps and
20
24
  last seen are fixed. The restricted state is the member fixture from
21
25
  `test/fixtures/operator-contract-v2.ts`, and the endpoint and matrix variants
22
26
  use its maintainer fixture, which covers every runs-as kind and agent pools,
23
- with `test/fixtures/operator-console.ts` crowding the team pool so its chips
24
- collapse to "+3 more".
27
+ with `test/fixtures/operator-console.ts` crowding the team pool so its avatar
28
+ stack collapses to "+3".
25
29
  Collections use their corresponding browser types.
26
30
  `test/operator-visual-fixtures.test.ts` validates contract versions, connector
27
31
  IDs, granted tools, matrix cells, classification, provenance, and secret
@@ -31,7 +35,10 @@ server serializer.
31
35
 
32
36
  `test/browser/operator-visual.spec.ts` intercepts API reads and serves the real
33
37
  hashed assets and HTML with the production CSP. Loading responses wait on an
34
- explicit gate. Error responses use fixed HTTP failures. Screenshots use a fixed
38
+ explicit gate. Error responses use fixed HTTP failures. The map trace focuses its
39
+ node instead of hovering it: the capture scrolls to the top first, which would
40
+ move the node out from under a pointer and drop the highlight, so the test waits
41
+ for `.map-svg.tracing` before capturing. Screenshots use a fixed
35
42
  clock, UTC, en-US, locally hosted fonts, disabled animations, hidden carets,
36
43
  and a 1280 × 900 viewport. Narrow fixtures use 390 × 844. Every test waits for
37
44
  its state and fonts before capturing.
@@ -39,11 +46,12 @@ its state and fonts before capturing.
39
46
  ## Page and state matrix
40
47
 
41
48
  Each row runs empty, loading, error, populated, and restricted-viewer fixtures
42
- in both light and dark themes, for 80 screenshots. The restricted viewer is a
49
+ in both light and dark themes, for 90 screenshots. The restricted viewer is a
43
50
  pool member, who gets the member page, with no tabs, on every path.
44
51
 
45
52
  | Page or tab | Empty state | Loading/error request | Restricted viewer (member) |
46
53
  | -------------------- | ------------------------------------ | --------------------- | ----------------------------------- |
54
+ | Overview | No connectors: nothing to map | Config | Member page at `/` |
47
55
  | Endpoints | Root endpoint without tools | Config | Member page: endpoints, accounts |
48
56
  | Connectors | No connectors | Config | Member page, no connector request |
49
57
  | Connector Tools | Empty catalog | Config | Member page, no connector request |
@@ -53,24 +61,26 @@ pool member, who gets the member page, with no tabs, on every path.
53
61
  | Activity | No calls | Activity | Member page, no activity request |
54
62
  | Settings | Snapshot without connectors or pools | Config | Member page, no configuration facts |
55
63
 
56
- Populated Endpoints shows who can use each endpoint; populated Tokens has one
64
+ Populated Overview has a failing connector, auth and credential problems
65
+ and a tokenless agent in its attention list. Populated Endpoints shows who can use each endpoint; populated Tokens has one
57
66
  agent per slot state, with pools and last seen.
58
67
 
59
- Another 54 screenshots cover configured, mismatched, unreadable and multifield
68
+ Another 60 screenshots cover the Overview map on the frozen v2 maintainer, one
69
+ person's trace, and the narrow map list; configured, mismatched, unreadable and multifield
60
70
  credentials; all three OAuth client mechanisms; clean and unobserved drift;
61
- historical Activity; client setup; schema dialog; read/write filters; expanded
62
- config provenance; narrow sign-in and Connection; deployment branding; `/mcp`
71
+ historical Activity; client setup; schema dialog; read/write filters; narrow
72
+ Settings; narrow sign-in and Connection; deployment branding; `/mcp`
63
73
  and an agent pool expanded with every runs-as kind; command search; narrow
64
74
  endpoints; the connector × endpoint matrix, one cell's open popover, and the
65
75
  narrow matrix; a new token's one-time secret dialog; and the member page, wide
66
76
  and narrow. `endpoints-expanded` and `narrow-endpoints` include the team row
67
- collapsed to "+3 more". The base
77
+ collapsed to "+3". The base
68
78
  populated Activity fixture includes grouped calls, an ungrouped legacy row,
69
79
  classification/result size/client facts, and a catalog-change event.
70
80
 
71
81
  `test/browser/operator-matrix.spec.ts` serves the frozen v2 fixtures to the
72
- real shell. It covers the matrix's table semantics, cell names and runs-as
73
- labels, popover keyboard behavior, placement at a short viewport, the in-flow
82
+ real shell. It covers the matrix's table semantics, cell names, status dots,
83
+ runs-as labels in each kind's popover, popover keyboard behavior, placement at a short viewport, the in-flow
74
84
  fallback without CSS anchors, and the absence of `style` attributes, agent URLs on Endpoints, the member page (its
75
85
  received body checked with `expectMemberViewWithholds`, `/ui/data` answering 404
76
86
  and never requested, no maintainer fact rendered, no maintainer request sent),
@@ -80,9 +90,31 @@ refresh that stalls past its deadline (one focusable Retry, no maintainer
80
90
  request until Retry succeeds), an open client setup drawer closed by a failed
81
91
  refresh with focus on Retry, Tokens under recovery (an open rotate question
82
92
  closes with focus on Retry and nothing sent; an unseen secret dialog stays open
83
- over the banner until dismissed), payload sentinels, CSP, and the refusal of a v1 body. `test/operator-endpoints.test.ts` covers the view model.
93
+ over the banner until dismissed), payload sentinels, CSP, and the refusal of a v1 body. Matrix coverage excludes retired grants from
94
+ its live counts and pie, keeps their names inspectable in the popover, and
95
+ renders an unavailable catalog as a dashed empty ring with "?" and an unknown
96
+ accessible name. Cell names distinguish equal grants against different live
97
+ catalog sizes. `test/operator-endpoints.test.ts` covers those view-model cases,
98
+ including a known empty catalog versus an unavailable one.
99
+ `test/operator-connection-map.test.ts` also checks that a failed or unobserved
100
+ catalog counts no tools, which the map draws as a muted dash, while a known
101
+ empty catalog counts zero.
102
+ `test/browser/operator-overview.spec.ts` covers the Overview on the frozen v2
103
+ fixtures: the status pill, stat labels and numbers, map columns, dashed read-only grants and dotted
104
+ tokenless access; attention rows with their fix links, a copied fix prompt equal
105
+ to the catalogue's text, config order through a staggered full refresh, prototype-named
106
+ connector IDs during loading and failure, and stable single-card height when
107
+ its catalog empties. A single long-id, long-title card at 320, 390, 1024 and
108
+ 1440px covers all eight status labels, both auth scopes, with and without the
109
+ Read-only badge, tools and an action. Text rows stay single-line, full text
110
+ remains accessible, links stay focusable, and the detail header and Overview
111
+ attention rows retain their height; the hover
112
+ sentence replacing the legend, and dimming; keyboard focus in column order with each node's sentence
113
+ as its accessible name, Enter opening an endpoint's row and a connector; the
114
+ narrow list, whose caller chips link where their map nodes do; an empty deployment; and a member at `/` getting the member page
115
+ with no maintainer read, plus CSP and no `style` attributes.
84
116
  `test/browser/operator-console.spec.ts` covers UI-3: who-can-use chips by
85
- label, raw id and agent mark and "+N more" from the keyboard; Tokens rows,
117
+ label, raw id and agent mark and "+N" from the keyboard; Tokens rows,
86
118
  issue with the once-only secret dialog, in-row rotate and revoke questions,
87
119
  503, 499 and 409 failures that send exactly one request, slots missing from
88
120
  the list or left unresolved shown with disabled controls until Refresh; and the
@@ -121,15 +153,15 @@ cannot prove these rules.
121
153
 
122
154
  The source of truth is `*-linux.png`, produced on `ubuntu-latest` with the
123
155
  lockfile's Playwright Chromium. Both CI workflows use the same runner, Node 22,
124
- and `npm run test:browser:install -- --with-deps`. macOS runs fixture routing and
156
+ and `pnpm run test:browser:install --with-deps`. macOS runs fixture routing and
125
157
  readiness checks but omits screenshot comparison. The update script refuses
126
158
  non-Linux hosts. Do not commit Darwin baselines.
127
159
 
128
160
  When Docker is unavailable, use the **Operator snapshots** workflow:
129
161
 
130
162
  ```sh
131
- git push --force-with-lease origin ui/834-shell
132
- gh workflow run operator-snapshots.yml --ref ui/834-shell
163
+ git push --force-with-lease origin <branch>
164
+ gh workflow run operator-snapshots.yml --ref <branch>
133
165
  # Read the completed run ID from the Actions page or gh run list.
134
166
  gh run download RUN_ID --name operator-linux-snapshots \
135
167
  --dir test/browser/operator-visual.spec.ts-snapshots
@@ -140,9 +172,9 @@ images and changed files, commit the intentional changes, and push again. The
140
172
  normal CI browser job must compare and pass against that commit. Snapshot
141
173
  updates run only through `workflow_dispatch`.
142
174
 
143
- On a matching Linux runner, `npm ci`, install Chromium with the command above,
144
- then run `npm run test:browser:snapshots`. The normal command is
145
- `npm run test:browser`. Missing baselines fail CI; retries cannot bless them.
175
+ On a matching Linux runner, `pnpm install --frozen-lockfile`, install Chromium with the command above,
176
+ then run `pnpm run test:browser:snapshots`. The normal command is
177
+ `pnpm run test:browser`. Missing baselines fail CI; retries cannot bless them.
146
178
  Set `OPERATOR_VISUAL_INSPECT=1` to retain local screenshots under ignored
147
179
  `test-results/` for inspection, without updating committed baselines.
148
180