@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
@@ -1,9 +1,9 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Google Slides usage\n\nActs as the signed-in person in Google Slides through Workspace delegation: read, create, and edit the decks they can open.\n\nConnection purpose: \n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open; an agent acts as the\nowner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an account. A call with\nnone mapped, or from an agent whose pool has no owner, fails\n`auth_required`; only an operator can change either. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut. A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires the `revisionId` from the read the requests were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n",
3
+ "content": "# Google Slides usage\n\nActs as the signed-in person in Google Slides through Workspace delegation: read, create, and edit the decks they can open.\n\nConnection purpose: \n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open; an agent acts as the\nowner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an account. A call with\nnone mapped fails `auth_required`; only an operator can change that. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut; pass it with the page it came\n from (the slide's `notesPageId` for a `notesCursor`). A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires `requiredRevisionId`: the `revisionId` from the read the requests\n were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n",
4
4
  "instructionsHeading": "Connection instructions",
5
5
  "fragments": {
6
6
  "guide_0": "# Google Slides usage\n\nActs as the signed-in person in Google Slides through Workspace delegation: read, create, and edit the decks they can open.\n\nConnection purpose: ",
7
- "guide_1": "\n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open; an agent acts as the\nowner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an account. A call with\nnone mapped, or from an agent whose pool has no owner, fails\n`auth_required`; only an operator can change either. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut. A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires the `revisionId` from the read the requests were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n"
7
+ "guide_1": "\n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open; an agent acts as the\nowner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an account. A call with\nnone mapped fails `auth_required`; only an operator can change that. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut; pass it with the page it came\n from (the slide's `notesPageId` for a `notesCursor`). A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires `requiredRevisionId`: the `revisionId` from the read the requests\n were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n"
8
8
  }
9
9
  };
@@ -46,7 +46,7 @@ import { skill } from "./skill.generated.js";
46
46
  * as `.md` embeds a one-operation OpenAPI 3.1 snippet. `scripts/drift-check.mjs`
47
47
  * assembles those snippets for the pages listed in
48
48
  * `src/providers/tithely/drift.json` and digests each operation like any
49
- * other provider's, so `npm run providers:check -- --provider tithely` runs
49
+ * other provider's, so `pnpm run providers:check --provider tithely` runs
50
50
  * credential-free.
51
51
  */
52
52
  import { apiConnector as api, defined } from "../../connectors/api-connector.js";
@@ -5,7 +5,7 @@ import { skill } from "./skill.generated.js";
5
5
  * generated model graph. The token connector reads a pinned operation index
6
6
  * compiled from Vercel's published OpenAPI document (https://openapi.vercel.sh/,
7
7
  * unversioned, so pinned by content hash in `openapi.source.json`);
8
- * `npm run providers:check -- --provider vercel` reports when the live document
8
+ * `pnpm run providers:check --provider vercel` reports when the live document
9
9
  * moves, and `src/providers/vercel/drift.json` also records the contract digest
10
10
  * of every endpoint a named tool is written against.
11
11
  */
