@zackbart/connecta 0.38.0 → 0.40.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 +91 -0
- package/README.md +3 -2
- package/bin/connecta.mjs +4 -8
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +6 -4
- package/dist/agent-routes.js +231 -0
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2382
- package/dist/auth/native.js +37 -0
- package/dist/auth/oauth-grant.js +13 -0
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth-elicitation.js +7 -1
- package/dist/authorization-handoff.js +11 -5
- package/dist/catalog-cache.js +219 -59
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +284 -47
- package/dist/catalog.js +18 -2
- package/dist/config.d.ts +16 -11
- package/dist/config.js +9 -6
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +7 -4
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +682 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +473 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +555 -0
- package/dist/connectors/remote-mcp.js +62 -2863
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +2 -0
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +152 -114
- package/dist/executor-result.js +3 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +60 -19
- package/dist/guest-types.d.ts +19 -5
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/errors.js +5 -4
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/oauth.js +3 -2
- package/dist/http/provider.js +32 -4
- package/dist/http/safety.js +8 -0
- package/dist/http/tools.js +44 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation.js +26 -8
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +54 -31
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +5 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +135 -13
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +1 -0
- package/dist/operator-ui/contract.d.ts +10 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/provider-call-facts.js +6 -2
- package/dist/providers/_shared/rest/operation-index.js +48 -50
- package/dist/providers/_shared/rest/value-safety.js +80 -0
- package/dist/providers/breeze/api.js +7 -1
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +4 -3
- package/dist/providers/ccb/api.js +2 -1
- package/dist/providers/ccb/index.js +5 -0
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/cloudflare/api.js +8 -1
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +2 -4
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +11 -16
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +10 -7
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +7 -5
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +7 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/planning-center/api.js +3 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +13 -14
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +15 -13
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +3 -1
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/vercel/api.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +4 -3
- package/dist/registry.d.ts +10 -4
- package/dist/registry.js +41 -13
- package/dist/routes/credentials.js +129 -60
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +30 -14
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +6 -1
- package/dist/routes/shared.js +7 -1
- package/dist/routes/ui-config.js +2 -2
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +16 -0
- package/dist/runtime/call.js +7 -2
- package/dist/runtime/native-storage.js +12 -0
- package/dist/runtime/request-reads.js +207 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/storage.js +12 -0
- package/dist/skills.js +37 -20
- package/dist/storage/keys.js +53 -3
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +53 -1
- package/dist/storage/sql.js +12 -2
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/types.d.ts +33 -0
- package/dist/usage-guide.js +16 -35
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +13 -13
- package/documentation/architecture.md +385 -20
- package/documentation/auth.md +233 -64
- package/documentation/code-mode.md +122 -39
- package/documentation/deploying.md +6 -3
- package/documentation/integrating.md +16 -2
- package/documentation/meta-tools.md +240 -49
- package/documentation/migration-0.33.md +5 -1
- package/documentation/migration-0.34.md +3 -1
- package/documentation/operator-tests.md +1 -1
- package/documentation/operator-ui.md +105 -12
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +10 -10
- package/examples/worker/README.md +4 -3
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +2 -1
- package/guides/how-it-works.md +4 -2
- package/package.json +1 -1
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { routePhrases } from "../../agent-routes.js";
|
|
1
2
|
import { asRecord, asArray, compact } from "../_shared/json.js";
|
|
2
3
|
// Google Sheets named projections, bounded cell reads and guarded writes.
|
|
3
4
|
import { Effect, Option, Schema } from "effect";
|
|
@@ -8,7 +9,7 @@ import { clampText, jsonBytes, RESULT_BUDGET_BYTES } from "../_shared/google/res
|
|
|
8
9
|
import { encodeForAgent, redactBeforeCut } from "../../sent-secrets.js";
|
|
9
10
|
import { read, write } from "../../http/tools.js";
|
|
10
11
|
import { boundCursor } from "../../http/paging.js";
|
|
11
|
-
import {
|
|
12
|
+
import { fieldsContract, selectFields } from "../../http/fields.js";
|
|
12
13
|
import { Operation } from "../../runtime/call.js";
|
|
13
14
|
import { sheetsIndex } from "./lib/hatch.js";
|
|
14
15
|
import { SHEETS_ADMITTED_KINDS, SHEETS_REFUSED_KINDS, sheetsExternalDataProblem } from "./lib/requests.js";
|
|
@@ -150,19 +151,19 @@ function cappedCell(ctx, whole, max, cut) {
|
|
|
150
151
|
return `${value.slice(0, max)}[… ${value.length - max} more characters truncated; read this cell alone with maxCellChars up to ${MAX_CELL_CHARS} for the rest]`;
|
|
151
152
|
}
|
|
152
153
|
/** Refuse a row no page of `budget` bytes can hold, rather than overrun the cap. */
|
|
153
|
-
function rowTooLarge(range, row, needed, budget) {
|
|
154
|
-
return new ConnectorCallError("invalid_args", `Row ${row} of ${range} needs a page of ${needed} bytes with the page's own fields, more than this page's maxBytes of ${budget}; narrow the range's columns or lower maxCellChars${raiseBytes(needed)}.`);
|
|
154
|
+
function rowTooLarge(range, row, needed, budget, phrases) {
|
|
155
|
+
return new ConnectorCallError("invalid_args", `Row ${row} of ${range} needs a page of ${needed} bytes with the page's own fields, more than this page's maxBytes of ${budget}; narrow the range's columns or lower maxCellChars${raiseBytes(needed, phrases)}.`);
|
|
155
156
|
}
|
|
156
157
|
/** Refuse a page too small for even one range's own fields. */
|
|
157
|
-
function pageTooSmall(range, needed, budget) {
|
|
158
|
-
return new ConnectorCallError("invalid_args", `A page reporting ${range} needs ${needed} bytes before any of its rows, more than this page's maxBytes of ${budget}; shorten the range${raiseBytes(needed)}.`);
|
|
158
|
+
function pageTooSmall(range, needed, budget, phrases) {
|
|
159
|
+
return new ConnectorCallError("invalid_args", `A page reporting ${range} needs ${needed} bytes before any of its rows, more than this page's maxBytes of ${budget}; shorten the range${raiseBytes(needed, phrases)}.`);
|
|
159
160
|
}
|
|
160
|
-
function raiseBytes(needed) {
|
|
161
|
+
function raiseBytes(needed, phrases) {
|
|
161
162
|
if (needed > MAX_PAGE_BYTES)
|
|
162
163
|
return "";
|
|
163
164
|
return needed <= DEFAULT_PAGE_BYTES
|
|
164
165
|
? `, or raise maxBytes to at least ${needed}`
|
|
165
|
-
: `, or raise maxBytes to at least ${needed} (above ${DEFAULT_PAGE_BYTES},
|
|
166
|
+
: `, or raise maxBytes to at least ${needed} (above ${DEFAULT_PAGE_BYTES}, ${phrases.onlyDirectRead}${phrases.program ? `: ${phrases.program} cannot receive it` : ""})`;
|
|
166
167
|
}
|
|
167
168
|
/** Refuse a row no page of `budget` cells can hold, rather than overrun the cap. */
|
|
168
169
|
function rowTooWide(range, row, width, budget) {
|
|
@@ -449,7 +450,7 @@ export function sheetsTools({ connection }) {
|
|
|
449
450
|
})) + 1;
|
|
450
451
|
if (bytes + fields > byteBudget) {
|
|
451
452
|
if (valueRanges.length === 0)
|
|
452
|
-
throw pageTooSmall(echoed, bytes + fields, byteBudget);
|
|
453
|
+
throw pageTooSmall(echoed, bytes + fields, byteBudget, routePhrases(ctx));
|
|
453
454
|
next = { i: resume.i + index, s: before };
|
|
454
455
|
break;
|
|
455
456
|
}
|
|
@@ -470,7 +471,7 @@ export function sheetsTools({ connection }) {
|
|
|
470
471
|
const size = jsonBytes(capped) + 1;
|
|
471
472
|
if (bytes + size > byteBudget) {
|
|
472
473
|
if (empty)
|
|
473
|
-
throw rowTooLarge(echoed, dropped + 1, bytes + size, byteBudget);
|
|
474
|
+
throw rowTooLarge(echoed, dropped + 1, bytes + size, byteBudget, routePhrases(ctx));
|
|
474
475
|
break;
|
|
475
476
|
}
|
|
476
477
|
cells += width;
|
|
@@ -702,15 +703,13 @@ export function sheetsTools({ connection }) {
|
|
|
702
703
|
};
|
|
703
704
|
if (!declaration.annotations.readOnlyHint)
|
|
704
705
|
return write(connection, { ...spec, destructive: declaration.annotations.destructiveHint });
|
|
706
|
+
const contract = fieldsContract("result", declaration.output, "Truncation and content continuation metadata remain available.");
|
|
705
707
|
return read(connection, {
|
|
706
708
|
...spec,
|
|
707
|
-
input: Schema.Struct({
|
|
708
|
-
...declaration.input.fields,
|
|
709
|
-
fields: Schema.optionalKey(fieldsInput("Dot paths to keep; truncation and content continuation metadata remain available.")),
|
|
710
|
-
}),
|
|
709
|
+
input: Schema.Struct({ ...declaration.input.fields, fields: Schema.optionalKey(contract.input) }),
|
|
711
710
|
run: (args) => Effect.gen(function* () {
|
|
712
711
|
const { fields, ...rest } = args;
|
|
713
|
-
const problem =
|
|
712
|
+
const problem = contract.problem(fields);
|
|
714
713
|
if (problem)
|
|
715
714
|
return yield* Effect.fail(new Refusal({ error: problem }));
|
|
716
715
|
const result = yield* spec.run(rest);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/generate-providers.mjs; do not edit.
|
|
2
2
|
export const skill = {
|
|
3
|
-
"content": "## Find and read a deck\n\nSlides cannot search or list decks. Find the file through Drive, or take the\npresentation id from `/presentation/d/<id>/` in its URL. A presentation is a\nDrive file; finding one does not grant permission to open it. A user-given id\nworks as is; no Drive lookup is needed to use it.\n\n`get_presentation` reads text in page order, with speaker notes, alt text and\nplaceholder ids. `get_page` opens groups and reads a slide, layout, master or\nnotes page. A slide's `index` is zero-based. Resolve a layout's placeholders\nwith `list_layouts` and `get_page` before filling a new slide.\nNamed-read `fields` narrows data while preserving `page` and `truncated` cut disclosures.\n\nText and notes have independent continuation. Pass an element's `textCursor`\nor a slide's `elementsCursor` to `get_page` with that slide's id; pass a\n`notesCursor` with its `notesPageId`. A deck's `layoutsCursor` continues in\n`list_layouts`. Content cursors bind to the deck's revision and the text they\ncontinue; a changed deck requires a fresh read. Follow continuation even when\na result contains fewer rows than requested.\n\nFor styles, transforms, HTML comment content, anchors' ranges or other full\nstructural JSON, use `slides_api_read`. Operation details use the legacy\n`presentationsId` spelling for presentations.get; pages and batch updates use\n`presentationId`. These are path parameter names, not query arguments.\nSpell colons and unreserved characters literally in hatch paths; encode other\ncharacters within one id segment. Named tools handle their own id encoding.\n\n`slides_api_read` with `{path: \"/presentations/<id>\"}` returns `{status,data}`;\nthe vendor presentation is `data`. A Connecta call's result delivery wraps that\ntool value in its own `data`; follow its result handle for large JSON.\nContent cursors belong to the named reading tools and are separate from that delivery handle.\n\n`get_slide_thumbnail` returns a link that opens as the delegated person for\nabout 30 minutes. Keep it private. Generic thumbnail/preview operations are\nrefused; use the named thumbnail workflow.\n\n## Edit at the version you read\n\nRead before editing and use its `revisionId` as the write precondition.\nRevision ids are preconditions, not replay keys. `create_slide` adds an empty\nslide; read its placeholder ids before inserting text. `replace_all_text`\nreplaces literal text, with `matchCase` and an optional `requiredRevisionId`.\nEach write returns the new `revisionId` for the next one. A `conflict` means\nthe deck changed since your read: re-read, rebuild the requests, and send them\nwith the new `revisionId`. A failure that says the write applied means Google\nanswered 2xx: do not repeat it; re-read. No Slides tool shares, moves or deletes\nthe deck file.\n\n`batch_update_presentation` takes the existing reviewed Request kinds.\nThe generic batch hatch uses `POST /presentations/deck1:batchUpdate` with\n`body: {requests: [{insertText: {objectId: \"shape1\", text: \"Hello\"}}],\nwriteControl: {requiredRevisionId: \"revision-from-read\"}}`. It retains the\nsame request-kind and revision guards. No additional preview capabilities are\nadmitted by indexing the complete Discovery document.\n\n## Comments can save separately\n\nA comment thread's `commentId` identifies the thread; its head post and\nreplies have their own `postId`. Authors' `user` ids are stable; display names\nare labels. Anchors join to page and object ids; speaker-note comments belong\nto the notes page. Reading comments requires comment access. A view-only\naccount may be refused rather than receiving an apparently empty list.\n\nResolve, reopen or reassign a thread with `update_comment_thread`; edit its\ntext with `update_comment_post`. Reassignment requires content. Only the\npost's author can edit or delete that post. Comments notify people as in the\nSlides editor.\n\n`create_comment` sends one `insertComment` by itself. When one edit changes\nthe deck and adds or changes comments, send it as one `batch_update_presentation`\ncall: `insertComment`, `addCommentReply`, `updateCommentPost`, `deleteComment`\nand `deleteCommentReply` are Request kinds and share a batch with deck kinds\nsuch as `updateTextStyle`, under the same `requiredRevisionId`. Slides saves\nthe deck part and the comment part separately, so a successful mixed batch can\nsave the deck without confirming the comments. When `commentUpdateState` is not\n`ALL_SAVED`, or the result says comment saves are unconfirmed, read the deck and\n`list_comments`, report the deck part and the comment part each as saved or\nunconfirmed, and send neither the batch nor its comment part again. Keep every\ncreated id for reconciliation.\n\n`create_comment_reply` sends one reply by itself. The write hatch keeps\ncomment-save state and advice in envelope-level `warnings`, even with\n`fields: [\"replies\"]`; Google's own state remains in vendor `data` when supplied.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `get_page`, `get_presentation`, `list_comments`, `list_layouts`, `slides_api_details`, `slides_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `slides_api_search` finds this API's operations and reads no account data, `slides_api_details` shows one operation's contract, and `slides_api_read` calls a reviewed read. `slides_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 1 operation is refused by design; use `slides_api_details` with the method and path to
|
|
3
|
+
"content": "## Find and read a deck\n\nSlides cannot search or list decks. Find the file through Drive, or take the\npresentation id from `/presentation/d/<id>/` in its URL. A presentation is a\nDrive file; finding one does not grant permission to open it. A user-given id\nworks as is; no Drive lookup is needed to use it.\n\n`get_presentation` reads text in page order, with speaker notes, alt text and\nplaceholder ids. `get_page` opens groups and reads a slide, layout, master or\nnotes page. A slide's `index` is zero-based. Resolve a layout's placeholders\nwith `list_layouts` and `get_page` before filling a new slide.\nNamed-read `fields` narrows data while preserving `page` and `truncated` cut disclosures.\n\nText and notes have independent continuation. Pass an element's `textCursor`\nor a slide's `elementsCursor` to `get_page` with that slide's id; pass a\n`notesCursor` with its `notesPageId`. A deck's `layoutsCursor` continues in\n`list_layouts`. Content cursors bind to the deck's revision and the text they\ncontinue; a changed deck requires a fresh read. Follow continuation even when\na result contains fewer rows than requested.\n\nFor styles, transforms, HTML comment content, anchors' ranges or other full\nstructural JSON, use `slides_api_read`. Operation details use the legacy\n`presentationsId` spelling for presentations.get; pages and batch updates use\n`presentationId`. These are path parameter names, not query arguments.\nSpell colons and unreserved characters literally in hatch paths; encode other\ncharacters within one id segment. Named tools handle their own id encoding.\n\n`slides_api_read` with `{path: \"/presentations/<id>\"}` returns `{status,data}`;\nthe vendor presentation is `data`. A Connecta call's result delivery wraps that\ntool value in its own `data`; follow its result handle for large JSON.\nContent cursors belong to the named reading tools and are separate from that delivery handle.\n\n`get_slide_thumbnail` returns a link that opens as the delegated person for\nabout 30 minutes. Keep it private. Generic thumbnail/preview operations are\nrefused; use the named thumbnail workflow.\n\n## Edit at the version you read\n\nRead before editing and use its `revisionId` as the write precondition.\nRevision ids are preconditions, not replay keys. `create_slide` adds an empty\nslide; read its placeholder ids before inserting text. `replace_all_text`\nreplaces literal text, with `matchCase` and an optional `requiredRevisionId`.\nEach write returns the new `revisionId` for the next one. A `conflict` means\nthe deck changed since your read: re-read, rebuild the requests, and send them\nwith the new `revisionId`. A failure that says the write applied means Google\nanswered 2xx: do not repeat it; re-read. No Slides tool shares, moves or deletes\nthe deck file.\n\n`batch_update_presentation` takes the existing reviewed Request kinds.\nThe generic batch hatch uses `POST /presentations/deck1:batchUpdate` with\n`body: {requests: [{insertText: {objectId: \"shape1\", text: \"Hello\"}}],\nwriteControl: {requiredRevisionId: \"revision-from-read\"}}`. It retains the\nsame request-kind and revision guards. No additional preview capabilities are\nadmitted by indexing the complete Discovery document.\n\n## Comments can save separately\n\nA comment thread's `commentId` identifies the thread; its head post and\nreplies have their own `postId`. Authors' `user` ids are stable; display names\nare labels. Anchors join to page and object ids; speaker-note comments belong\nto the notes page. Reading comments requires comment access. A view-only\naccount may be refused rather than receiving an apparently empty list.\n\nResolve, reopen or reassign a thread with `update_comment_thread`; edit its\ntext with `update_comment_post`. Reassignment requires content. Only the\npost's author can edit or delete that post. Comments notify people as in the\nSlides editor.\n\n`create_comment` sends one `insertComment` by itself. When one edit changes\nthe deck and adds or changes comments, send it as one `batch_update_presentation`\ncall: `insertComment`, `addCommentReply`, `updateCommentPost`, `deleteComment`\nand `deleteCommentReply` are Request kinds and share a batch with deck kinds\nsuch as `updateTextStyle`, under the same `requiredRevisionId`. Slides saves\nthe deck part and the comment part separately, so a successful mixed batch can\nsave the deck without confirming the comments. When `commentUpdateState` is not\n`ALL_SAVED`, or the result says comment saves are unconfirmed, read the deck and\n`list_comments`, report the deck part and the comment part each as saved or\nunconfirmed, and send neither the batch nor its comment part again. Keep every\ncreated id for reconciliation.\n\n`create_comment_reply` sends one reply by itself. The write hatch keeps\ncomment-save state and advice in envelope-level `warnings`, even with\n`fields: [\"replies\"]`; Google's own state remains in vendor `data` when supplied.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `get_page`, `get_presentation`, `list_comments`, `list_layouts`, `slides_api_details`, `slides_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `slides_api_search` finds this API's operations and reads no account data, `slides_api_details` shows one operation's contract, and `slides_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `slides_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 1 operation is refused by design; use `slides_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. `conflict` means: A revision-guarded batch update returned ABORTED or FAILED_PRECONDITION with HTTP 400 or 409; The content or revision bound to a named continuation cursor changed; restart the read.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
|
|
4
4
|
"instructionsHeading": "Connection instructions"
|
|
5
5
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { routePhrases } from "../../agent-routes.js";
|
|
1
2
|
import { asRecord, asArray, compact } from "../_shared/json.js";
|
|
2
3
|
import { decodeBase64url } from "../../bytes.js";
|
|
3
4
|
// Google Slides named workflows on real kit schemas, with bound content continuation.
|
|
@@ -11,7 +12,7 @@ import { SLIDES_INDEX } from "./api.js";
|
|
|
11
12
|
import * as contracts from "./lib/schemas.js";
|
|
12
13
|
import { read, write } from "../../http/tools.js";
|
|
13
14
|
import { Operation } from "../../runtime/call.js";
|
|
14
|
-
import {
|
|
15
|
+
import { fieldsContract, selectFields } from "../../http/fields.js";
|
|
15
16
|
import { Refusal } from "../../http/errors.js";
|
|
16
17
|
import { boundCursor } from "../../http/paging.js";
|
|
17
18
|
/** Slides per page of get_presentation. Slides itself returns the whole deck. */
|
|
@@ -1799,9 +1800,13 @@ export function slidesTools({ connection }) {
|
|
|
1799
1800
|
if (bytes <= BRIDGE_BYTES)
|
|
1800
1801
|
return result;
|
|
1801
1802
|
if (bytes <= MAX_IDS_RESULT_BYTES) {
|
|
1803
|
+
// Only a surface that runs programs is told what a program can carry.
|
|
1804
|
+
const { program } = routePhrases(ctx);
|
|
1805
|
+
if (!program)
|
|
1806
|
+
return result;
|
|
1802
1807
|
return {
|
|
1803
1808
|
...result,
|
|
1804
|
-
note: `${result.note ?? ""} At ${bytes} bytes this result is more than
|
|
1809
|
+
note: `${result.note ?? ""} At ${bytes} bytes this result is more than ${program} can carry (${BRIDGE_BYTES}): call the tool directly and page it with connecta.result.`.trim(),
|
|
1805
1810
|
};
|
|
1806
1811
|
}
|
|
1807
1812
|
// Past any result: the save state and how to recover the ids stay;
|
|
@@ -1818,27 +1823,24 @@ export function slidesTools({ connection }) {
|
|
|
1818
1823
|
return declarations.map((declaration) => {
|
|
1819
1824
|
const isRead = declaration.annotations.readOnlyHint === true;
|
|
1820
1825
|
const isList = declaration.name.startsWith("list_");
|
|
1821
|
-
const
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1826
|
+
const fields = isRead
|
|
1827
|
+
? isList
|
|
1828
|
+
? fieldsContract("item", declaration.name === "list_layouts" ? contracts.LayoutRow : contracts.CommentRow, "Truncation metadata is preserved.")
|
|
1829
|
+
: fieldsContract("result", declaration.output, "Page and truncation metadata are preserved.")
|
|
1830
|
+
: undefined;
|
|
1825
1831
|
const spec = {
|
|
1826
1832
|
name: declaration.name,
|
|
1827
1833
|
description: declaration.description,
|
|
1828
|
-
input:
|
|
1834
|
+
input: fields
|
|
1829
1835
|
? Schema.Struct({
|
|
1830
1836
|
...declaration.input.fields,
|
|
1831
|
-
fields: Schema.optionalKey(
|
|
1832
|
-
? "Dot paths to keep in each item; truncation metadata is preserved."
|
|
1833
|
-
: "Dot paths to keep in the result; page and truncation metadata are preserved.")),
|
|
1837
|
+
fields: Schema.optionalKey(fields.input),
|
|
1834
1838
|
})
|
|
1835
1839
|
: declaration.input,
|
|
1836
1840
|
output: declaration.output,
|
|
1837
1841
|
...(declaration.writeSteps ? { writeSteps: declaration.writeSteps } : {}),
|
|
1838
1842
|
run: (args) => Effect.gen(function* () {
|
|
1839
|
-
const problem =
|
|
1840
|
-
? fieldsProblem(args["fields"], names, isList ? "a Slides list item" : "the result")
|
|
1841
|
-
: undefined;
|
|
1843
|
+
const problem = fields?.problem(args["fields"]);
|
|
1842
1844
|
if (problem)
|
|
1843
1845
|
return yield* Effect.fail(new Refusal({ error: problem }));
|
|
1844
1846
|
const current = yield* Effect.serviceOption(Operation);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/generate-providers.mjs; do not edit.
|
|
2
2
|
export const skill = {
|
|
3
|
-
"content": "## Account and money\n\nBefore investigating a payment or moving money, confirm the account and mode\nwith `get_stripe_account_info`. A Connect account's objects belong to that\naccount, rather than the platform's account. Sandbox objects do not establish anything about live\ncustomers, revenue or payouts.\n\nResolve ids before acting. Stripe ids identify types: `cus_` customer, `sub_`\nsubscription, `ch_` charge, `pi_` PaymentIntent, `in_` invoice and `acct_`\naccount. A plausible-looking id can refer to a different object or to nobody.\n\nAmounts are integers in the currency's minor unit: 1099 is 10.99 USD, while\n10 is 10 JPY. Keep currencies separate when totaling balances or payments.\n\n## Payment investigations\n\nA PaymentIntent's `latest_charge` joins it to the charge holding the payment's\n`outcome`, `failure_code` and `failure_message`. Follow that reference to\nexplain a decline. Search supports a documented field set for each resource;\ncharge search has no `payment_intent` field. Resolve the parent and follow its\nreference when the needed relationship is not searchable.\n\nSearch is eventually consistent; use a direct object read to reconcile a\nrecent write. A POST's idempotency key identifies the exact operation and\nparameters. Stripe v1 may prune keys after 24 hours, so reconcile the object\nbefore attempting the operation outside that window. A new key can create a\nsecond payment or refund.\n\nIn v1 requests, an empty string (`\"\"`) or `null` unsets a field.\n\n## Data reduction and boundaries\n\nKeep only the customer and payment facts the question needs. Reduce lists to\ncounts, totals or matching records before returning them to the conversation.\nUse the account and balance summaries when their projections answer the task;\nother account fields and balance transactions are available through the API.\n\nCredential creation, customer/account sign-in links, secret payload expansions,\nfull Issuing card details, file uploads and the meter-event stream are outside\nthis connection's surface. Use the Dashboard or application server for those\nflows. Single meter events remain available. A guest Checkout payment page is\navailable only when it is unbound to a Customer or Account and cannot manage\nsaved payment methods; use a payment link or hosted invoice for other cases.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `stripe_api_details`, `stripe_api_read`, `stripe_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `stripe_api_search` finds this API's operations and reads no account data, `stripe_api_details` shows one operation's contract, and `stripe_api_read` calls a reviewed read. `stripe_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 15 operations are refused by design; use `stripe_api_details` with the method and path to
|
|
3
|
+
"content": "## Account and money\n\nBefore investigating a payment or moving money, confirm the account and mode\nwith `get_stripe_account_info`. A Connect account's objects belong to that\naccount, rather than the platform's account. Sandbox objects do not establish anything about live\ncustomers, revenue or payouts.\n\nResolve ids before acting. Stripe ids identify types: `cus_` customer, `sub_`\nsubscription, `ch_` charge, `pi_` PaymentIntent, `in_` invoice and `acct_`\naccount. A plausible-looking id can refer to a different object or to nobody.\n\nAmounts are integers in the currency's minor unit: 1099 is 10.99 USD, while\n10 is 10 JPY. Keep currencies separate when totaling balances or payments.\n\n## Payment investigations\n\nA PaymentIntent's `latest_charge` joins it to the charge holding the payment's\n`outcome`, `failure_code` and `failure_message`. Follow that reference to\nexplain a decline. Search supports a documented field set for each resource;\ncharge search has no `payment_intent` field. Resolve the parent and follow its\nreference when the needed relationship is not searchable.\n\nSearch is eventually consistent; use a direct object read to reconcile a\nrecent write. A POST's idempotency key identifies the exact operation and\nparameters. Stripe v1 may prune keys after 24 hours, so reconcile the object\nbefore attempting the operation outside that window. A new key can create a\nsecond payment or refund.\n\nIn v1 requests, an empty string (`\"\"`) or `null` unsets a field.\n\n## Data reduction and boundaries\n\nKeep only the customer and payment facts the question needs. Reduce lists to\ncounts, totals or matching records before returning them to the conversation.\nUse the account and balance summaries when their projections answer the task;\nother account fields and balance transactions are available through the API.\n\nCredential creation, customer/account sign-in links, secret payload expansions,\nfull Issuing card details, file uploads and the meter-event stream are outside\nthis connection's surface. Use the Dashboard or application server for those\nflows. Single meter events remain available. A guest Checkout payment page is\navailable only when it is unbound to a Customer or Account and cannot manage\nsaved payment methods; use a payment link or hosted invoice for other cases.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `stripe_api_details`, `stripe_api_read`, `stripe_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `stripe_api_search` finds this API's operations and reads no account data, `stripe_api_details` shows one operation's contract, and `stripe_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `stripe_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 15 operations are refused by design; use `stripe_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. A write that takes `idempotencyKey` may be repeated with the same key and arguments while the vendor retains it.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. `not_found` means the record does not exist; this API tells absence from a permission gap. `conflict` means: HTTP 409 when the request conflicts with another request, including a concurrent use of the same idempotency key; HTTP 429 with code lock_timeout when another request holds the object's lock.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
|
|
4
4
|
"instructionsHeading": "Account instructions"
|
|
5
5
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Routes, routePhrases } from "../../agent-routes.js";
|
|
1
2
|
import { asRecord, asArray } from "../_shared/json.js";
|
|
2
3
|
// Tithe.ly's giving API (v1) as a kit vendor: the operations,
|
|
3
4
|
// what its statuses mean, its credential, its in-band refusal, the probe, and
|
|
@@ -389,9 +390,10 @@ function keyPairProblem(values) {
|
|
|
389
390
|
/** Operator-held Basic keys, resolved per request; a mismatched pair is refused before anything leaves. */
|
|
390
391
|
export const authenticate = Effect.gen(function* () {
|
|
391
392
|
const values = yield* (yield* Credentials).values.pipe(Effect.mapError((error) => new Refusal({ error })));
|
|
393
|
+
const routes = yield* Routes;
|
|
392
394
|
const pair = keyPairProblem(values);
|
|
393
395
|
if (typeof pair === "string") {
|
|
394
|
-
return yield* failed(new ConnectorCallError("auth_required", `${pair}
|
|
396
|
+
return yield* failed(new ConnectorCallError("auth_required", `${pair} ${routePhrases(routes).authorizeFor("recovery options")}; an operator adds the keys on this connection in the operator UI.`));
|
|
395
397
|
}
|
|
396
398
|
return { Authorization: `Basic ${btoa(`${pair.publicKey}:${pair.privateKey}`)}` };
|
|
397
399
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/generate-providers.mjs; do not edit.
|
|
2
2
|
export const skill = {
|
|
3
|
-
"content": "## Giving data is pastoral\n\nDonor names, emails, addresses, and gift amounts are pastoral data. Reduce\ninside `execute_code`: page, filter, and total there, and return the\naggregate (sums by fund, month, or donor count), not donor rows, unless the\ntask needs named people.\n\n## Ids and joins\n\n- Ids carry their kind: `org_` organization, `user_` donor account, `ch_`\n charge, `rc_` recurring gift, `pm_` payment method. Read them from the list\n tools; never guess one.\n- Tithe.ly lists charges and recurring gifts only for one organization or one\n donor, so pass `organizationId` or `accountId`.\n- A charge carries its donor's name and email, its organization's name, and\n its payment method, so most joins need no second read.\n- `list_accounts` covers every account these keys can see; `scope: \"api\"`\n narrows it to accounts created through this API.\n- The named reads project Tithe.ly's records. A field a projection omits, such\n as an account's phone and postal address, comes back from `tithely_api_read`\n on that record's path.\n\n## Money, dates and funds\n\n- A fund is a Tithe.ly giving type: `giving_type` in Tithe.ly's records,\n `fund` in projections.\n- Projections give amounts as integer `…Cents` and dates as ISO 8601 UTC\n `…At`. Tithe.ly's own records, from `tithely_api_read`, keep string cents\n and Unix seconds.\n- `depositedAt` may be `\"pending\"`. There is no deposits resource: net amount\n and fees live on each charge.\n- A charge a recurring gift produced has `recurring: true`; a one-time gift\n has `recurring: false`.\n\n## Writes\n\n- Every write moves money or changes a donor's records. Bodies are form\n fields in integer cents and Unix seconds.\n- `POST /refunds/{charge_id}` refunds a whole charge.\n `DELETE /recurring/{recurring_id}` stops future charges and refunds nothing.\n- Card and bank tokens, which `POST /charge-once` and `POST /payment-methods`\n need, come only from Tithely.js in a donor's browser. An agent cannot\n create one.\n- Tithe.ly reports most refusals as HTTP 200 with `status: \"fail\"` and a\n reason but no code. They arrive as `connector_call_failed`; read the reason\n before deciding what to do next.\n\n## Not available\n\nOnly Tithe.ly's API v1 is reachable; its undocumented v2 is not.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `list_accounts`, `list_charges`, `list_organizations`, `list_payment_methods`, `list_recurring_charges`, `tithely_api_details`, `tithely_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 25, at most 99). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `tithely_api_search` finds this API's operations and reads no account data, `tithely_api_details` shows one operation's contract, and `tithely_api_read` calls a reviewed read. `tithely_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
|
|
3
|
+
"content": "## Giving data is pastoral\n\nDonor names, emails, addresses, and gift amounts are pastoral data. Reduce\ninside `execute_code`: page, filter, and total there, and return the\naggregate (sums by fund, month, or donor count), not donor rows, unless the\ntask needs named people.\n\n## Ids and joins\n\n- Ids carry their kind: `org_` organization, `user_` donor account, `ch_`\n charge, `rc_` recurring gift, `pm_` payment method. Read them from the list\n tools; never guess one.\n- Tithe.ly lists charges and recurring gifts only for one organization or one\n donor, so pass `organizationId` or `accountId`.\n- A charge carries its donor's name and email, its organization's name, and\n its payment method, so most joins need no second read.\n- `list_accounts` covers every account these keys can see; `scope: \"api\"`\n narrows it to accounts created through this API.\n- The named reads project Tithe.ly's records. A field a projection omits, such\n as an account's phone and postal address, comes back from `tithely_api_read`\n on that record's path.\n\n## Money, dates and funds\n\n- A fund is a Tithe.ly giving type: `giving_type` in Tithe.ly's records,\n `fund` in projections.\n- Projections give amounts as integer `…Cents` and dates as ISO 8601 UTC\n `…At`. Tithe.ly's own records, from `tithely_api_read`, keep string cents\n and Unix seconds.\n- `depositedAt` may be `\"pending\"`. There is no deposits resource: net amount\n and fees live on each charge.\n- A charge a recurring gift produced has `recurring: true`; a one-time gift\n has `recurring: false`.\n\n## Writes\n\n- Every write moves money or changes a donor's records. Bodies are form\n fields in integer cents and Unix seconds.\n- `POST /refunds/{charge_id}` refunds a whole charge.\n `DELETE /recurring/{recurring_id}` stops future charges and refunds nothing.\n- Card and bank tokens, which `POST /charge-once` and `POST /payment-methods`\n need, come only from Tithely.js in a donor's browser. An agent cannot\n create one.\n- Tithe.ly reports most refusals as HTTP 200 with `status: \"fail\"` and a\n reason but no code. They arrive as `connector_call_failed`; read the reason\n before deciding what to do next.\n\n## Not available\n\nOnly Tithe.ly's API v1 is reachable; its undocumented v2 is not.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `list_accounts`, `list_charges`, `list_organizations`, `list_payment_methods`, `list_recurring_charges`, `tithely_api_details`, `tithely_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 25, at most 99). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `tithely_api_search` finds this API's operations and reads no account data, `tithely_api_details` shows one operation's contract, and `tithely_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `tithely_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
|
|
4
4
|
"instructionsHeading": "Account instructions"
|
|
5
5
|
};
|
|
@@ -456,7 +456,7 @@ export function transformGuide(guide, options) {
|
|
|
456
456
|
const base = Object.values(VERCEL_VERDICTS).filter((verdict) => verdict === "refuse").length;
|
|
457
457
|
const total = vercelIndex.ops.filter((op) => VERCEL_VERDICTS[`${op.method} ${op.path}`] === "refuse" ||
|
|
458
458
|
PIN_DISCOVERY_REFUSALS[`${op.method} ${op.path}`] !== undefined).length;
|
|
459
|
-
const sentence = `${base} operations are refused by design; use \`vercel_api_details\` with the method and path to
|
|
459
|
+
const sentence = `${base} operations are refused by design; use \`vercel_api_details\` with the method and path to see each summary and refusal,`;
|
|
460
460
|
const lines = guide.content.split("\n");
|
|
461
461
|
const targets = lines.flatMap((line, position) => line.startsWith("- **Hatch:** `vercel_api_search` ") && line.includes(sentence) ? [position] : []);
|
|
462
462
|
const position = targets[0];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/generate-providers.mjs; do not edit.
|
|
2
2
|
export const skill = {
|
|
3
|
-
"content": "## Projects, deployments and domains\n\nResolve a team by slug, a project by name, and deployments by the observed\nproject id. Team ids start with `team_`, project ids with `prj_`, and deployment\nids with `dpl_`. A deployment hostname and a deployment id identify the same\ndeployment; an alias can later point to a different deployment.\n\nSome project paths accept a project name, but a deployment list's projectId\nfilter requires the observed `prj_` id. Resolve the name before listing its\ndeployments; an empty result for a guessed name does not prove absence.\nComplete each identifier lookup before starting the reads that depend on it.\n\n<!-- when: unpinned -->\nThe configured team is a routing default, not a restriction. Explicit teamId\nor slug selects another team the token can reach; teamId: null selects personal\nscope. Vercel token permissions determine access. Prefer an explicit team when\njoining resources across several calls.\n<!-- end -->\n\n<!-- when: pinned -->\nThe deployment confines this connection to one team. A supplied team slug must\nbelong to that team; personal scope and foreign teams are refused. Project and\ndeployment references require ownership reads. Global/account and transfer\noperations without a verifiable team boundary remain unavailable. A team list\nreturns only the pinned team.\n<!-- end -->\n\nRead deployment state before its build events. Runtime logs diagnose requests\nafter a deployment built; build logs explain a failed build. Narrow time windows\nbefore raising collection limits. Generic reads refuse live follow streams;\nuse the bounded build/runtime readers. Deployment timestamps are milliseconds.\n\nProject domains and deployment aliases are different joins: a project domain\nbelongs to the project, while an alias identifies its assigned deployment.\nRead a pending domain's verification instructions before changing DNS.\n\n## Environment changes\n\nResolve environment-variable ids using metadata, including target, branch and\ncustom environment. A key can have distinct production and preview values.\nExisting values cannot be retrieved. Supply new values only when the task asks\nfor a change; confirm the returned id and metadata.\n\nEnvironment changes apply to future deployments. A new deployment is a separate\nwrite. Promoting an existing deployment does not rebuild it or update embedded\nenvironment values. After an uncertain update, reconcile observable metadata\nsuch as id, targets and comment; reading metadata cannot verify a secret value.\n\n## Explicit files and credentials\n\nDeployment files can reference previously uploaded digests. File uploads accept\nexplicit bytes and never read a path on the agent's machine. Decrypted-value,\ncredential-minting, protection-bypass-link, signing and domain move-out operations\nare refused; use Vercel's dashboard for those workflows.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `list_teams`, `vercel_api_details`, `vercel_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. For `vercel_api_read`, put it in `query[page.param]` or `body[page.param]`, as `page.in` specifies. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 20, at most 100). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `vercel_api_search` finds this API's operations and reads no account data, `vercel_api_details` shows one operation's contract, and `vercel_api_read` calls a reviewed read. `vercel_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 29 operations are refused by design; use `vercel_api_details` with the method and path to
|
|
3
|
+
"content": "## Projects, deployments and domains\n\nResolve a team by slug, a project by name, and deployments by the observed\nproject id. Team ids start with `team_`, project ids with `prj_`, and deployment\nids with `dpl_`. A deployment hostname and a deployment id identify the same\ndeployment; an alias can later point to a different deployment.\n\nSome project paths accept a project name, but a deployment list's projectId\nfilter requires the observed `prj_` id. Resolve the name before listing its\ndeployments; an empty result for a guessed name does not prove absence.\nComplete each identifier lookup before starting the reads that depend on it.\n\n<!-- when: unpinned -->\nThe configured team is a routing default, not a restriction. Explicit teamId\nor slug selects another team the token can reach; teamId: null selects personal\nscope. Vercel token permissions determine access. Prefer an explicit team when\njoining resources across several calls.\n<!-- end -->\n\n<!-- when: pinned -->\nThe deployment confines this connection to one team. A supplied team slug must\nbelong to that team; personal scope and foreign teams are refused. Project and\ndeployment references require ownership reads. Global/account and transfer\noperations without a verifiable team boundary remain unavailable. A team list\nreturns only the pinned team.\n<!-- end -->\n\nRead deployment state before its build events. Runtime logs diagnose requests\nafter a deployment built; build logs explain a failed build. Narrow time windows\nbefore raising collection limits. Generic reads refuse live follow streams;\nuse the bounded build/runtime readers. Deployment timestamps are milliseconds.\n\nProject domains and deployment aliases are different joins: a project domain\nbelongs to the project, while an alias identifies its assigned deployment.\nRead a pending domain's verification instructions before changing DNS.\n\n## Environment changes\n\nResolve environment-variable ids using metadata, including target, branch and\ncustom environment. A key can have distinct production and preview values.\nExisting values cannot be retrieved. Supply new values only when the task asks\nfor a change; confirm the returned id and metadata.\n\nEnvironment changes apply to future deployments. A new deployment is a separate\nwrite. Promoting an existing deployment does not rebuild it or update embedded\nenvironment values. After an uncertain update, reconcile observable metadata\nsuch as id, targets and comment; reading metadata cannot verify a secret value.\n\n## Explicit files and credentials\n\nDeployment files can reference previously uploaded digests. File uploads accept\nexplicit bytes and never read a path on the agent's machine. Decrypted-value,\ncredential-minting, protection-bypass-link, signing and domain move-out operations\nare refused; use Vercel's dashboard for those workflows.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `list_teams`, `vercel_api_details`, `vercel_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. For `vercel_api_read`, put it in `query[page.param]` or `body[page.param]`, as `page.in` specifies. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 20, at most 100). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `vercel_api_search` finds this API's operations and reads no account data, `vercel_api_details` shows one operation's contract, and `vercel_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `vercel_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 29 operations are refused by design; use `vercel_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. `not_found` means the record does not exist; this API tells absence from a permission gap. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
|
|
4
4
|
"instructionsHeading": "Account instructions"
|
|
5
5
|
};
|
|
@@ -4,7 +4,7 @@ import { Effect, Schema } from "effect";
|
|
|
4
4
|
import { ConnectorCallError } from "../../errors.js";
|
|
5
5
|
import { Refusal } from "../../http/errors.js";
|
|
6
6
|
import { read, write, list } from "../../http/tools.js";
|
|
7
|
-
import {
|
|
7
|
+
import { fieldsContract, selectFields } from "../../http/fields.js";
|
|
8
8
|
import { vercelPage } from "../../http/hatch-paging.js";
|
|
9
9
|
import { schema, typedParams } from "../_shared/rest/kit.js";
|
|
10
10
|
import { refuseWithheldUpload } from "../_shared/rest/value-safety.js";
|
|
@@ -66,6 +66,7 @@ const ENV_SCHEMA = {
|
|
|
66
66
|
},
|
|
67
67
|
required: ["id", "key", "type"],
|
|
68
68
|
};
|
|
69
|
+
const ENV_FIELDS = fieldsContract("item", ENV_SCHEMA, "Values cannot be restored.");
|
|
69
70
|
/** Deliberately omits `value`, even though redaction already removed it. */
|
|
70
71
|
function envMetadata(value) {
|
|
71
72
|
const variable = record(value);
|
|
@@ -158,7 +159,7 @@ export function vercelTools({ connection, options, }) {
|
|
|
158
159
|
type: "array",
|
|
159
160
|
maxItems: 50,
|
|
160
161
|
items: { type: "string", minLength: 1, maxLength: 200 },
|
|
161
|
-
description:
|
|
162
|
+
description: ENV_FIELDS.description,
|
|
162
163
|
},
|
|
163
164
|
}, ["projectId"]),
|
|
164
165
|
outputSchema: {
|
|
@@ -171,7 +172,7 @@ export function vercelTools({ connection, options, }) {
|
|
|
171
172
|
},
|
|
172
173
|
run: (args) => Effect.gen(function* () {
|
|
173
174
|
const fields = args["fields"];
|
|
174
|
-
const problem =
|
|
175
|
+
const problem = ENV_FIELDS.problem(fields);
|
|
175
176
|
if (problem)
|
|
176
177
|
throw problem;
|
|
177
178
|
const { data } = yield* send("GET", `/v10/projects/${segment(args["projectId"])}/env`, {
|
package/dist/registry.d.ts
CHANGED
|
@@ -71,8 +71,10 @@ export interface RegistryOptions {
|
|
|
71
71
|
maxStashBytes?: number;
|
|
72
72
|
maxStashEntries?: number;
|
|
73
73
|
} | undefined;
|
|
74
|
+
/** Route names every context carries unless a call names its own; meta by default. */
|
|
75
|
+
routes?: import("./types.js").AgentRouteNames | undefined;
|
|
74
76
|
}
|
|
75
|
-
export type ConnectorOperationOptions = Pick<ConnectorContext, "signal" | "timeoutMs" | "defer">;
|
|
77
|
+
export type ConnectorOperationOptions = Pick<ConnectorContext, "signal" | "timeoutMs" | "defer" | "routes">;
|
|
76
78
|
/**
|
|
77
79
|
* The registry surface a per-connection MCP server consumes: every meta-tool
|
|
78
80
|
* (`src/meta-tools.ts`) and the `execute_code` sandbox bridge (`src/execute.ts`)
|
|
@@ -84,6 +86,7 @@ export type ConnectorOperationOptions = Pick<ConnectorContext, "signal" | "timeo
|
|
|
84
86
|
*/
|
|
85
87
|
/** Host-admitted identity and endpoint bindings for result paging. */
|
|
86
88
|
export interface ResultIdentity {
|
|
89
|
+
pool: string | null;
|
|
87
90
|
subject: string | null;
|
|
88
91
|
principal: string | null;
|
|
89
92
|
endpoint: string;
|
|
@@ -124,8 +127,11 @@ export interface RegistryView {
|
|
|
124
127
|
recheckResultAccess(dependencies: readonly ProgramResultDependency[], classification: "read" | "write", signal?: AbortSignal): Promise<boolean>;
|
|
125
128
|
/** Reserve deployment-wide capacity before writing a paging envelope's chunks. */
|
|
126
129
|
stashResult(id: string, chunks: readonly string[], ttlSeconds: number): Promise<boolean>;
|
|
127
|
-
/**
|
|
128
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Local declared-vs-stored credential mismatch, with no downstream I/O. With
|
|
132
|
+
* `requestScope`, the read joins that request's read memo.
|
|
133
|
+
*/
|
|
134
|
+
credentialDriftFor(id: string, requestScope?: object): Promise<string | undefined>;
|
|
129
135
|
/** Value-free shape learned from successful calls, never a provider declaration. */
|
|
130
136
|
observedOutputSchema(connectorId: string, definition: ToolDef): ToolDef["outputSchema"] | undefined;
|
|
131
137
|
/** Passively learn one successful unwrapped result; failures stay isolated. */
|
|
@@ -287,7 +293,7 @@ export declare class Registry implements RegistryView {
|
|
|
287
293
|
describeStaticTools(id: string): ToolDef[] | undefined;
|
|
288
294
|
private validateClassification;
|
|
289
295
|
getTools(id: string, baseUrl: string, requestScope?: object, callOptions?: ConnectorOperationOptions, scope?: RegistryScope): Promise<ToolDef[]>;
|
|
290
|
-
credentialDriftFor(id: string): Promise<string | undefined>;
|
|
296
|
+
credentialDriftFor(id: string, requestScope?: object): Promise<string | undefined>;
|
|
291
297
|
/** Age of a complete cache entry; reads never refresh it. */
|
|
292
298
|
catalogAgeMs(id: string): number | null;
|
|
293
299
|
/** Pure lookup shared by status probes and operator catalog refreshes. */
|
package/dist/registry.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { wrapViewCredentials } from "./runtime/call.js";
|
|
2
|
+
import { META_ROUTES } from "./agent-routes.js";
|
|
2
3
|
import { sha256Hex } from "./bytes.js";
|
|
3
4
|
import { catalogRevision } from "./catalog-revision.js";
|
|
4
5
|
import { authCall, authInvoke } from "./auth/native.js";
|
|
@@ -20,11 +21,15 @@ import { boundedStatus, failureRecord, failureStatus, logFailure, ownStatus } fr
|
|
|
20
21
|
import { ObservedOutputSchemas } from "./result-shapes.js";
|
|
21
22
|
import { ContinuationBinding, OperationCredentials, ContinuationStorage, operationView, setViewService, duringViewEffect, } from "./runtime/call.js";
|
|
22
23
|
import { hasNativeStorage, registerNativeStorage } from "./runtime/native-storage.js";
|
|
24
|
+
import { observeStorageWrites } from "./runtime/request-reads.js";
|
|
25
|
+
import { attachStorageDigestKey } from "./storage-digest.js";
|
|
26
|
+
import { requestReads } from "./runtime/request-scope.js";
|
|
27
|
+
import { credentialReads } from "./credential-reads.js";
|
|
23
28
|
import { provideRegistryPrograms, registryEdgeProgram, registryProgram } from "./runtime/registry.js";
|
|
24
29
|
import { causeError, runEdge } from "./runtime/run.js";
|
|
25
30
|
import { Storage } from "./runtime/services.js";
|
|
26
31
|
import { SharedRead } from "./runtime/shared-read.js";
|
|
27
|
-
import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageSet, storageList, } from "./runtime/storage.js";
|
|
32
|
+
import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageGetMany, storageSet, storageList, } from "./runtime/storage.js";
|
|
28
33
|
import { redactCatalog, sentSecretsFor, sentSecretsForRequest } from "./sent-secrets.js";
|
|
29
34
|
import { GUIDE_SUMMARY_LENGTH, normalizeGuideSummary } from "./skills.js";
|
|
30
35
|
import { jsonCodec, OAUTH_HANDOFF_TTL_SECONDS, oauthHandoffKeys, resultKeys, scopes, stashExpiryKeys, stashLedgerKeys, } from "./storage/keys.js";
|
|
@@ -105,12 +110,14 @@ function namespaced(storage, prefix) {
|
|
|
105
110
|
list: async (keyPrefix) => (await storage.list(prefix + keyPrefix)).map((key) => key.slice(prefix.length)),
|
|
106
111
|
compareAndSet: (k, expected, next, o) => storage.compareAndSet(prefix + k, expected, next, o),
|
|
107
112
|
};
|
|
113
|
+
observeStorageWrites(edge, { storage, prefix });
|
|
108
114
|
return hasNativeStorage(storage)
|
|
109
115
|
? registerNativeStorage(edge, {
|
|
110
116
|
// Select against the underlying current methods at dispatch. A
|
|
111
117
|
// namespace created during a decoration regains the native path when
|
|
112
118
|
// that decoration is restored, without capturing stale capabilities.
|
|
113
119
|
get: (key) => storageGet(prefix + key).pipe(Effect.provideService(Storage, storage)),
|
|
120
|
+
getMany: (keys) => storageGetMany(keys.map((key) => prefix + key)).pipe(Effect.provideService(Storage, storage)),
|
|
114
121
|
set: (key, value, options) => storageSet(prefix + key, value, options).pipe(Effect.provideService(Storage, storage)),
|
|
115
122
|
delete: (key) => storageDelete(prefix + key).pipe(Effect.provideService(Storage, storage)),
|
|
116
123
|
list: (keyPrefix) => storageList(prefix + keyPrefix).pipe(Effect.provideService(Storage, storage), Effect.map((keys) => keys.map((key) => key.slice(prefix.length)))),
|
|
@@ -459,8 +466,12 @@ export class Registry {
|
|
|
459
466
|
let credentialProgram;
|
|
460
467
|
if (this.opts.credentialVault && credentialConfig) {
|
|
461
468
|
const vault = this.opts.credentialVault;
|
|
469
|
+
// The credential a call sends: always read fresh. It records what it
|
|
470
|
+
// saw in the request's memo, so a later status or detail read in the
|
|
471
|
+
// same request is never older than what was sent.
|
|
472
|
+
const fresh = credentialReads(vault, requestReads(requestScope), true);
|
|
462
473
|
credentialProgram = Effect.tryPromise({
|
|
463
|
-
try: () =>
|
|
474
|
+
try: () => fresh.getAll(id, this.opts.credentialOwner),
|
|
464
475
|
catch: (error) => error,
|
|
465
476
|
}).pipe(Effect.flatMap((values) => {
|
|
466
477
|
const shape = storedCredentialShape(credentialConfig, values);
|
|
@@ -485,6 +496,7 @@ export class Registry {
|
|
|
485
496
|
...(this.opts.oauthClientName !== undefined ? { oauthClientName: this.opts.oauthClientName } : {}),
|
|
486
497
|
...(credentialAccess ? { credential: credentialAccess } : {}),
|
|
487
498
|
requestScope,
|
|
499
|
+
routes: this.opts.routes ?? META_ROUTES,
|
|
488
500
|
...callOptions,
|
|
489
501
|
}, scope?.caller);
|
|
490
502
|
setViewService(context, OperationCredentials, credentialProgram);
|
|
@@ -500,7 +512,11 @@ export class Registry {
|
|
|
500
512
|
storage: this.opts.catalogStorage ?? this.opts.storage,
|
|
501
513
|
partition: this.catalogPartition(scope),
|
|
502
514
|
credentialRevision: () => catalogRevision(this.opts.credentialVault),
|
|
503
|
-
sharedPartition: JSON.stringify([
|
|
515
|
+
sharedPartition: JSON.stringify([
|
|
516
|
+
baseUrl,
|
|
517
|
+
this.opts.publicUrl ?? null,
|
|
518
|
+
(scope?.caller ?? scope?.operatorCaller)?.pool ?? null,
|
|
519
|
+
]),
|
|
504
520
|
defaultTtlMs: (this.opts.toolCacheTtlSeconds ?? CONFIG_DEFAULTS.discovery.catalogTtlSeconds) * 1000,
|
|
505
521
|
minTtlMs: (this.opts.catalogMinTtlSeconds ?? CONFIG_DEFAULTS.discovery.catalogMinTtlSeconds) * 1000,
|
|
506
522
|
maxTtlMs: (this.opts.catalogMaxTtlSeconds ?? CONFIG_DEFAULTS.discovery.catalogMaxTtlSeconds) * 1000,
|
|
@@ -515,6 +531,7 @@ export class Registry {
|
|
|
515
531
|
}
|
|
516
532
|
: {}),
|
|
517
533
|
});
|
|
534
|
+
attachStorageDigestKey(context, this.opts.credentialVault);
|
|
518
535
|
sentSecretsFor(context);
|
|
519
536
|
wrapViewCredentials(context);
|
|
520
537
|
attachOAuthPartition(context, this.oauthPartition);
|
|
@@ -601,7 +618,7 @@ export class Registry {
|
|
|
601
618
|
* connector's namespace. Backs connecta.result.
|
|
602
619
|
*/
|
|
603
620
|
resultIdentity() {
|
|
604
|
-
return { subject: null, principal: null, endpoint: "/mcp", origin: null };
|
|
621
|
+
return { pool: null, subject: null, principal: null, endpoint: "/mcp", origin: null };
|
|
605
622
|
}
|
|
606
623
|
recheckResultAccess(_dependencies, _classification, _signal) {
|
|
607
624
|
return Promise.resolve(true);
|
|
@@ -763,8 +780,8 @@ export class Registry {
|
|
|
763
780
|
getTools(id, baseUrl, requestScope, callOptions = {}, scope) {
|
|
764
781
|
return runEdge(registryEdgeProgram(Registry.prototype, this, "getTools", id, baseUrl, requestScope, callOptions, scope));
|
|
765
782
|
}
|
|
766
|
-
credentialDriftFor(id) {
|
|
767
|
-
return runEdge(registryEdgeProgram(Registry.prototype, this, "credentialDriftFor", id));
|
|
783
|
+
credentialDriftFor(id, requestScope) {
|
|
784
|
+
return runEdge(registryEdgeProgram(Registry.prototype, this, "credentialDriftFor", id, requestScope));
|
|
768
785
|
}
|
|
769
786
|
/** Age of a complete cache entry; reads never refresh it. */
|
|
770
787
|
catalogAgeMs(id) {
|
|
@@ -944,13 +961,16 @@ export class Registry {
|
|
|
944
961
|
return yield* load.join(signal);
|
|
945
962
|
}));
|
|
946
963
|
}),
|
|
947
|
-
credentialDriftFor: (self, id) => Effect.gen(function* () {
|
|
964
|
+
credentialDriftFor: (self, id, requestScope) => Effect.gen(function* () {
|
|
948
965
|
const credential = self.connectors.get(id)?.credential;
|
|
949
966
|
const vault = self.opts.credentialVault;
|
|
950
967
|
if (!credential || !vault)
|
|
951
968
|
return undefined;
|
|
969
|
+
// Memoized per request: drift only labels a status or detail view.
|
|
970
|
+
// A dispatch reads its credential fresh (contextFor).
|
|
971
|
+
const reader = requestScope ? credentialReads(vault, requestReads(requestScope)) : vault;
|
|
952
972
|
return yield* Effect.tryPromise({
|
|
953
|
-
try: () =>
|
|
973
|
+
try: () => reader.getAll(id, self.opts.credentialOwner),
|
|
954
974
|
catch: (error) => error,
|
|
955
975
|
}).pipe(Effect.map((values) => {
|
|
956
976
|
const shape = storedCredentialShape(credential, values);
|
|
@@ -968,8 +988,13 @@ export class Registry {
|
|
|
968
988
|
const withObservations = (status) => self.withCatalogObservations(id, baseUrl, status, scope);
|
|
969
989
|
return duringViewEffect(ctx, () => Effect.gen(function* () {
|
|
970
990
|
if (connector.credential) {
|
|
991
|
+
// Memoized per request: this read only picks the status shown.
|
|
992
|
+
// The probe that follows reads the credential it sends fresh.
|
|
993
|
+
const vault = self.opts.credentialVault;
|
|
971
994
|
const values = yield* Effect.tryPromise({
|
|
972
|
-
try: () =>
|
|
995
|
+
try: () => vault
|
|
996
|
+
? credentialReads(vault, requestReads(requestScope)).getAll(id, self.opts.credentialOwner)
|
|
997
|
+
: Promise.resolve(null),
|
|
973
998
|
catch: (error) => error,
|
|
974
999
|
});
|
|
975
1000
|
const shape = storedCredentialShape(connector.credential, values);
|
|
@@ -1080,6 +1105,7 @@ class ScopedRegistryView {
|
|
|
1080
1105
|
return {
|
|
1081
1106
|
subject: this.scope.subjectKey ?? null,
|
|
1082
1107
|
principal: this.scope.principalKey ?? null,
|
|
1108
|
+
pool: this.scope.caller?.pool ?? null,
|
|
1083
1109
|
endpoint: this.scope.endpoint ?? "/mcp",
|
|
1084
1110
|
origin: this.scope.origin ?? null,
|
|
1085
1111
|
};
|
|
@@ -1090,8 +1116,8 @@ class ScopedRegistryView {
|
|
|
1090
1116
|
resultsStorage() {
|
|
1091
1117
|
return this.scope.subjectKey ? this.root.scopedStorage(this.scope.subjectKey) : this.root.resultsStorage();
|
|
1092
1118
|
}
|
|
1093
|
-
credentialDriftFor(id) {
|
|
1094
|
-
return runEdge(registryEdgeProgram(ScopedRegistryView.prototype, this, "credentialDriftFor", id));
|
|
1119
|
+
credentialDriftFor(id, requestScope) {
|
|
1120
|
+
return runEdge(registryEdgeProgram(ScopedRegistryView.prototype, this, "credentialDriftFor", id, requestScope));
|
|
1095
1121
|
}
|
|
1096
1122
|
observedOutputSchema(connectorId, definition) {
|
|
1097
1123
|
return this.registryFor(connectorId)?.observedOutputSchema(connectorId, definition);
|
|
@@ -1147,9 +1173,11 @@ class ScopedRegistryView {
|
|
|
1147
1173
|
}
|
|
1148
1174
|
return carryCatalogFreshness(tools, visible);
|
|
1149
1175
|
}),
|
|
1150
|
-
credentialDriftFor: (self, id) => Effect.suspend(() => {
|
|
1176
|
+
credentialDriftFor: (self, id, requestScope) => Effect.suspend(() => {
|
|
1151
1177
|
const registry = self.registryFor(id);
|
|
1152
|
-
return registry
|
|
1178
|
+
return registry
|
|
1179
|
+
? registryProgram(registry, "credentialDriftFor", id, requestScope)
|
|
1180
|
+
: Effect.succeed(undefined);
|
|
1153
1181
|
}),
|
|
1154
1182
|
statusFor: (self, ...args) => Effect.suspend(() => {
|
|
1155
1183
|
const registry = self.registryFor(args[0]);
|