@zackbart/connecta 0.39.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/bin/connecta.mjs +4 -8
  3. package/bin/version.mjs +1 -1
  4. package/dist/access-tokens.js +6 -4
  5. package/dist/activity.d.ts +30 -4
  6. package/dist/activity.js +28 -2
  7. package/dist/agent-observation.js +24 -2
  8. package/dist/agent-routes.js +4 -41
  9. package/dist/auth/clerk.d.ts +6 -1
  10. package/dist/auth/clerk.js +16 -0
  11. package/dist/auth/cloudflare-access.js +49 -7
  12. package/dist/auth/downstream-oauth/consent.js +79 -29
  13. package/dist/auth/downstream-oauth/grant-store.js +58 -7
  14. package/dist/auth/downstream-oauth/refresh-records.js +6 -5
  15. package/dist/auth/downstream-oauth.js +12 -4
  16. package/dist/auth/native.js +10 -0
  17. package/dist/auth/oauth-request-not-sent.js +4 -0
  18. package/dist/auth/static-oauth.js +51 -21
  19. package/dist/catalog-cache.js +151 -37
  20. package/dist/catalog-freshness.js +13 -0
  21. package/dist/catalog-service.js +39 -12
  22. package/dist/catalog.js +103 -38
  23. package/dist/config.d.ts +10 -4
  24. package/dist/config.js +30 -7
  25. package/dist/connectors/remote-mcp/call.js +1 -1
  26. package/dist/connectors/remote-mcp/connection.js +52 -7
  27. package/dist/connectors/remote-mcp/oauth.js +6 -2
  28. package/dist/connectors/remote-mcp/resources.js +5 -2
  29. package/dist/connectors/remote-mcp/transport.js +37 -8
  30. package/dist/connectors/remote-mcp.js +8 -5
  31. package/dist/credential-contract.d.ts +3 -1
  32. package/dist/describe-config.d.ts +1 -0
  33. package/dist/describe-config.js +1 -0
  34. package/dist/downstream-input.js +11 -2
  35. package/dist/errors.d.ts +8 -1
  36. package/dist/errors.js +2 -0
  37. package/dist/execute.js +20 -7
  38. package/dist/executor-result.js +1 -1
  39. package/dist/executors/quickjs-child.js +1 -1
  40. package/dist/executors/quickjs-runtime.js +1 -1
  41. package/dist/executors/quickjs.js +1 -1
  42. package/dist/guarded-transport.js +2 -0
  43. package/dist/http/client.js +7 -0
  44. package/dist/http/hatch.js +2 -0
  45. package/dist/http/node-transport.js +110 -4
  46. package/dist/http/oauth.js +17 -6
  47. package/dist/index.js +10 -21
  48. package/dist/invocation-failure.js +28 -0
  49. package/dist/invocation.js +77 -35
  50. package/dist/membership.d.ts +1 -1
  51. package/dist/meta-tools.js +58 -36
  52. package/dist/module-contracts.d.ts +3 -1
  53. package/dist/native-tools.js +12 -5
  54. package/dist/node.d.ts +7 -0
  55. package/dist/node.js +82 -3
  56. package/dist/operation-activity.js +103 -0
  57. package/dist/operator-record.d.ts +8 -0
  58. package/dist/operator-record.js +15 -0
  59. package/dist/operator-ui/generated.js +2 -2
  60. package/dist/provider-call-facts.js +6 -2
  61. package/dist/provider.d.ts +1 -1
  62. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  63. package/dist/providers/breeze/index.js +1 -1
  64. package/dist/providers/ccb/index.js +1 -1
  65. package/dist/providers/cloudflare/index.js +1 -1
  66. package/dist/providers/docs/index.js +1 -1
  67. package/dist/providers/drive/index.js +1 -1
  68. package/dist/providers/forms/index.js +1 -1
  69. package/dist/providers/github/index.js +1 -1
  70. package/dist/providers/gmail/index.js +1 -1
  71. package/dist/providers/granola/index.js +1 -1
  72. package/dist/providers/overflow/index.js +1 -1
  73. package/dist/providers/planning-center/index.js +1 -1
  74. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  75. package/dist/providers/quickbooks/api.js +28 -25
  76. package/dist/providers/quickbooks/index.js +1 -1
  77. package/dist/providers/quickbooks/lib/writes.js +55 -0
  78. package/dist/providers/quickbooks/safety.js +17 -5
  79. package/dist/providers/quickbooks/skill.generated.js +1 -1
  80. package/dist/providers/sheets/index.js +1 -1
  81. package/dist/providers/slides/index.js +1 -1
  82. package/dist/providers/stripe/index.js +2 -2
  83. package/dist/providers/tithely/index.js +1 -1
  84. package/dist/providers/vercel/api.js +1 -0
  85. package/dist/registry.js +16 -4
  86. package/dist/resource-read.js +21 -2
  87. package/dist/route-names.js +44 -0
  88. package/dist/routes/activity-context.js +1 -0
  89. package/dist/routes/activity-disclosure.js +26 -4
  90. package/dist/routes/activity.js +2 -1
  91. package/dist/routes/mcp.js +32 -13
  92. package/dist/routes/ui-config.js +1 -1
  93. package/dist/routes/ui-data.js +3 -2
  94. package/dist/runtime/native-storage.js +15 -0
  95. package/dist/runtime/request-reads.js +87 -10
  96. package/dist/runtime/request-scope.js +2 -1
  97. package/dist/skill-origin.js +16 -0
  98. package/dist/skills.js +132 -43
  99. package/dist/storage/d1-driver.js +33 -5
  100. package/dist/storage/keys.js +7 -1
  101. package/dist/storage/sql-native.js +85 -3
  102. package/dist/storage/sql.js +6 -0
  103. package/dist/timing-trace.js +84 -0
  104. package/dist/types.d.ts +5 -0
  105. package/dist/url-safety.js +38 -20
  106. package/dist/utility-recovery.js +33 -0
  107. package/dist/version.d.ts +1 -1
  108. package/dist/version.js +1 -1
  109. package/dist/worker.js +33 -8
  110. package/documentation/architecture.md +83 -2
  111. package/documentation/auth.md +132 -37
  112. package/documentation/code-mode.md +61 -46
  113. package/documentation/deploying.md +25 -1
  114. package/documentation/integrating.md +2 -2
  115. package/documentation/meta-tools.md +134 -15
  116. package/documentation/operating.md +86 -1
  117. package/documentation/operator-ui.md +51 -2
  118. package/documentation/providers.md +16 -14
  119. package/package.json +10 -8
  120. package/templates/node/package.json +1 -1