@@ -3,7 +3,7 @@ export const skill = {
3
3
  "content": "- Resolve team, project, and deployment ids with list reads; never guess one. Team ids begin `team_`, project ids `prj_`, and deployment ids `dpl_`.\n- Diagnose a deployment by reading its state first (`readyState`, `errorCode`, `errorMessage`), then its build logs, then runtime logs for a deployment that built. Narrow time windows before raising limits.\n- Environment variable changes apply only to future deployments; a new deployment is a separate write. Promoting an existing deployment to production does not rebuild it.\n- Treat every create, update, delete, promotion, cancellation, and purchase as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n",
4
4
  "instructionsHeading": "Account instructions",
5
5
  "fragments": {
6
- "oauth": "\n\nTool names, descriptions, argument schemas, and result schemas come from Vercel's live server. Connecta applies release-reviewed classification; reviewed writes stay writes even when Vercel claims they only read.\n\n- Discover the live catalog before assuming a tool exists. Vercel can change the surface independently of a Connecta release, and account features may affect what the authorization can reach.\n- Complete authorization and grant access to the owning team before acting. Listing this server does not prove team access.\n- MCP pagination and arguments follow the live MCP schemas; do not apply REST pagination or argument shapes to them.\n- Diagnose deployments with `get_deployment`, then build logs, runtime error clusters, and runtime logs.\n- `get_project_env` and `filter_project_envs` can return decrypted environment values, so Connecta reviews them as writes, and a read-only endpoint refuses them. Prefer reading variable keys and targets without values.\n- Purchase tools change billing. Read a quote first and carry its price, idempotency key, and requested term into the confirmed purchase unchanged.\n- `get_access_to_vercel_url` creates a temporary access grant. Treat the URL it returns as a credential and do not expose it outside the requested task.\n- `deploy_to_vercel` and `import-claude-design-from-url` can create or update live projects. Read the target and deployment mode before invoking them.\n- An `auth_required` failure means this connector's OAuth grant is missing or expired. Run `authorize_connector` for this connector id, then retry.\n",
6
+ "oauth": "\n\nTool names, descriptions, argument schemas, and result schemas come from Vercel's live server. Connecta applies release-reviewed classification; reviewed writes stay writes even when Vercel claims they only read.\n\n- Discover the live catalog before assuming a tool exists. Vercel can change the surface independently of a Connecta release, and account features may affect what the authorization can reach.\n- Complete authorization and grant access to the owning team before acting. Listing this server does not prove team access.\n- MCP pagination and arguments follow the live MCP schemas; do not apply REST pagination or argument shapes to them.\n- Diagnose deployments with `get_deployment`, then build logs, runtime error clusters, and runtime logs.\n- `get_project_env` and `filter_project_envs` can return decrypted environment values, so Connecta reviews them as writes, and a read-only endpoint refuses them. Prefer reading variable keys and targets without values.\n- Purchase tools change billing. Read a quote first and carry its price, idempotency key, and requested term into the confirmed purchase unchanged.\n- `get_access_to_vercel_url` creates a temporary access grant. Treat the URL it returns as a credential and do not expose it outside the requested task.\n- `web_fetch_vercel_url` runs application code and is reviewed as destructive; the toolbar reply, reaction, edit, and resolve tools are writes.\n- `deploy_to_vercel` and `import-claude-design-from-url` can create or update live projects. Read the target and deployment mode before invoking them.\n- An `auth_required` failure means this connector's OAuth grant is missing or expired. Run `authorize_connector` for this connector id, then retry.\n",
7
7
  "key": "\n\n- This is Connecta's REST connector to `api.vercel.com`, holding one operator-managed access token. A default team routes calls; it does not narrow what the token can reach.\n- `vercel_api_search` finds API operations, not records: it searches the pinned API index and names the tool that calls each operation. `vercel_api_details` returns one operation's parameters with types, enums, and required names. Neither sends a request.\n- `vercel_api_read` calls GET operations, plus reviewed POSTs that only read: `POST /v2/observability/query`, registrar availability, price, and search, and `POST /v8/artifacts`. `vercel_api_write` calls every other POST, PUT, PATCH, and DELETE, with `method` and `path` stated; a read-only endpoint refuses it.\n- Paths are concrete and carry the version search returned: `/v13/deployments/dpl_123`, not `/deployments/dpl_123`. Pass `query` and `body` as JSON objects named as `vercel_api_details` lists them. Every call is checked against the pinned index before it is sent: an unknown path returns the nearest operations, and an unknown or missing parameter returns `validation.issues` with the accepted names. Nothing reached Vercel, so fix the call from that answer.\n- Results arrive as `{ status, data, page? }`. On a list, `page.next` is the cursor and `page.param` the query parameter to pass it as: `until` (a millisecond timestamp from `pagination.next`) on most lists, `cursor` on newer ones. Lists belong inside `execute_code`; pass `select` (dot paths such as `deployments.uid` and `deployments.state`) to keep only the fields the question needs.\n- `list_teams` returns team ids and slugs and the connection's default team. `get_deployment_build_logs` reads a deployment's build events without following live output. `get_runtime_logs` collects the runtime log stream for at most `waitMs` or `maxRows`, then stops and says why. Generic reads refuse `follow` and the runtime-log stream, which never end on their own.\n- `vercel_api_upload` sends explicit text or base64 bytes to an octet-stream operation, such as `POST /v2/files` before a deployment (Connecta computes `x-vercel-digest`). `vercel_api_write` sends JSON only.\n- Environment values never come back. `list_project_env_vars` never decrypts. `upsert_project_env_var` (`upsert: false` for create-only), `update_project_env_var`, and `delete_project_env_var` return metadata without values; values are write input only. Generic reads refuse `GET /v1/projects/{idOrName}/env/{id}`, `GET /v10/projects/{idOrName}/env`, `GET /v1/env/{id}`, and any `decrypt`, and Connecta replaces the value of every environment variable in any result with `\"[redacted]\"`, including project reads, shared variables, and audit events.\n- Other secrets read as `[redacted]` in every result: credential-named fields and their whole contents (`token`, `secret`, `password`, `jwt`, `credentials`, `Authorization`, …), protection-bypass secrets (each bypass's metadata stays under a numbered placeholder), deploy hook URLs, drain and webhook headers and signing secrets (including the one a new webhook or drain returns), drain and webhook destination URLs and external route, rewrite, and redirect destinations beyond their scheme and host, the args and values of every header, cookie, and query rule (route transforms and conditions, firewall conditions), team invite codes, synced Global Config items, and credentials inside other URLs. Failures of every reviewed operation, and of any operation whose request takes a credential-named field, say only the status and a reviewed code. Read or rotate withheld secrets in the Vercel dashboard. A `[redacted]` field is never the real value; do not send it back.\n- Refused by design: endpoints that mint credentials (access tokens, AI Gateway API keys, project OIDC and trace tokens, Connect and SSO tokens, Edge Config tokens, KMS-signed tokens, Flags SDK keys, protection-bypass secrets, installation credential rotation), Connect authorization requests, project transfer requests (the code lets another team claim the project), KMS signing, a domain move-out (its answer is a transfer token), Global Config items, backups, and tokens, and domain transfer auth codes. Each refusal names its reason.\n- The hosted OAuth connection's documentation search, runtime error clusters, toolbar threads, agent runs, web analytics summaries, purchase quotes, `deploy_to_vercel`, and access grants for protected URLs do not exist with a token. Configure an OAuth connector for those.\n- A `429` carries Vercel's reset time; Vercel meters endpoints separately, so wait before retrying the same operation. An `auth_required` failure means the token is missing, expired, or outside this team: an operator must replace it in this connection in the operator UI, and `authorize_connector` returns that handoff. A rejected argument comes back in Vercel's own words; read it rather than replacing the token.\n- Embedded URL safety uses reviewed paths plus global signing parameters. Generic credential-named query parameters (`code`, `key`, `token`, `sig`, etc.) and credential-looking fragments withhold the destination's entire query/fragment only in deployment `errorMessage`, build-event `payload.text`/`text`, and runtime-log `message` on the reviewed deployment/log operations and vendor error text. Ordinary `code`/`key` links can therefore be false positives in these diagnostics. Deployment metadata and other application content keep those ordinary URL parameters byte for byte. Literal AWS SigV4/Google V4 `X-Amz-*`/`X-Goog-*` Signature, Credential and Security-Token values are withheld in every decoded string before projection. A withheld value is not the original: do not write it back. Deployment creation also inspects decoded `files[].data` when `encoding` is `base64` (one decode bounded by the field's encoded length and the host's request limit; malformed base64 is refused). All writes, including named writes and supported raw/base64/multipart uploads, reject URL destinations whose own query/fragment contains the exact `[redacted]` placeholder, plain or encoded, with `invalid_args` before dispatch. Admission iteratively checks every JSON key and value within the host's accepted request size, without separate depth or node-count ceilings; cyclic direct arguments are refused. A marker outside the destination remains ordinary text. Split strings, encoded host/signing names, bare hosts without `//`, and opaque bearer paths remain outside this scan.\n",
8
8
  "shared": "- Resolve team, project, and deployment ids with list reads; never guess one. Team ids begin `team_`, project ids `prj_`, and deployment ids `dpl_`.\n- Diagnose a deployment by reading its state first (`readyState`, `errorCode`, `errorMessage`), then its build logs, then runtime logs for a deployment that built. Narrow time windows before raising limits.\n- Environment variable changes apply only to future deployments; a new deployment is a separate write. Promoting an existing deployment to production does not rebuild it.\n- Treat every create, update, delete, promotion, cancellation, and purchase as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n"
9
9
  }
package/dist/registry.js CHANGED
@@ -16,6 +16,8 @@ import { GUIDE_SUMMARY_LENGTH, normalizeGuideSummary } from "./skills.js";
16
16
  import { attachOAuthSealer, vaultOAuthSealer } from "./oauth-sealing.js";
17
17
  import { attachOAuthPartition, oauthPartitionIdle } from "./oauth-partition.js";
18
18
  import { attachCaller } from "./connector-caller.js";
19
+ import { isWorkspaceAddress } from "./workspace-address.js";
20
+ import { workspaceSubjectOf } from "./workspace-subject.js";
19
21
  import { runEdge } from "./runtime/run.js";
20
22
  import { SharedRead } from "./runtime/shared-read.js";
21
23
  import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageSet } from "./runtime/storage.js";
