@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.
- package/CHANGELOG.md +35 -0
- package/README.md +99 -128
- package/SECURITY.md +1 -1
- package/bin/version.mjs +1 -1
- package/dist/activity.d.ts +3 -0
- package/dist/branding.d.ts +7 -0
- package/dist/branding.js +76 -9
- package/dist/catalog-service.js +15 -1
- package/dist/config-value-sources.js +6 -1
- package/dist/config.d.ts +13 -2
- package/dist/config.js +36 -2
- package/dist/connector-caller.js +2 -1
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +1 -0
- package/dist/execute.js +1 -11
- package/dist/fix-prompt.js +2 -2
- package/dist/index.js +1 -0
- package/dist/membership.d.ts +2 -0
- package/dist/oauth-callback-outcome.js +2 -2
- package/dist/operator-ui/agents.d.ts +1 -1
- package/dist/operator-ui/agents.js +16 -16
- package/dist/operator-ui/config-summary.js +562 -0
- package/dist/operator-ui/connection-map.js +375 -0
- package/dist/operator-ui/endpoints.js +44 -15
- package/dist/operator-ui/fix-prompts.js +1 -1
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/member-page.js +13 -28
- package/dist/operator-ui/overview.js +134 -0
- package/dist/operator-ui/view.d.ts +27 -22
- package/dist/operator-ui/view.js +104 -88
- package/dist/page-styles.js +5 -3
- package/dist/providers/_shared/google/workspace.d.ts +6 -4
- package/dist/providers/_shared/google/workspace.js +6 -2
- package/dist/providers/ccb/index.js +1 -1
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.js +1 -1
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/gmail/index.js +1 -1
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/mixpanel/skill.generated.js +3 -3
- package/dist/providers/notion/skill.generated.js +1 -1
- package/dist/providers/overflow/index.js +1 -1
- package/dist/providers/overflow/skill.generated.js +2 -2
- package/dist/providers/planning-center/index.js +1 -1
- package/dist/providers/planning-center/skill.generated.js +4 -4
- package/dist/providers/revenuecat/skill.generated.js +2 -2
- package/dist/providers/sheets/index.js +1 -1
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.js +1 -1
- package/dist/providers/slides/skill.generated.js +2 -2
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/index.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/registry.js +15 -0
- package/dist/routes/mcp.js +20 -13
- package/dist/routes/oauth.js +2 -2
- package/dist/skills.js +10 -0
- package/dist/storage/sql.js +5 -1
- package/dist/types.d.ts +18 -7
- package/dist/ui.js +9 -4
- package/dist/usage-guide.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/README.md +2 -2
- package/documentation/architecture.md +18 -13
- package/documentation/auth.md +183 -67
- package/documentation/code-mode.md +23 -5
- package/documentation/deploying.md +5 -1
- package/documentation/integrating.md +9 -6
- package/documentation/meta-tools.md +36 -22
- package/documentation/migration-0.33.md +21 -13
- package/documentation/migration-0.34.md +67 -0
- package/documentation/operator-tests.md +46 -20
- package/documentation/operator-ui.md +294 -104
- package/examples/worker/AGENTS.md +15 -9
- package/examples/worker/README.md +41 -14
- package/examples/worker/src/connecta.config.ts +5 -4
- package/examples/worker/wrangler.jsonc +1 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -7
- package/templates/node/README.md +14 -11
- package/templates/node/package.json +1 -1
- 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
|
|
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
|
|
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 `
|
|
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
|
-
* `
|
|
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
|
}
|
package/dist/routes/mcp.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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 {
|
package/dist/routes/oauth.js
CHANGED
|
@@ -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
|
|
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}
|
|
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) {
|
package/dist/storage/sql.js
CHANGED
|
@@ -492,7 +492,11 @@ export function sqlActivityStore(driver, kind, options = {}) {
|
|
|
492
492
|
}
|
|
493
493
|
});
|
|
494
494
|
return {
|
|
495
|
-
describe: () => ({
|
|
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>
|
|
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
|
-
/**
|
|
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 `#
|
|
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 `
|
|
874
|
+
* Defaults to `14px`.
|
|
870
875
|
*/
|
|
871
876
|
radius?: string | number;
|
|
872
|
-
/**
|
|
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
|
-
/**
|
|
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]}
|
|
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">
|
|
150
|
-
|
|
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",
|
package/dist/usage-guide.js
CHANGED
|
@@ -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;
|
|
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.
|
|
1
|
+
export declare const CONNECTA_VERSION = "0.34.0";
|
package/dist/version.js
CHANGED
package/documentation/README.md
CHANGED
|
@@ -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
|
|
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 `
|
|
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 `
|
|
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
|
-
|
|
1240
|
-
page). The UI's `browser.css` imports both,
|
|
1241
|
-
|
|
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
|
|
1263
|
-
|
|
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 `
|
|
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 `
|
|
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()`; `
|
|
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/
|
|
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 `
|
|
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
|