@@ -133,9 +133,13 @@ export function renewedAfterUnauthorized(request, sent, fresh) {
133
133
  export function refusalRequest(error) {
134
134
  return error !== null && typeof error === "object" ? refusals.get(error) : undefined;
135
135
  }
136
- export function knownProviderFailure(error) {
137
- if (error !== null && typeof error === "object")
136
+ /** A local refusal may additionally name the physical request it prevented. */
137
+ export function knownProviderFailure(error, unsent) {
138
+ if (error !== null && typeof error === "object") {
138
139
  knownFailures.add(error);
140
+ if (unsent && !unsent.dispatched)
141
+ refusals.set(error, unsent);
142
+ }
139
143
  return error;
140
144
  }
141
145
  export function isKnownProviderFailure(error) {
@@ -89,8 +89,8 @@ export declare const PROVIDER_COMMON: {
89
89
  };
90
90
  instructions: Field<unknown, unknown, true>;
91
91
  title: Field<unknown, unknown, true>;
92
- authScope: Field<unknown, unknown, true>;
93
92
  purpose: Field<unknown, unknown, true>;
93
+ authScope: Field<unknown, unknown, true>;
94
94
  maxResultBytes: Field<unknown, unknown, true>;
95
95
  };
96
96
  /** The provider's maintained, connection-independent usage guide. */
@@ -92,8 +92,8 @@ export declare const GOOGLE_WORKSPACE_OPTIONS: import("../../../config-schema.js
92
92
  };
93
93
  instructions: import("../../../config-schema.js").Field<unknown, unknown, true>;
94
94
  title: import("../../../config-schema.js").Field<unknown, unknown, true>;
95
- baseUrl: import("../../../config-schema.js").Field<unknown, unknown, true>;
96
95
  purpose: import("../../../config-schema.js").Field<unknown, unknown, true>;
96
+ baseUrl: import("../../../config-schema.js").Field<unknown, unknown, true>;
97
97
  maxResultBytes: import("../../../config-schema.js").Field<unknown, unknown, true>;
98
98
  readOnly: import("../../../config-schema.js").Field<unknown, unknown, true>;
99
99
  subject: import("../../../config-schema.js").Field<unknown, unknown, true>;
@@ -75,7 +75,7 @@ export const breeze = defineApiProvider({
75
75
  ? [`Paged lists return ${options.defaultPageSize} items by default on this connection.`]
76
76
  : []),
77
77
  ],
78
- summary: "Church records: profile-field decoding, occurrence/person joins, decimal giving, and archival API caveats.",
78
+ summary: "Load for Breeze people, giving records, paging and guarded updates.",
79
79
  required: true,
80
80
  }),
81
81
  hatch: {
@@ -81,7 +81,7 @@ export const ccb = defineApiProvider({
81
81
  ? [`Named lists default to ${options.defaultPerPage} rows on this connection.`]
82
82
  : []),
83
83
  ],
84
- summary: `${options.environment === "sandbox" ? "Sandbox" : "Production"} CCB, ${options.mode === "identity" ? "per person" : "church-wide"}: id resolution, sensitive data, paging, rate limits, hatches.`,
84
+ summary: "Load for CCB person ids, sensitive data, paging, rate limits and guarded writes.",
85
85
  required: true,
86
86
  }),
87
87
  pageSize: { default: 25, max: 100 },