@@ -997,10 +999,23 @@ class ScopedRegistryView {
997
999
  const connector = this.root.getConnector(id);
998
1000
  if (!connector)
999
1001
  return undefined;
1002
+ // An agent has no Workspace account: on a mapping-subject Workspace
1003
+ // connector it acts as the pool's owner. Without one there is no account
1004
+ // to act as, so the connector is absent, as a personal connector is.
1005
+ if (this.agentWithoutOwner && workspaceSubjectOf(connector)?.kind === "mapping")
1006
+ return undefined;
1000
1007
  if (connector.authScope !== "personal")
1001
1008
  return this.root;
1002
1009
  return admission ? this.admissionPersonal : this.personal;
1003
1010
  }
1011
+ /** An agent admitted through a pool that names no Workspace owner. */
1012
+ get agentWithoutOwner() {
1013
+ const caller = this.scope.caller;
1014
+ if (caller?.identity.agent === undefined)
1015
+ return false;
1016
+ // The same test the Workspace provider applies before acting as the owner.
1017
+ return caller.pool === undefined || !isWorkspaceAddress(caller.poolOwner);
1018
+ }
1004
1019
  listConnectors() {
1005
1020
  return this.root.listConnectors().filter((connector) => this.registryFor(connector.id) !== undefined);
1006
1021
  }
@@ -8,7 +8,7 @@ import { Duration, Effect, Exit, Option, Result, Scope } from "effect";
8
8
  import { registerExecuteTool } from "../execute.js";
9
9
  import { ExecutorAdmissionError } from "../executor-admission.js";
10
10
  import { registerMetaTools } from "../meta-tools.js";
11
- import { CONNECTA_INSTRUCTIONS } from "../skills.js";
11
+ import { endpointInstructions } from "../skills.js";
12
12
  import { registerSkills } from "./skills.js";
13
13
  import { failureRecord, logFailure } from "../operator-record.js";
14
14
  import { redactAgentOutput, sentSecretsForRequest } from "../sent-secrets.js";
@@ -341,7 +341,10 @@ function serveMcp(request, requestSignal, requestScope, opts, baseUrl, actor, re
341
341
  // A request-local server cannot publish catalog changes. Set this before
342
342
  // tool registration, whose SDK default otherwise advertises listChanged.
343
343
  capabilities: { tools: { listChanged: false }, extensions: { "io.modelcontextprotocol/skills": {} } },
344
- instructions: CONNECTA_INSTRUCTIONS,
344
+ // Instructions differ per endpoint only by the admitted pool's
345
+ // description. Every cache hint below is private, so a client keeps
346
+ // what it learned per endpoint URL and never shares it across them.
347
+ instructions: endpointInstructions(pool !== undefined ? opts.pools.get(pool)?.description : undefined),
345
348
  inputRequired: { legacyShim: false },
346
349
  requestState: { verify: (state, context) => authElicitation.verify(state, context) },
347
350
  cacheHints: {
@@ -738,6 +741,18 @@ export function createMcpRoute(opts) {
738
741
  return yield* refuseAdmission(late.failure);
739
742
  }
740
743
  }
744
+ // The caller rides on the view, and the paging recheck builds the
745
+ // same one: an agent's Workspace visibility depends on the owner.
746
+ const callerFor = (identity, owner) => ({
747
+ identity,
748
+ // `authorize` admits an open deployment's every request as the
749
+ // anonymous actor; only a provider's `ok` is an authentication.
750
+ authenticated: opts.config.auth.length > 0,
751
+ // Both pool endpoints build the caller here from the same
752
+ // admitted scope, so an agent at /agent/<pool> carries the owner.
753
+ ...(poolName !== undefined ? { pool: poolName } : {}),
754
+ ...(owner !== undefined ? { poolOwner: owner } : {}),
755
+ });
741
756
  let scopedRegistry;
742
757
  try {
743
758
  scopedRegistry = opts.registry.scoped({
@@ -760,11 +775,12 @@ export function createMcpRoute(opts) {
760
775
  const currentScope = scopeFor(current);
761
776
  if (!currentScope)
762
777
  return false;
763
- const { access: currentAccess, readOnly: currentReadOnly } = currentScope;
778
+ const { access: currentAccess, readOnly: currentReadOnly, poolOwner: currentOwner } = currentScope;
764
779
  const view = opts.registry.scoped({
765
780
  ...currentAccess,
766
781
  ...(current.subjectKey ? { subjectKey: current.subjectKey } : {}),
767
782
  ...(current.principalKey ? { principalKey: current.principalKey } : {}),
783
+ caller: callerFor(current.identity, currentOwner),
768
784
  });
769
785
  if (classification === "write" && currentReadOnly)
770
786
  return false;
@@ -789,16 +805,7 @@ export function createMcpRoute(opts) {
789
805
  await closeConnectorScope(resolved.connector, view.contextFor(resolved.connector.id, baseUrl, requestScope), runtimeContext?.waitUntil?.bind(runtimeContext));
790
806
  }
791
807
  },
792
- caller: {
793
- identity: authz.identity,
794
- // `authorize` admits an open deployment's every request as the
795
- // anonymous actor; only a provider's `ok` is an authentication.
796
- authenticated: opts.config.auth.length > 0,
797
- // Both pool endpoints build the caller here from the same
798
- // admitted scope, so an agent at /agent/<pool> carries the owner.
799
- ...(poolName !== undefined ? { pool: poolName } : {}),
800
- ...(poolOwner !== undefined ? { poolOwner } : {}),
801
- },
808
+ caller: callerFor(authz.identity, poolOwner),
802
809
  });
803
810
  }
804
811
  catch {
@@ -38,7 +38,7 @@ function html(reason, opts, connector) {
38
38
  const details = outcome.fixPrompt
39
39
  ? `<details class="status-details">
40
40
  <summary>Details for the operator</summary>
41
- <p>If this keeps happening, send this to whoever runs ${escapeHtml(brand.productName)}. It is written for a coding agent working on the deployment and carries nothing from the provider's response.</p>
41
+ <p>If this keeps happening, send this to whoever runs ${escapeHtml(brand.productName)}. It is for a coding agent and holds nothing from the provider.</p>
42
42
  <pre>${escapeHtml(outcome.fixPrompt)}</pre>
43
43
  </details>`
44
44
  : "";
@@ -55,7 +55,7 @@ function html(reason, opts, connector) {
55
55
  </section>
56
56
  </main>`;
57
57
  return new Response(renderPage(opts.config.ui?.branding, {
58
- title: `${outcome.heading} — ${brand.pageTitle}`,
58
+ title: `${outcome.heading} · ${brand.pageTitle}`,
59
59
  uiMounted,
60
60
  body,
61
61
  }), { status: outcome.status, headers: htmlSecurityHeaders({ "Content-Type": "text/html; charset=utf-8" }) });
package/dist/skills.js CHANGED
@@ -13,6 +13,16 @@ const RECOVERY = 'When an auth error includes a handoff, give it to the user or
13
13
  * and describe and refuses every write call. Guides may still name them.
14
14
  */
15
15
  export const CONNECTA_INSTRUCTIONS = `${ROUTE} On a writable endpoint, programs and call_destructive_tool may write. On a read-only endpoint, search omits write tools and every write call is refused; guides may still name them. The execute_code description says which this endpoint is. Never repeat a write to recover its output. ${RECOVERY}`;
16
+ /**
17
+ * The instructions one endpoint serves: {@link CONNECTA_INSTRUCTIONS}, then
18
+ * the admitted pool's `description` after a fixed lead-in. Plain `/mcp` and a
19
+ * pool without a description serve the constant unchanged. The description
20
+ * comes from deployment config for the pool the request was admitted through;
21
+ * nothing a request sends reaches it.
22
+ */
23
+ export function endpointInstructions(description) {
24
+ return description === undefined ? CONNECTA_INSTRUCTIONS : `${CONNECTA_INSTRUCTIONS} This endpoint: ${description}`;
25
+ }
16
26
  export { USAGE_SKILL } from "./usage-guide.js";
17
27
  /** True when at least one of `connectors` carries a usage guide. */
18
28
  export function hasConnectorGuides(connectors) {
@@ -492,7 +492,11 @@ export function sqlActivityStore(driver, kind, options = {}) {
492
492
  }
493
493
  });
494
494
  return {
495
- describe: () => ({ kind, retentionDays }),
495
+ describe: () => ({
496
+ kind,
497
+ retentionDays,
498
+ optionSources: { retentionDays: options.retentionDays === undefined ? "default" : "config" },
499
+ }),
496
500
  async record(event) {
497
501
  await ensure();
498
502
  const occurredAtMs = Date.parse(event.occurredAt);
package/dist/types.d.ts CHANGED
@@ -822,7 +822,7 @@ export interface ConnectaBranding {
822
822
  description?: string;
823
823
  /**
824
824
  * Browser tab title and page meta name. Defaults to
825
- * `"<productName> — <ownerName>"`, or just `productName` when no owner is set.
825
+ * `"<productName> · <ownerName>"`, or just `productName` when no owner is set.
826
826
  */
827
827
  pageTitle?: string;
828
828
  /**
@@ -832,14 +832,18 @@ export interface ConnectaBranding {
832
832
  * elsewhere (it replaces the `/favicon.svg` link in the page head; the
833
833
  * `/favicon.*` routes still serve whatever `svg`/`ico` provide). `href` must
834
834
  * be an absolute `http(s)` URL or a root-relative path; anything else falls
835
- * back to the default mark.
835
+ * back to the default mark. With `svg` set, the top bar shows it beside the
836
+ * name; the drawn mark shows only when no branding is set.
836
837
  */
837
838
  favicon?: {
838
839
  svg?: string;
839
840
  ico?: Uint8Array;
840
841
  href?: string;
841
842
  };
842
- /** `theme-color` meta value. Defaults to "#ffffff". */
843
+ /**
844
+ * `theme-color` meta value. Unset, pages follow the page background:
845
+ * `#f6f4ef` in light and `#12110f` in dark.
846
+ */
843
847
  themeColor?: string;
844
848
  /**
845
849
  * Operator-page appearance. Every field is optional, and a rejected value
@@ -860,18 +864,25 @@ export interface ConnectaBranding {
860
864
  export interface ConnectaTheme {
861
865
  /**
862
866
  * The brand color: links, focus rings, primary actions, the active nav item.
863
- * Hex only (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Defaults to `#2f5fe0`.
867
+ * Hex only (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Defaults to `#4c44e0`
868
+ * (`#8c86ff` in dark). Text on it and accent text in dark derive from it.
864
869
  */
865
870
  accent?: string;
866
871
  /**
867
872
  * Corner rounding for cards, inputs, and buttons. A CSS length (`10px`,
868
873
  * `0.5rem`) or a bare number read as pixels. `0` restores square corners.
869
- * Defaults to `10px`.
874
+ * Defaults to `14px`.
870
875
  */
871
876
  radius?: string | number;
872
- /** Body font stack. A plain CSS font-family list. Defaults to a system stack. */
877
+ /**
878
+ * Body font stack. A plain CSS font-family list. Defaults to Hanken
879
+ * Grotesk, served with the operator UI, then a system stack.
880
+ */
873
881
  fontFamily?: string;
874
- /** Monospace font stack for addresses, ids, and the endpoint URL. */
882
+ /**
883
+ * Monospace font stack for addresses, ids, and the endpoint URL. Defaults
884
+ * to JetBrains Mono, served with the operator UI, then a system stack.
885
+ */
875
886
  monoFamily?: string;
876
887
  /**
877
888
  * `"system"` (the default) follows the operator's OS setting; `"light"` and
package/dist/ui.js CHANGED
@@ -6,9 +6,10 @@ import { uiData } from "./routes/ui-data.js";
6
6
  import { runEdge } from "./runtime/run.js";
7
7
  import { OPERATOR_UI_STYLE_PATH, OPERATOR_UI_SCHEME_PATH, OPERATOR_UI_SCRIPT_PATH, OPERATOR_UI_NOTICES_PATH, } from "./operator-ui/generated.js";
8
8
  export { filterUiConnectors, } from "./operator-ui/model.js";
9
- import { escapeHtml, isSafeHttpsUrl, renderPage, resolveBranding } from "./branding.js";
9
+ import { brandMark, escapeHtml, isSafeHttpsUrl, renderPage, resolveBranding } from "./branding.js";
10
10
  export { CONNECTA_FAVICON_SVG, resolveBranding, isSafeHttpUrl, isSafeHttpsUrl, isSafeIconHref } from "./branding.js";
11
11
  const OPERATOR_PAGE_LABELS = {
12
+ overview: "Overview",
12
13
  endpoints: "Endpoints",
13
14
  connections: "Connectors",
14
15
  connector: "Connector",
@@ -18,6 +19,8 @@ const OPERATOR_PAGE_LABELS = {
18
19
  };
19
20
  export function operatorPageForPath(path) {
20
21
  if (path === "/")
22
+ return "overview";
23
+ if (path === "/endpoints")
21
24
  return "endpoints";
22
25
  if (path === "/connectors")
23
26
  return "connections";
@@ -36,7 +39,7 @@ export function operatorPageForPath(path) {
36
39
  return undefined;
37
40
  }
38
41
  export function operatorPageTitle(page, configuredTitle) {
39
- return `${OPERATOR_PAGE_LABELS[page]} — ${configuredTitle}`;
42
+ return `${OPERATOR_PAGE_LABELS[page]} · ${configuredTitle}`;
40
43
  }
41
44
  /** Report the verdict the registry stored, independent of whether an endpoint is read-only. */
42
45
  export function uiToolSafety(definition) {
@@ -146,8 +149,8 @@ _nonce, page = "endpoints") {
146
149
  <h1>${OPERATOR_PAGE_LABELS[page]}</h1>
147
150
  <div class="lead-copy">
148
151
  <p>${escapeHtml(brand.description)}</p>
149
- <noscript><p class="msg">The operator pages need JavaScript. Nothing else here
150
- does — agents reach this deployment through <span class="mono">/mcp</span>.</p></noscript>
152
+ <noscript><p class="msg">Operator pages need JavaScript. Agents connect at
153
+ <span class="mono">/mcp</span>.</p></noscript>
151
154
  </div>
152
155
  </div>
153
156
  </main>
@@ -159,6 +162,7 @@ _nonce, page = "endpoints") {
159
162
  schemePinned: brand.theme.colorScheme !== "system",
160
163
  titleSuffix: brand.pageTitle,
161
164
  productName: brand.productName,
165
+ brandMark: brandMark(branding, true),
162
166
  productDescription: brand.description,
163
167
  productOperatorLabel: brand.productName + " operator",
164
168
  })}</script><script defer src="${OPERATOR_UI_SCRIPT_PATH}"></script>`,
@@ -182,6 +186,7 @@ export function operatorUi(options = {}) {
182
186
  options = assertKnownOptions(options, "operatorUi()", OPERATOR_UI_OPTIONS);
183
187
  const reservedPaths = [
184
188
  "/",
189
+ "/endpoints",
185
190
  "/connectors",
186
191
  "/connectors/*",
187
192
  "/tools",
@@ -77,7 +77,7 @@ async () => {
77
77
 
78
78
  Host-call budget exhaustion ends the run with budget_exceeded. Catch and
79
79
  allSettled cannot recover it or return partial output. Bound batches before
80
- starting them; search, describe, call, result, and skill spend host calls.
80
+ starting them; every guest call except emit spends host calls.
81
81
  Split remaining reads. Never replay writes for their output.
82
82
 
83
83
  ## Per-connector guides
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const CONNECTA_VERSION = "0.33.0";
1
+ export declare const CONNECTA_VERSION = "0.34.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs.
2
2
  // Workers-safe: no runtime filesystem read or deployment override.
3
- export const CONNECTA_VERSION = "0.33.0";
3
+ export const CONNECTA_VERSION = "0.34.0";
@@ -11,7 +11,7 @@ For a new deployment, start with [Deploying Connecta](./deploying.md).
11
11
  | Task | Self-contained procedure |
12
12
  | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
13
13
  | Add or remove a connector | [Change the connector roster](./integrating.md#add-or-remove-a-connector) |
14
- | Organize an agent's pools | [Organizing an agent's pools](./auth.md#organizing-an-agents-pools) |
14
+ | Organize an agent's pools and tiers | [Organizing an agent's pools](./auth.md#organizing-an-agents-pools) |
15
15
  | Add a person (maintainer or pool member) | [Add a person](./auth.md#add-a-person), [member page](./operator-ui.md#use-the-member-page) |
16
16
  | Connect a shared credential | [Connect a shared credential](./auth.md#connect-a-shared-credential) |
17
17
  | Issue, rotate, or revoke an agent token | [Manage an agent token](./auth.md#issue-rotate-or-revoke-an-agent-token) |
@@ -71,7 +71,7 @@ own scoped change procedures. Each [provider folder](https://github.com/zackbart
71
71
  owns its maintained `SKILL.md`, options, tests, fixtures, and review data.
72
72
  Use those details instead of copying provider catalogs into general guides.
73
73
  The provider list in the human README is generated; preserve its marker pair
74
- and run `npm run check:providers-generated` after edits.
74
+ and run `pnpm run check:providers-generated` after edits.
75
75
 
76
76
  When changing behavior, update its owning contract and task guide in the same
77
77
  change. Check examples against current source and point to the tests that
@@ -174,7 +174,7 @@ never select an identity, grant, connector view, or pool. Direct and program
174
174
  activity records retain only the self-declared `clientName` and `clientVersion`,
175
175
  validated by one ASCII grammar at the record builder, SQL write/read boundaries,
176
176
  and authenticated activity read route. Names match
177
- `[A-Za-z0-9][A-Za-z0-9 ._@/+-]{0,63}`; versions match
177
+ `[A-Za-z0-9@][A-Za-z0-9 ._@/+-]{0,63}`; versions match
178
178
  `[A-Za-z0-9][A-Za-z0-9._+-]{0,31}`, each as a whole string. Names allow up to
179
179
  64 characters, versions up to 32. `__proto__`, `constructor`, and `prototype`
180
180
  are reserved. Invalid values are absent, never truncated or escaped. Capabilities and other client metadata never enter activity.
@@ -970,7 +970,7 @@ manual review evidence instead of claiming a machine-checkable JSON pin.
970
970
 
971
971
  The generic REST connector (`src/providers/_shared/rest/`) serves
972
972
  `<vendor>_api_search`, `_api_details`, `_api_read`, and `_api_write` over an
973
- `OperationIndex` that `npm run providers:spec` compiles offline from the
973
+ `OperationIndex` that `pnpm run providers:spec` compiles offline from the
974
974
  provider's pinned `openapi.source.json` into `openapi.generated.ts`.
975
975
  Each call is matched to an operation and checked against its parameters before
976
976
  transport; `_api_read` admits GET and the vendor's reviewed read-only POSTs in
@@ -1236,10 +1236,13 @@ connecta shows a person — the operator shell, the OAuth callback, a browser's
1236
1236
  404 — reads it. `src/operator-ui/tokens.css`
1237
1237
  resolves every color, radius, and font through a custom property and mixes the
1238
1238
  rest from those with `color-mix`; `page.css` holds the base typography and the
1239
- primitives (shell, masthead, buttons, badges, messages, the one-message status
1240
- page). The UI's `browser.css` imports both, and the build writes the same CSS,
1241
- minified, into `src/page-styles.ts`, a module of two strings that core imports
1239
+ pieces server-rendered pages share (top bar, buttons, badges, messages, the
1240
+ one-message status page). The UI's `browser.css` imports both, then one file
1241
+ per area under `src/operator-ui/styles/` (fonts, shared components, shell, and
1242
+ each page). The build writes the shared CSS, minified, into
1243
+ `src/page-styles.ts`, with the hashed font paths, a module that core imports
1242
1244
  without the UI bundle, so an OAuth callback still renders with the UI omitted.
1245
+ Those pages link the fonts only when the UI module serves them.
1243
1246
  `renderPage` in `src/branding.ts` is the one layout: head, favicon (the default
1244
1247
  is linked only when the UI serves it), `data-scheme`, the tokens with the theme
1245
1248
  after them, and the masthead. So `branding.theme` only has to append a `:root`
@@ -1253,14 +1256,16 @@ Maintained providers act as the caller downstream. `src/connector-caller.ts`
1253
1256
  attaches the admitted identity beside the request-scoped context, readable only
1254
1257
  by in-repo code and set from authorization, never arguments. Google Workspace
1255
1258
  domain-wide delegation
1256
- (`src/providers/google/`, first consumed by `./providers/gmail`) hands the
1259
+ (`src/providers/_shared/google/`, first consumed by `./providers/gmail`) hands the
1257
1260
  admitted identity to a deployment-config `subject` function and mints a
1258
1261
  service-account token as whatever Workspace address it returns. The caller
1259
1262
  carries whether an inbound provider _authenticated_ it: an open deployment
1260
1263
  admits everyone as the anonymous actor, and the function is never asked about
1261
1264
  them. It also carries the admitted pool's `owner`, which core copies from
1262
- config: an agent acts as it, before and instead of the function, and an agent
1263
- in a pool without one fails closed. A fixed `subject` address is every
1265
+ config: an agent acts as it, before and instead of the function. In a pool
1266
+ without one, the scoped registry view omits a mapping-subject connector for an
1267
+ agent (`src/registry.ts`, keyed on the tag in `src/workspace-subject.ts`), as it
1268
+ omits a personal connector, and the provider still fails closed if reached. A fixed `subject` address is every
1264
1269
  caller's. No caller, an unauthenticated one, or no address fails
1265
1270
  `auth_required` before any request leaves. The provider never chooses the account and a
1266
1271
  deployment's own connector still cannot read the caller (PRINCIPLES.md, INV-3; `test/google-workspace-delegation.test.ts`). Its tokens live in a
@@ -1321,7 +1326,7 @@ Effect callers skip the Promise and take the program: `admit` and
1321
1326
  and `acquireCallScoped` for call admission, `closeScope` and `closeScopeOnExit`
1322
1327
  for a connector scope, `withDeadlineEffect` where Promise code has
1323
1328
  `withDeadline`. The build prunes every declaration no `exports` target reaches,
1324
- and `npm run check:declarations` fails if what remains names an Effect type.
1329
+ and `pnpm run check:declarations` fails if what remains names an Effect type.
1325
1330
 
1326
1331
  ### One runner
1327
1332
 
@@ -1456,7 +1461,7 @@ resolve a single copy. Adopting an unstable subpath is its own pull request.
1456
1461
 
1457
1462
  The stable `4.0.0` release was re-evaluated on 2026-10-01 with
1458
1463
  [`scripts/probes/effect-v4-evaluation.mjs`](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation.mjs).
1459
- Run `npm run build`, then `node scripts/probes/effect-v4-evaluation.mjs output.json`.
1464
+ Run `pnpm run build`, then `node scripts/probes/effect-v4-evaluation.mjs output.json`.
1460
1465
  The [raw observations](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation-2026-10-01.json)
1461
1466
  are synthetic Node requests and neutral esbuild bundles, not live-client or
1462
1467
  workerd lifetime verification. Bundle figures add representative prototypes
@@ -1494,7 +1499,7 @@ replacement's benefit or compatibility.
1494
1499
  The core's cost is recorded rather than guessed. Against 0.24.4 the root entry
1495
1500
  grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to
1496
1501
  314,336, and each provider by about 36 KB, since each reaches Effect through
1497
- `remoteMcp()`; `npm run check:bundle` caps every entry against
1502
+ `remoteMcp()`; `pnpm run check:bundle` caps every entry against
1498
1503
  `scripts/bundle-budget.json`. An install carries about 53 MB of `effect`, with
1499
1504
  no dependencies of its own.
1500
1505
 
@@ -1510,7 +1515,7 @@ src/
1510
1515
  catalog-drift.ts reviewed classifications and the drift counts a refresh produces
1511
1516
  provider.ts defineProvider(): the one maintained-provider shape
1512
1517
  activity.ts optional history factory and best-effort recorder
1513
- auth/ bearer, Cloudflare Access, clerk (optional peer), downstream OAuth (remote and static)
1518
+ auth/ Cloudflare Access, clerk (optional peer), downstream OAuth (remote and static)
1514
1519
  executors/ the QuickJS pool and child (Node only)
1515
1520
  node.ts listen() (Node only)
1516
1521
  d1.ts, sqlite.ts the two storage drivers; storage/ holds the SQL core and key families
@@ -1518,7 +1523,7 @@ src/
1518
1523
 
1519
1524
  There are exactly two deployment shapes — `templates/node/`, which
1520
1525
  `connecta init` copies with its container files, and `examples/worker/` — and
1521
- `test/deployment-shapes.node.test.ts` with `npm run check:examples` keeps both
1526
+ `test/deployment-shapes.node.test.ts` with `pnpm run check:examples` keeps both
1522
1527
  compiling and configuring the real thing.
1523
1528
 
1524
1529
  ## Sharp edges