@@ -75,7 +75,7 @@ function usageGuide(options) {
75
75
  const { auth, accountId, zoneId, pin } = settings(options);
76
76
  return {
77
77
  heading: "Cloudflare usage",
78
- summary: "Configured Cloudflare REST credential: resolve domain/account ids, then inspect DNS, Workers, R2, KV or analytics.",
78
+ summary: "Load for Cloudflare DNS, Workers, R2, KV and analytics; resolve account and zone ids before calls.",
79
79
  context: [
80
80
  `Connector purpose: ${options.purpose}`,
81
81
  `This connection already has an operator-configured ${auth === "apiToken" ? "scoped API token" : "Global API Key"}; there is no sign-in step.`,
@@ -101,7 +101,7 @@ export const docs = defineApiProvider({
101
101
  "Workspace delegation is configured by the deployment. Each call acts as the mapped Workspace user; document access follows that user's Drive sharing.",
102
102
  ...(options.readOnly ? ["This connection is read-only."] : []),
103
103
  ],
104
- summary: "Docs by id: tabs, UTF-16 edit indexes, revisions and partial-create recovery.",
104
+ summary: "Load for Docs tabs, UTF-16 edit indexes, revision checks and partial-create recovery.",
105
105
  required: true,
106
106
  }),
107
107
  hatch: {
@@ -105,7 +105,7 @@ export const drive = defineApiProvider({
105
105
  `Connection purpose: ${options.purpose}`,
106
106
  "Acts as the Workspace user deployment config maps from the admitted caller or pool owner. No caller argument selects an account. An unmapped or ownerless caller has no access unless the deployment fixes a shared account.",
107
107
  ],
108
- summary: "Each caller's Drive files, bounded content, recoverable trash and quiet sharing.",
108
+ summary: "Load for Drive file discovery, bounded content, recoverable trash and sharing.",
109
109
  required: true,
110
110
  }),
111
111
  tools: driveTools,
@@ -101,7 +101,7 @@ export const forms = defineApiProvider({
101
101
  ...(options.readOnly ? ["This connection is read-only."] : []),
102
102
  ],
103
103
  instructions: options.instructions,
104
- summary: "Each caller's own forms: questions by id, labeled responses, revision-checked batch edits.",
104
+ summary: "Load for form questions, labeled responses and revision-checked batch edits.",
105
105
  // Required: whose forms they are, that Drive lists them, and the
106
106
  // revision-checked edit sequence are conventions no schema can carry.
107
107
  required: true,
@@ -39,7 +39,7 @@ export const github = defineProvider({
39
39
  "The App identity is already configured. Installation access is resolved on use; ask the operator to repair configuration only after an auth failure.",
40
40
  ...(options.readOnly === true ? ["This connection is read-only; every write is refused."] : []),
41
41
  ],
42
- summary: "GitHub App repository reads, scoped data search, release writes and guarded merges.",
42
+ summary: "Load for GitHub repository searches, releases and guarded merges.",
43
43
  required: true,
44
44
  });
45
45
  const credential = options.app.privateKey === undefined
@@ -133,7 +133,7 @@ export const gmail = defineApiProvider({
133
133
  "A missing subject fails auth_required; ask the operator to repair configuration after an auth failure.",
134
134
  ...(options.readOnly ? ["This connection is read-only; draft writes are refused."] : []),
135
135
  ],
136
- summary: "Each caller's own mailbox: Gmail search syntax, capped bodies, reply threading, drafts that never send.",
136
+ summary: "Load for Gmail search, capped message bodies, threaded replies and drafts that never send.",
137
137
  required: true,
138
138
  }),
139
139
  tools: gmailTools,
@@ -46,7 +46,7 @@ export const granola = defineApiProvider({
46
46
  probe,
47
47
  guide: (options) => ({
48
48
  heading: "Granola usage",
49
- summary: "Read workspace notes and transcripts; discover accessible spaces and folders before filtering.",
49
+ summary: "Load for workspace notes and transcripts; discover accessible spaces and folders before filtering.",
50
50
  context: [
51
51
  `Workspace purpose: ${options.purpose}`,
52
52
  "One shared workspace API key over REST. This connection exposes only reviewed reads.",
@@ -87,7 +87,7 @@ export const overflow = defineApiProvider({
87
87
  ? [`Paged lists return ${options.defaultPageSize} items by default on this connection.`]
88
88
  : []),
89
89
  ],
90
- summary: "Giving records: asset-specific status, per-field money units, donor joins and deposit reconciliation.",
90
+ summary: "Load for giving statuses, money units, donor joins and deposit reconciliation.",
91
91
  required: true,
92
92
  }),
93
93
  hatch: {
@@ -44,7 +44,7 @@ export const planningCenter = defineApiProvider({
44
44
  },
45
45
  guide: (options) => ({
46
46
  heading: "Planning Center usage",
47
- summary: "People ids join reachable products; use their local dates, units, roles and documented status values.",
47
+ summary: "Load when joining Planning Center products by person id or interpreting dates, units, roles and statuses.",
48
48
  required: true,
49
49
  context: [
50
50
  `Purpose: ${options.purpose.trim()}`,
@@ -55,4 +55,4 @@ export declare const PLANNING_CENTER_MAX_RESPONSE_BYTES: number;
55
55
  */
56
56
  export declare const PLANNING_CENTER_ADMISSION: ConnectorCallAdmissionPolicy;
57
57
  /** Planning Center refuses a request without a descriptive one (HTTP 403). */
58
- export declare const DEFAULT_USER_AGENT = "connecta/0.39.0 (+https://github.com/zackbart/connecta)";
58
+ export declare const DEFAULT_USER_AGENT = "connecta/0.41.0 (+https://github.com/zackbart/connecta)";
@@ -58,32 +58,35 @@ export const customerWrite = post("writeCustomer", "/customer", {
58
58
  }).annotate(Summary, "Create a customer or sparsely update naming fields through named tools only");
59
59
  export const invoiceWrite = post("createInvoice", "/invoice", {
60
60
  query: { minorversion: Schema.Literal("75"), requestid: Schema.String },
61
- payload: Schema.Struct({
62
- CustomerRef: Schema.Struct({ value: PublicId }),
63
- CurrencyRef: Schema.Struct({ value: Schema.String }),
64
- ExchangeRate: Schema.Number,
65
- TxnDate: Schema.String,
66
- DueDate: Schema.String,
67
- DocNumber: Schema.String,
68
- Line: Schema.Array(Schema.Struct({
69
- Amount: Schema.Number,
70
- DetailType: Schema.Literal("SalesItemLineDetail"),
71
- SalesItemLineDetail: Schema.Struct({
72
- ItemRef: Schema.Struct({ value: PublicId }),
73
- Qty: Schema.Literal(1),
74
- UnitPrice: Schema.Number,
75
- }),
76
- })),
77
- EmailStatus: Schema.Literal("NotSet"),
78
- AllowIPNPayment: Schema.Literal(false),
79
- AllowOnlinePayment: Schema.Literal(false),
80
- AllowOnlineCreditCardPayment: Schema.Literal(false),
81
- AllowOnlineACHPayment: Schema.Literal(false),
82
- AllowOnlinePayPalPayment: Schema.Literal(false),
83
- AllowOnlineAffirmPayment: Schema.Literal(false),
84
- }),
61
+ payload: Schema.Union([
62
+ Schema.Struct({
63
+ CustomerRef: Schema.Struct({ value: PublicId }),
64
+ CurrencyRef: Schema.Struct({ value: Schema.String }),
65
+ ExchangeRate: Schema.Number,
66
+ TxnDate: Schema.String,
67
+ DueDate: Schema.String,
68
+ DocNumber: Schema.String,
69
+ Line: Schema.Array(Schema.Struct({
70
+ Amount: Schema.Number,
71
+ DetailType: Schema.Literal("SalesItemLineDetail"),
72
+ SalesItemLineDetail: Schema.Struct({
73
+ ItemRef: Schema.Struct({ value: PublicId }),
74
+ Qty: Schema.Literal(1),
75
+ UnitPrice: Schema.Number,
76
+ }),
77
+ })),
78
+ EmailStatus: Schema.Literal("NotSet"),
79
+ AllowIPNPayment: Schema.Literal(false),
80
+ AllowOnlinePayment: Schema.Literal(false),
81
+ AllowOnlineCreditCardPayment: Schema.Literal(false),
82
+ AllowOnlineACHPayment: Schema.Literal(false),
83
+ AllowOnlinePayPalPayment: Schema.Literal(false),
84
+ AllowOnlineAffirmPayment: Schema.Literal(false),
85
+ }),
86
+ Schema.Struct({ Id: PublicId, SyncToken, sparse: Schema.Literal(true), DocNumber: Schema.String }),
87
+ ]),
85
88
  success: Schema.Struct({ Invoice: RecordShape }),
86
- }).annotate(Summary, "Create a real unsent service invoice through create_invoice only");
89
+ }).annotate(Summary, "Create an unsent service invoice or sparsely update an eligible unsent invoice number through named tools only");
87
90
  export const QuickBooksApi = vendor("QuickBooks Online", companyInfo, queryRecords, customerWrite, invoiceWrite, ...Object.values(recordReads), ...Object.values(reportReads));
88
91
  export const QUICKBOOKS_INDEX = vendorIndex(QuickBooksApi, {
89
92
  prefix: "quickbooks",
@@ -62,7 +62,7 @@ export const quickbooks = defineApiProvider({
62
62
  : "This connection is read-only. Set readOnly: false in deployment code to enable bounded named writes.",
63
63
  "Contact details, bank/card/tax identifiers, credentials and private links are withheld on every read.",
64
64
  ],
65
- summary: "QuickBooks Online accounting reads and explicitly enabled customer/service-invoice writes.",
65
+ summary: "Load for QuickBooks accounting reads and enabled customer or service-invoice writes.",
66
66
  required: true,
67
67
  }),
68
68
  hatch: {
@@ -85,6 +85,61 @@ export function quickbooksWrites(connection, realmId) {
85
85
  .send(customerWrite, { query: { ...version, requestid: requestId }, payload: { ...body, sparse: true } })
86
86
  .pipe(Effect.map(({ data }) => namedRecord(object(data)["Customer"]))),
87
87
  }),
88
+ write(connection, {
89
+ name: "update_unsent_invoice",
90
+ destructive: true,
91
+ idempotent: false,
92
+ description: "Sparsely change only DocNumber on an observed unsent US-company USD invoice. Requires caller-observed Id and SyncToken. Preflight refuses unknown, send-ready, delivered or payment-enabled state and stale versions. No delivery/payment settings are changed. Reconcile unknown outcomes by reading the Id; never replay.",
93
+ input: Schema.Struct({
94
+ requestId: RequestId,
95
+ Id: PublicId,
96
+ SyncToken,
97
+ docNumber: Schema.String.check(matches("^[A-Za-z0-9][A-Za-z0-9_-]{0,20}$")),
98
+ }),
99
+ check: (args, problems) => {
100
+ if (args.docNumber === "AUTO_GENERATE")
101
+ problems.add("docNumber", "an explicit document number other than AUTO_GENERATE");
102
+ },
103
+ output: NamedRecordShape,
104
+ writeSteps: { company: "once", invoice: "once", write: "once" },
105
+ run: (args) => Effect.gen(function* () {
106
+ const company = object(object((yield* connection.send(companyInfo, { params: { id: realmId }, query: version }, { step: "company", effect: "read" })).data)["CompanyInfo"]);
107
+ if (company["Id"] !== realmId || company["Country"] !== "US")
108
+ return yield* refuse("Invoice number updates require a verified US company.");
109
+ yield* charge;
110
+ const invoice = object(object((yield* connection.send(recordReads["invoices"], { params: { id: args.Id }, query: version }, { step: "invoice", effect: "read" })).data)["Invoice"]);
111
+ if (invoice["Id"] !== args.Id || invoice["SyncToken"] !== args.SyncToken)
112
+ return yield* refuse("Invoice version changed; read and review the current invoice before a new intent.");
113
+ if (invoice["EmailStatus"] !== "NotSet" ||
114
+ invoice["DeliveryInfo"] !== undefined ||
115
+ object(invoice["CurrencyRef"])["value"] !== "USD" ||
116
+ ![
117
+ "AllowIPNPayment",
118
+ "AllowOnlinePayment",
119
+ "AllowOnlineCreditCardPayment",
120
+ "AllowOnlineACHPayment",
121
+ "AllowOnlinePayPalPayment",
122
+ "AllowOnlineAffirmPayment",
123
+ ].every((key) => invoice[key] === false))
124
+ return yield* refuse("Only observed unsent USD invoices with explicit NotSet, no delivery information and all payment flags false qualify. Unknown state is refused.");
125
+ yield* charge;
126
+ const { data, status } = yield* connection.send(invoiceWrite, {
127
+ query: { ...version, requestid: args.requestId },
128
+ payload: { Id: args.Id, SyncToken: args.SyncToken, sparse: true, DocNumber: args.docNumber },
129
+ });
130
+ const updated = object(object(data)["Invoice"]);
131
+ if (updated["Id"] !== args.Id ||
132
+ updated["DocNumber"] !== args.docNumber ||
133
+ updated["SyncToken"] === args.SyncToken)
134
+ return yield* Effect.fail(new StatusFailure({
135
+ status,
136
+ headers: new Headers(),
137
+ body: undefined,
138
+ operation: { method: "POST", template: "/invoice" },
139
+ }));
140
+ return namedRecord(updated);
141
+ }),
142
+ }),
88
143
  write(connection, {
89
144
  name: "create_invoice",
90
145
  destructive: true,
@@ -3,6 +3,13 @@ import { ConnectorCallError } from "../../errors.js";
3
3
  import { withholdEmbeddedUrls } from "../_shared/rest/value-safety.js";
4
4
  import { QUICKBOOKS_INDEX } from "./api.js";
5
5
  const scalars = [
6
+ "EmailStatus",
7
+ "AllowIPNPayment",
8
+ "AllowOnlinePayment",
9
+ "AllowOnlineCreditCardPayment",
10
+ "AllowOnlineACHPayment",
11
+ "AllowOnlinePayPalPayment",
12
+ "AllowOnlineAffirmPayment",
6
13
  "Id",
7
14
  "SyncToken",
8
15
  "DisplayName",
@@ -72,6 +79,7 @@ export const RecordShape = Schema.Struct({
72
79
  ...optional(scalars),
73
80
  ...optional(references),
74
81
  ...optional(details),
82
+ DeliveryInfo: Schema.optionalKey(Schema.Unknown),
75
83
  MetaData: Schema.optionalKey(Schema.Unknown),
76
84
  Line: Schema.optionalKey(Schema.Unknown),
77
85
  LinkedTxn: Schema.optionalKey(Schema.Unknown),
@@ -128,6 +136,8 @@ function safeRecord(value) {
128
136
  for (const key of references)
129
137
  if (source[key])
130
138
  out[key] = pick(source[key], ["value", "name", "type"]);
139
+ if (source["DeliveryInfo"] !== undefined)
140
+ out["DeliveryInfo"] = pick(source["DeliveryInfo"], ["DeliveryType", "DeliveryTime"]);
131
141
  if (source["MetaData"])
132
142
  out["MetaData"] = pick(source["MetaData"], ["CreateTime", "LastUpdatedTime"]);
133
143
  for (const key of details)
@@ -197,11 +207,13 @@ function recordPath(path) {
197
207
  return recordPath(rest);
198
208
  const picked = head === "MetaData"
199
209
  ? ["CreateTime", "LastUpdatedTime"]
200
- : head === "LinkedTxn"
201
- ? ["TxnId", "TxnType", "TxnLineId"]
202
- : references.includes(head)
203
- ? ["value", "name", "type"]
204
- : undefined;
210
+ : head === "DeliveryInfo"
211
+ ? ["DeliveryType", "DeliveryTime"]
212
+ : head === "LinkedTxn"
213
+ ? ["TxnId", "TxnType", "TxnLineId"]
214
+ : references.includes(head)
215
+ ? ["value", "name", "type"]
216
+ : undefined;
205
217
  if (!picked)
206
218
  return "dropped";
207
219
  if (rest.length === 0)
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "## Accounting identity and joins\n\nAn Id identifies a record inside this company and record family. DocNumber is\na human label, may repeat, and is not an Id. SyncToken is the public record\nversion used for concurrency control, not an OAuth credential. References carry\nthe target Id in value; invoice CustomerRef joins Customer, bill VendorRef\njoins Vendor, and payment Line.LinkedTxn joins invoices or bills.\n\nAccounting Payment and BillPayment records describe received and paid money;\nreading them does not execute a payment. Desktop, Payments, Payroll, attachments,\nand unreviewed accounting entities are outside this connection.\n\n## Money, time and reports\n\nAmounts use decimal currency units, never integer cents. Preserve CurrencyRef\nand ExchangeRate when combining transactions; never sum unlike currencies.\nTxnDate is the company's accounting date, not a UTC instant. MetaData timestamps\ncarry their own offset. Customer and vendor searches omit inactive records\nunless the Active filter explicitly selects them.\n\nReports are trees with Header, Columns, nested Rows and section summaries.\nRead column types and metadata together with row values; column order and\nsection labels can change. Empty amount strings mean no value, not zero.\nDo not flatten away section identity or add totals to their component rows.\nCheck the returned period, basis, currency and NoReportData option before\ncomparing reports. Aging is an as-of snapshot, rather than a transaction range.\n\nReturn aggregates and the few identifying records the task needs; avoid\ncopying full customer or vendor directories into the conversation.\n\n\n## Named writes and reconciliation\n\nThe connector defaults to read-only. Deployment code must set `readOnly: false`\nto expose named writes; endpoint and pool read-only policies still apply. The\nAPI hatch remains read-only. `create_customer` accepts DisplayName and optional\nCompanyName. `update_customer` requires caller-observed Id and SyncToken and\nchanges only those naming fields with sparse:true. Omitted CompanyName stays\nunchanged. A stale-version conflict never fetches a newer version or overwrites\nit. Read and review the current record before proposing a new update.\nDisplayName must be unique across customers, vendors and employees.\n\n`create_invoice` creates a real accounting transaction, never a draft, for a\nverified US company and a customer using USD. This bounds the no-email claim\nto the reviewed US autosend contract and money to two-decimal USD units. First\nobserve an active customer with its CurrencyRef and active Service item Ids.\nSupply currency USD, a positive explicit exchange rate, accounting and\ndue dates, a reconciliation DocNumber and 1–20 lines. Each line is one service\nunit with an explicit positive amount in decimal currency units, supplied as a\nstring with exactly two fractional digits. No prices, dates or exchange rates\nare defaulted or fetched. Preflight reads verify the fixed company Country is US\nand recheck customer currency and item activity/type; they are not an atomic snapshot. Vendor validation can still\nreject changed references. Taxes and totals are computed by QuickBooks: inspect\nthe returned currency, exchange rate and totals; line sums are not a promise of\nan untaxed total. Locale/company tax requirements may reject this narrow body.\nNo customer/item creation happens implicitly.\nEach additional request charges the call-admission budget before dispatch.\nContradictory returned references, dates, rate or service prices produce an\nunknown outcome; reconcile through reads rather than repeating the write.\n\nInvoice email status is fixed to NotSet; online, IPN, credit-card, ACH, PayPal\nand Affirm payment flags are fixed false. No email field or send operation is\nexposed. Company invoice-autosend opt-in does not supply the documented\nNeedToSend request state. This prevents this creation request from asking Intuit\nto email the invoice, including when the customer has an email on file.\nInvoice updates are unavailable: preserving an existing send-ready state has\nnot been established safe, while resetting it would change payment settings.\nDeletes, voids, email delivery, charges, refunds, other accounting writes and\narbitrary mutations are unavailable.\n\nEvery write requires a caller-generated `requestId`: 1–50 ASCII letters,\ndigits, underscores or hyphens, starting with a letter or digit. Keep it stable\nfor the exact intent and unique across requests/tools for this company. It is\nsent in Intuit's requestid query parameter with minorversion 75, rather than an\nidempotency header. Intuit documents recognizing the same content and ID and\nreturning the original response; no retention duration is established. This\nnever permits automatic replay. A dispatched write is sent once even after\n401, timeout, server failure or an invalid success response.\n\nAfter an unknown customer-create outcome, search exact DisplayName, including\ninactive records when needed, and inspect the match. For an invoice, search\nDocNumber plus CustomerRef and inspect currency, dates, lines and computed\namounts. DocNumber can repeat, so it alone proves nothing. Reconcile and report\nambiguity before considering another create; never generate a fresh requestId\nand blindly repeat a potentially committed intent. Unknown update outcomes\nneed a read of the known Id and version. A requestId is not a lookup endpoint or\na permanent duplicate-prevention guarantee.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `quickbooks_api_details`, `quickbooks_api_search`, `search_records`, 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 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:** `quickbooks_api_search` finds this API's operations and reads no account data, `quickbooks_api_details` shows one operation's contract, and `quickbooks_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. An operation without a reviewed verdict is refused. 3 operations are refused by design; use `quickbooks_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: Intuit fault 5010 for a stale SyncToken; Intuit fault 6240 for a duplicate DisplayName.\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": "## Accounting identity and joins\n\nAn Id identifies a record inside this company and record family. DocNumber is\na human label, may repeat, and is not an Id. SyncToken is the public record\nversion used for concurrency control, not an OAuth credential. References carry\nthe target Id in value; invoice CustomerRef joins Customer, bill VendorRef\njoins Vendor, and payment Line.LinkedTxn joins invoices or bills.\n\nAccounting Payment and BillPayment records describe received and paid money;\nreading them does not execute a payment. Desktop, Payments, Payroll, attachments,\nand unreviewed accounting entities are outside this connection.\n\n## Money, time and reports\n\nAmounts use decimal currency units, never integer cents. Preserve CurrencyRef\nand ExchangeRate when combining transactions; never sum unlike currencies.\nTxnDate is the company's accounting date, not a UTC instant. MetaData timestamps\ncarry their own offset. Customer and vendor searches omit inactive records\nunless the Active filter explicitly selects them.\n\nReports are trees with Header, Columns, nested Rows and section summaries.\nRead column types and metadata together with row values; column order and\nsection labels can change. Empty amount strings mean no value, not zero.\nDo not flatten away section identity or add totals to their component rows.\nCheck the returned period, basis, currency and NoReportData option before\ncomparing reports. Aging is an as-of snapshot, rather than a transaction range.\n\nReturn aggregates and the few identifying records the task needs; avoid\ncopying full customer or vendor directories into the conversation.\n\n\n## Named writes and reconciliation\n\nThe connector defaults to read-only. Deployment code must set `readOnly: false`\nto expose named writes; endpoint and pool read-only policies still apply. The\nAPI hatch remains read-only. `create_customer` accepts DisplayName and optional\nCompanyName. `update_customer` requires caller-observed Id and SyncToken and\nchanges only those naming fields with sparse:true. Omitted CompanyName stays\nunchanged. A stale-version conflict never fetches a newer version or overwrites\nit. Read and review the current record before proposing a new update.\nDisplayName must be unique across customers, vendors and employees.\n\n`create_invoice` creates a real accounting transaction, never a draft, for a\nverified US company and a customer using USD. This bounds the no-email claim\nto the reviewed US autosend contract and money to two-decimal USD units. First\nobserve an active customer with its CurrencyRef and active Service item Ids.\nSupply currency USD, a positive explicit exchange rate, accounting and\ndue dates, a reconciliation DocNumber and 1–20 lines. Each line is one service\nunit with an explicit positive amount in decimal currency units, supplied as a\nstring with exactly two fractional digits. No prices, dates or exchange rates\nare defaulted or fetched. Preflight reads verify the fixed company Country is US\nand recheck customer currency and item activity/type; they are not an atomic snapshot. Vendor validation can still\nreject changed references. Taxes and totals are computed by QuickBooks: inspect\nthe returned currency, exchange rate and totals; line sums are not a promise of\nan untaxed total. Locale/company tax requirements may reject this narrow body.\nNo customer/item creation happens implicitly.\nEach additional request charges the call-admission budget before dispatch.\nContradictory returned references, dates, rate or service prices produce an\nunknown outcome; reconcile through reads rather than repeating the write.\n\nInvoice email status is fixed to NotSet; online, IPN, credit-card, ACH, PayPal\nand Affirm payment flags are fixed false. No email field or send operation is\nexposed. Company invoice-autosend opt-in does not supply the documented\nNeedToSend request state. This prevents this creation request from asking Intuit\nto email the invoice, including when the customer has an email on file.\n`update_unsent_invoice` changes only DocNumber, with caller-observed Id and\nSyncToken. It reads the US company and invoice before dispatch and requires USD,\nexplicit NotSet, no DeliveryInfo and every payment flag explicitly false.\nMissing state, send-ready and delivered invoices are refused. It preserves all\nomitted settings through sparse:true and never substitutes a newer version.\nNo date edits are exposed. This removes documented autosend prerequisites;\napplying them to updates is an inference, not an Intuit update-specific no-email\nguarantee. NotSet cannot prove that no historical delivery ever occurred.\nRead the known Id after an uncertain response; never blindly resend.\nDeletes, voids, email delivery, charges, refunds, other accounting writes and\narbitrary mutations are unavailable.\n\nEvery write requires a caller-generated `requestId`: 1–50 ASCII letters,\ndigits, underscores or hyphens, starting with a letter or digit. Keep it stable\nfor the exact intent and unique across requests/tools for this company. It is\nsent in Intuit's requestid query parameter with minorversion 75, rather than an\nidempotency header. Intuit documents recognizing the same content and ID and\nreturning the original response; no retention duration is established. This\nnever permits automatic replay. A dispatched write is sent once even after\n401, timeout, server failure or an invalid success response.\n\nAfter an unknown customer-create outcome, search exact DisplayName, including\ninactive records when needed, and inspect the match. For an invoice, search\nDocNumber plus CustomerRef and inspect currency, dates, lines and computed\namounts. DocNumber can repeat, so it alone proves nothing. Reconcile and report\nambiguity before considering another create; never generate a fresh requestId\nand blindly repeat a potentially committed intent. Unknown update outcomes\nneed a read of the known Id and version. A requestId is not a lookup endpoint or\na permanent duplicate-prevention guarantee.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `quickbooks_api_details`, `quickbooks_api_search`, `search_records`, 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 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:** `quickbooks_api_search` finds this API's operations and reads no account data, `quickbooks_api_details` shows one operation's contract, and `quickbooks_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. An operation without a reviewed verdict is refused. 3 operations are refused by design; use `quickbooks_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: Intuit fault 5010 for a stale SyncToken; Intuit fault 6240 for a duplicate DisplayName.\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": "Company instructions"
5
5
  };
@@ -110,7 +110,7 @@ export const sheets = defineApiProvider({
110
110
  ...(options.readOnly ? ["This connection is read-only."] : []),
111
111
  ],
112
112
  instructions: options.instructions,
113
- summary: "Each caller's own spreadsheets by id: A1 ranges, cell-paged reads, RAW or USER_ENTERED writes, formatting and structure.",
113
+ summary: "Load for spreadsheet A1 ranges, paged cells, RAW/USER_ENTERED writes and formatting.",
114
114
  // Whose spreadsheets they are, how Drive finds them, and the cell-write
115
115
  // meanings of null and "" are conventions no schema carries.
116
116
  required: true,
@@ -130,7 +130,7 @@ export const slides = defineApiProvider({
130
130
  "Generic thumbnails/preview links are refused; get_slide_thumbnail is the reviewed short-lived-link workflow. The generic batch retains only the 49 shipped request kinds; no extra preview methods are admitted.",
131
131
  ...(options.readOnly ? ["This connection is read-only; writes are refused."] : []),
132
132
  ],
133
- summary: "Each caller's decks: text, notes, layouts, comments, guarded edits and indexed Slides JSON operations.",
133
+ summary: "Load for deck text, notes, layouts, comments and guarded Slides edits.",
134
134
  required: true,
135
135
  }),
136
136
  tools: slidesTools,
@@ -53,8 +53,8 @@ function usageGuide(options) {
53
53
  return {
54
54
  heading: "Stripe usage",
55
55
  summary: options.mode === "production"
56
- ? "PRODUCTION: real money. One configured live-mode key over REST; resolve ids before acting."
57
- : "Sandbox: test data only. One configured test-mode key over REST; resolve ids before acting.",
56
+ ? "Load for live Stripe payments, refunds and invoices; real money, resolve ids before acting."
57
+ : "Load for sandbox Stripe payments, refunds and invoices; test data, resolve ids before acting.",
58
58
  context: [
59
59
  `Mode: ${options.mode}. Connector purpose: ${options.purpose}`,
60
60
  copy.warning,
@@ -73,7 +73,7 @@ function usageGuide(options) {
73
73
  ? [`List tools return ${options.defaultPageSize} rows by default on this connection.`]
74
74
  : []),
75
75
  ],
76
- summary: `${live ? "Live" : "Test"} giving: donor-data reduction, cents and ISO dates, ids that join, and the approval-gated write hatch.`,
76
+ summary: "Load for giving records, donor joins, cents, ISO dates and guarded writes.",
77
77
  // Required: the reduction rule for donor data and the id joins live only
78
78
  // in the guide. No schema can carry either.
79
79
  required: true,
@@ -414,6 +414,7 @@ export function usageGuide(options) {
414
414
  options = pinOptions(options);
415
415
  const team = defaultTeam(options);
416
416
  return {
417
+ summary: "Load for Vercel team scope, project/deployment ids, build events, runtime logs and guarded writes.",
417
418
  context: [
418
419
  `Vercel REST access token is already configured. Account purpose: ${options.purpose}`,
419
420
  options.pin
package/dist/registry.js CHANGED
@@ -20,7 +20,7 @@ import { attachOAuthSealer, vaultOAuthSealer } from "./oauth-sealing.js";
20
20
  import { boundedStatus, failureRecord, failureStatus, logFailure, ownStatus } from "./operator-record.js";
21
21
  import { ObservedOutputSchemas } from "./result-shapes.js";
22
22
  import { ContinuationBinding, OperationCredentials, ContinuationStorage, operationView, setViewService, duringViewEffect, } from "./runtime/call.js";
23
- import { hasNativeStorage, registerNativeStorage } from "./runtime/native-storage.js";
23
+ import { getNativeStorage, hasNativeStorage, registerNativeStorage } from "./runtime/native-storage.js";
24
24
  import { observeStorageWrites } from "./runtime/request-reads.js";
25
25
  import { attachStorageDigestKey } from "./storage-digest.js";
26
26
  import { requestReads } from "./runtime/request-scope.js";
@@ -31,7 +31,7 @@ import { Storage } from "./runtime/services.js";
31
31
  import { SharedRead } from "./runtime/shared-read.js";
32
32
  import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageGetMany, storageSet, storageList, } from "./runtime/storage.js";
33
33
  import { redactCatalog, sentSecretsFor, sentSecretsForRequest } from "./sent-secrets.js";
34
- import { GUIDE_SUMMARY_LENGTH, normalizeGuideSummary } from "./skills.js";
34
+ import { GUIDE_SUMMARY_LENGTH, normalizeGuideSummary, connectorGuide, connectorGuideSummary } from "./skills.js";
35
35
  import { jsonCodec, OAUTH_HANDOFF_TTL_SECONDS, oauthHandoffKeys, resultKeys, scopes, stashExpiryKeys, stashLedgerKeys, } from "./storage/keys.js";
36
36
  import { assertStaticToolNames, hasControlCharacters } from "./tool-name.js";
37
37
  import { carryToolClassification, classifyTool } from "./tool-safety.js";
@@ -298,6 +298,13 @@ export class Registry {
298
298
  `normalization; the discovery bound is ${GUIDE_SUMMARY_LENGTH}. ` +
299
299
  "Shorten it or omit it to derive one.");
300
300
  }
301
+ // Advisory English-language heuristic: a task verb or operation noun
302
+ // distinguishes actionable guidance from credential/scope facts. Never
303
+ // log guide text, which may contain deployment-private information.
304
+ if (connectorGuide(c) !== undefined &&
305
+ !/\b(load|use|when|read|reads|write|writes|search|query|inspect|resolve|list|review|edit|create|update|deploy|paging|reconciliation|accounting)\b/i.test(connectorGuideSummary(c) ?? "")) {
306
+ opts.logger.warn(`Connector "${c.id}" guide needs an actionable summary; say when to load it with usageGuide.summary.`);
307
+ }
301
308
  this.connectors.set(c.id, c);
302
309
  if (c.callAdmission !== undefined) {
303
310
  this.callAdmission.set(c.id, new ConnectorCallAdmissionController(c.id, c.callAdmission));
@@ -874,16 +881,21 @@ export class Registry {
874
881
  // ID after failure or timeout, including a late header commit.
875
882
  const remaining = Math.max(0, writeDeadline - (yield* Clock.currentTimeMillis));
876
883
  const written = yield* Effect.gen(function* () {
877
- for (let index = keys.length - 1; index >= 0; index--) {
884
+ const multi = getNativeStorage(self.opts.storage)?.setMany;
885
+ for (let index = keys.length - 1; index >= 0;) {
886
+ const first = multi && index > 0 ? Math.max(1, index - 63) : index;
878
887
  const before = yield* Clock.currentTimeMillis;
879
888
  if (before >= writeDeadline || before >= chunkExpiry)
880
889
  return false;
881
890
  pendingWrite = true;
882
- yield* storageSet(keys[index], chunks[index], { expiresAtMs: chunkExpiry }).pipe(Effect.tap(() => Effect.sync(() => {
891
+ yield* (multi && index > 0
892
+ ? multi(Array.from({ length: index - first + 1 }, (_, offset) => [keys[index - offset], chunks[index - offset]]), { expiresAtMs: chunkExpiry })
893
+ : storageSet(keys[index], chunks[index], { expiresAtMs: chunkExpiry })).pipe(Effect.tap(() => Effect.sync(() => {
883
894
  pendingWrite = false;
884
895
  })));
885
896
  if ((yield* Clock.currentTimeMillis) >= writeDeadline)
886
897
  return false;
898
+ index = first - 1;
887
899
  }
888
900
  return true;
889
901
  }).pipe(Effect.timeoutOrElse({ duration: Duration.millis(remaining), orElse: () => Effect.succeed(false) }), Effect.onError(() => release.pipe(Effect.ignore)));
@@ -1,8 +1,10 @@
1
+ import { utilityRecovery } from "./utility-recovery.js";
1
2
  import { Effect } from "effect";
2
3
  import { classifyCallError, ConnectorCallError } from "./errors.js";
3
4
  import { InvocationFailure, timed } from "./invocation.js";
4
5
  import { withDeadlineEffect } from "./runtime/run.js";
5
6
  import { sentSecretsFor } from "./sent-secrets.js";
7
+ import { downstreamOrigin, isSkillResource } from "./skill-origin.js";
6
8
  import { hasControlCharacters } from "./tool-name.js";
7
9
  const encoder = new TextEncoder();
8
10
  const URI_HINT = "Use resource://<connectorId>/<encodeURIComponent(downstreamUri)> with a whole-connector grant.";
@@ -26,13 +28,26 @@ function resourceTarget(registry, raw) {
26
28
  const connector = registry.getResourceConnector(match[1]);
27
29
  if (!connector?.readResource) {
28
30
  throw new InvocationFailure({
29
- code: "unknown_address",
31
+ ...utilityRecovery(connector ? "unsupported_capability" : "unknown_address", "No resource reader is available."),
32
+ code: connector ? "unsupported_capability" : "unknown_address",
30
33
  message: `No resource reader is available for this address. ${URI_HINT}`,
31
34
  retryable: false,
32
35
  });
33
36
  }
34
37
  return { connector, uri };
35
38
  }
39
+ /** Frame skill text for a program; native resources/read keeps exact bytes and never comes here. */
40
+ function labelSkillText(contents, connectorId, uri) {
41
+ const items = contents?.contents;
42
+ if (!Array.isArray(items))
43
+ return contents;
44
+ return {
45
+ ...contents,
46
+ contents: items.map((item) => typeof item?.text === "string" && (isSkillResource(uri) || isSkillResource(String(item.uri ?? "")))
47
+ ? { ...item, text: `${downstreamOrigin(connectorId)}\n\n${item.text}` }
48
+ : item),
49
+ };
50
+ }
36
51
  /** A protocol read, under the same request scope, call permit and deadline as tools. */
37
52
  export function readResource(registry, raw, options) {
38
53
  let connector;
@@ -68,7 +83,7 @@ export function readResource(registry, raw, options) {
68
83
  },
69
84
  catch: (error) => error,
70
85
  }));
71
- return options.sentSecrets.redact(contents);
86
+ return labelSkillText(options.sentSecrets.redact(contents), target.connector.id, target.uri);
72
87
  })), {
73
88
  timeoutMs: options.timeoutMs,
74
89
  ...(options.signal ? { signal: options.signal } : {}),
@@ -98,6 +113,10 @@ export function readResource(registry, raw, options) {
98
113
  retry: "Retry connecta.read with the same resource URI after the operator completes recovery.",
99
114
  };
100
115
  }
116
+ if (["unavailable", "unsupported_capability", "input_required_unsupported"].includes(details.code) &&
117
+ !details.nextAction) {
118
+ details = { ...details, nextAction: utilityRecovery(details.code, details.message).nextAction };
119
+ }
101
120
  return Effect.fail(new InvocationFailure(options.sentSecrets.redact(details)));
102
121
  }));
103
122
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `meta`: the six meta-tools. `code`: in-program wording, selected only by the
3
+ * eval code arm. `native` and `native-code`: native pools without and with
4
+ * `execute_code`.
5
+ */
6
+ const NAMES = {
7
+ meta: Object.freeze({
8
+ search: "search_tools",
9
+ read: "call_tool",
10
+ write: "call_destructive_tool",
11
+ program: "execute_code",
12
+ authorize: "authorize_connector",
13
+ skills: "skills",
14
+ }),
15
+ code: Object.freeze({
16
+ search: "connecta.search inside execute_code",
17
+ read: "connecta.call inside execute_code",
18
+ write: "connecta.call inside execute_code",
19
+ program: "execute_code",
20
+ authorize: "authorize_connector",
21
+ skills: "skills",
22
+ }),
23
+ native: Object.freeze({
24
+ search: "your host's tool search",
25
+ read: "the original native tool",
26
+ write: "the original native tool",
27
+ program: "your host's tool search",
28
+ authorize: "the returned authorization handoff",
29
+ skills: "skills",
30
+ }),
31
+ "native-code": Object.freeze({
32
+ search: "your host's tool search",
33
+ read: "the original native tool",
34
+ write: "the original native tool",
35
+ program: "execute_code",
36
+ authorize: "the returned authorization handoff",
37
+ skills: "skills",
38
+ }),
39
+ };
40
+ /** The six-tool surface's names: the default wherever no other surface applies. */
41
+ export const META_ROUTES = NAMES.meta;
42
+ export function routeNames(surface) {
43
+ return NAMES[surface];
44
+ }
@@ -8,6 +8,7 @@ export function operatorActivityContext(context, authz) {
8
8
  return {
9
9
  sink: activity.store,
10
10
  recordTool: activity.recordTool,
11
+ recordOperation: activity.recordOperation,
11
12
  actor: authz.actor,
12
13
  ...(principal
13
14
  ? { principalActor: { kind: authz.actor.kind, id: principal.id, namespace: principal.namespace } }