@zackbart/connecta 0.40.0 → 0.42.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 (195) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/bin/connecta.mjs +26 -17
  3. package/bin/meta-tool-names.d.mts +3 -1
  4. package/bin/meta-tool-names.mjs +4 -1
  5. package/bin/version.mjs +1 -1
  6. package/dist/activity.d.ts +32 -5
  7. package/dist/activity.js +32 -6
  8. package/dist/advertised-schema.js +15 -0
  9. package/dist/agent-observation.js +24 -2
  10. package/dist/agent-routes.js +33 -44
  11. package/dist/auth/clerk.d.ts +6 -1
  12. package/dist/auth/clerk.js +16 -0
  13. package/dist/auth/cloudflare-access.js +49 -7
  14. package/dist/auth/downstream-oauth/consent.js +79 -29
  15. package/dist/auth/downstream-oauth/grant-store.js +58 -7
  16. package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
  17. package/dist/auth/downstream-oauth/refresh-records.js +6 -5
  18. package/dist/auth/downstream-oauth.js +15 -4
  19. package/dist/auth/oauth-request-not-sent.js +4 -0
  20. package/dist/auth/static-oauth.js +77 -39
  21. package/dist/auth-elicitation.js +118 -61
  22. package/dist/authorization-handoff.js +36 -3
  23. package/dist/catalog-cache.js +352 -43
  24. package/dist/catalog-freshness.js +25 -0
  25. package/dist/catalog-service.js +408 -78
  26. package/dist/catalog.js +216 -62
  27. package/dist/config-defaults.d.ts +2 -1
  28. package/dist/config-defaults.js +4 -1
  29. package/dist/config.d.ts +53 -21
  30. package/dist/config.js +56 -17
  31. package/dist/connectors/remote-mcp/call.js +1 -1
  32. package/dist/connectors/remote-mcp/catalog.js +52 -28
  33. package/dist/connectors/remote-mcp/connection.js +113 -22
  34. package/dist/connectors/remote-mcp/oauth.js +2 -2
  35. package/dist/connectors/remote-mcp/resources.js +5 -2
  36. package/dist/connectors/remote-mcp/transport.js +24 -6
  37. package/dist/connectors/remote-mcp.js +14 -5
  38. package/dist/credential-contract.d.ts +3 -1
  39. package/dist/describe-config.d.ts +4 -2
  40. package/dist/describe-config.js +1 -0
  41. package/dist/discovery-health.js +26 -11
  42. package/dist/downstream-input.js +11 -2
  43. package/dist/errors.d.ts +60 -4
  44. package/dist/errors.js +88 -3
  45. package/dist/execute.js +491 -202
  46. package/dist/executor-admission.js +2 -0
  47. package/dist/executor-contract.js +6 -0
  48. package/dist/executor-result.js +3 -3
  49. package/dist/executors/quickjs-child.js +14 -5
  50. package/dist/executors/quickjs-runtime.d.ts +2 -0
  51. package/dist/executors/quickjs-runtime.js +37 -20
  52. package/dist/executors/quickjs.js +24 -7
  53. package/dist/guarded-transport.js +2 -0
  54. package/dist/guest-functions.js +5 -4
  55. package/dist/guest-runtime.js +37 -1
  56. package/dist/guest-types.d.ts +18 -0
  57. package/dist/http/client.js +14 -2
  58. package/dist/http/errors.js +9 -4
  59. package/dist/http/fields.js +17 -5
  60. package/dist/http/hatch.js +11 -3
  61. package/dist/http/node-transport.js +110 -4
  62. package/dist/http/oauth.js +17 -6
  63. package/dist/http/paging.js +1 -1
  64. package/dist/http/provider.js +2 -6
  65. package/dist/http/vendor.js +6 -1
  66. package/dist/index.d.ts +1 -1
  67. package/dist/index.js +15 -21
  68. package/dist/invocation-failure.js +28 -0
  69. package/dist/invocation.js +167 -64
  70. package/dist/membership.d.ts +1 -1
  71. package/dist/meta-output.js +24 -18
  72. package/dist/meta-tool-names.d.ts +1 -1
  73. package/dist/meta-tool-names.js +1 -1
  74. package/dist/meta-tools.js +286 -73
  75. package/dist/module-contracts.d.ts +3 -1
  76. package/dist/native-recovery.js +4 -1
  77. package/dist/native-tools.js +177 -55
  78. package/dist/node.d.ts +7 -0
  79. package/dist/node.js +85 -4
  80. package/dist/oauth-handoff.js +53 -43
  81. package/dist/operation-activity.js +104 -0
  82. package/dist/operation-vocabulary.js +6 -0
  83. package/dist/operator-record.d.ts +9 -1
  84. package/dist/operator-record.js +16 -0
  85. package/dist/operator-ui/generated.js +2 -2
  86. package/dist/program-result.js +2 -0
  87. package/dist/program-source.js +1 -1
  88. package/dist/program-writes.js +112 -19
  89. package/dist/provider.d.ts +1 -14
  90. package/dist/provider.js +0 -3
  91. package/dist/providers/_shared/google/kit.js +2 -2
  92. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  93. package/dist/providers/breeze/api.js +2 -2
  94. package/dist/providers/breeze/index.js +1 -1
  95. package/dist/providers/breeze/skill.generated.js +1 -1
  96. package/dist/providers/ccb/api.js +2 -2
  97. package/dist/providers/ccb/index.js +1 -1
  98. package/dist/providers/ccb/skill.generated.js +1 -1
  99. package/dist/providers/cloudflare/api.js +2 -2
  100. package/dist/providers/cloudflare/index.js +3 -1
  101. package/dist/providers/cloudflare/skill.generated.js +1 -1
  102. package/dist/providers/docs/index.js +1 -1
  103. package/dist/providers/docs/skill.generated.js +1 -1
  104. package/dist/providers/drive/index.js +1 -1
  105. package/dist/providers/drive/skill.generated.js +1 -1
  106. package/dist/providers/forms/index.js +2 -1
  107. package/dist/providers/forms/skill.generated.js +1 -1
  108. package/dist/providers/github/api.js +98 -6
  109. package/dist/providers/github/index.js +8 -3
  110. package/dist/providers/github/lib/auth.d.ts +3 -1
  111. package/dist/providers/github/lib/auth.js +43 -12
  112. package/dist/providers/github/lib/discovery.js +11 -1
  113. package/dist/providers/github/lib/operations.js +8 -0
  114. package/dist/providers/github/safety.js +39 -0
  115. package/dist/providers/github/skill.generated.js +1 -1
  116. package/dist/providers/github/tools.js +12 -5
  117. package/dist/providers/gmail/index.js +2 -1
  118. package/dist/providers/gmail/skill.generated.js +1 -1
  119. package/dist/providers/granola/index.js +1 -1
  120. package/dist/providers/granola/skill.generated.js +1 -1
  121. package/dist/providers/overflow/index.js +3 -1
  122. package/dist/providers/overflow/skill.generated.js +1 -1
  123. package/dist/providers/planning-center/index.js +12 -6
  124. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  125. package/dist/providers/planning-center/skill.generated.js +1 -1
  126. package/dist/providers/quickbooks/api.js +28 -25
  127. package/dist/providers/quickbooks/index.js +3 -6
  128. package/dist/providers/quickbooks/lib/writes.js +55 -0
  129. package/dist/providers/quickbooks/safety.js +17 -5
  130. package/dist/providers/quickbooks/skill.generated.js +1 -1
  131. package/dist/providers/sheets/index.js +2 -1
  132. package/dist/providers/sheets/skill.generated.js +1 -1
  133. package/dist/providers/slides/index.js +3 -3
  134. package/dist/providers/slides/skill.generated.js +1 -1
  135. package/dist/providers/stripe/index.js +2 -2
  136. package/dist/providers/stripe/skill.generated.js +1 -1
  137. package/dist/providers/tithely/index.js +2 -1
  138. package/dist/providers/tithely/skill.generated.js +1 -1
  139. package/dist/providers/vercel/api.js +3 -2
  140. package/dist/providers/vercel/skill.generated.js +1 -1
  141. package/dist/registry.d.ts +36 -4
  142. package/dist/registry.js +142 -30
  143. package/dist/resource-read.js +21 -2
  144. package/dist/result-preview.js +210 -0
  145. package/dist/result-seal.js +168 -0
  146. package/dist/route-names.js +53 -0
  147. package/dist/routes/activity-context.js +1 -0
  148. package/dist/routes/activity-disclosure.js +26 -4
  149. package/dist/routes/activity.js +2 -1
  150. package/dist/routes/connect.js +21 -13
  151. package/dist/routes/mcp.js +83 -35
  152. package/dist/routes/shared.d.ts +6 -0
  153. package/dist/routes/ui-config.js +1 -1
  154. package/dist/routes/ui-data.js +3 -2
  155. package/dist/runtime/call.js +15 -10
  156. package/dist/runtime/native-storage.js +12 -0
  157. package/dist/runtime/request-reads.js +66 -14
  158. package/dist/runtime/request-scope.js +38 -2
  159. package/dist/runtime/run.js +4 -1
  160. package/dist/sent-secrets.d.ts +20 -0
  161. package/dist/sent-secrets.js +49 -1
  162. package/dist/skill-origin.js +16 -0
  163. package/dist/skills.js +213 -57
  164. package/dist/storage/d1-driver.js +33 -5
  165. package/dist/storage/keys.js +16 -7
  166. package/dist/storage/sql-native.js +60 -3
  167. package/dist/storage/sql.js +6 -0
  168. package/dist/timing-trace.js +84 -0
  169. package/dist/tool-safety.d.ts +33 -0
  170. package/dist/tool-safety.js +51 -15
  171. package/dist/types.d.ts +36 -0
  172. package/dist/url-safety.js +38 -20
  173. package/dist/usage-guide.js +14 -13
  174. package/dist/utility-recovery.js +33 -0
  175. package/dist/validate.js +29 -10
  176. package/dist/version.d.ts +1 -1
  177. package/dist/version.js +1 -1
  178. package/dist/worker.js +60 -15
  179. package/documentation/architecture.md +152 -43
  180. package/documentation/auth.md +160 -53
  181. package/documentation/code-mode.md +396 -119
  182. package/documentation/deploying.md +35 -10
  183. package/documentation/hosted-mcp-migration.md +9 -3
  184. package/documentation/integrating.md +27 -5
  185. package/documentation/meta-tools.md +761 -157
  186. package/documentation/operating.md +91 -4
  187. package/documentation/operator-ui.md +51 -2
  188. package/documentation/providers.md +34 -20
  189. package/examples/worker/README.md +3 -3
  190. package/guides/how-it-works.md +2 -1
  191. package/guides/safety-and-trust.md +1 -3
  192. package/package.json +10 -8
  193. package/templates/node/AGENTS.md +3 -3
  194. package/templates/node/README.md +1 -1
  195. package/templates/node/package.json +1 -1
@@ -4,6 +4,9 @@ import { authCall, authInvoke, authFlow, authStorage, registerAuth } from "./aut
4
4
  import { runEdge } from "./runtime/run.js";
5
5
  import { oauthConnectKeys } from "./storage/keys.js";
6
6
  const HANDOFF_TTL_MS = 15 * 60_000;
7
+ // A flow can issue a link until its ten-minute retry state ends, and that link lives
8
+ // fifteen more, so a reset's record outlives every link that could repeat it.
9
+ const FLOW_RESET_EXPIRY = { ttlSeconds: 30 * 60 };
7
10
  const INTERACTIVE_OAUTH_REQUIRED = "An interactive provider (Clerk or Cloudflare Access) is required to connect OAuth connectors.";
8
11
  // Length-prefixed fields avoid JSON keys and escaping without reserving any
9
12
  // character in an identity. This is an opaque wire encoding, not encryption.
@@ -16,7 +19,7 @@ function encodeHandoff(h) {
16
19
  h.expiresAt.toString(36),
17
20
  h.nonce,
18
21
  h.force ? "1" : "0",
19
- h.after ?? "",
22
+ h.flow ?? "",
20
23
  ]
21
24
  .map((field) => `${field.length}:${field}`)
22
25
  .join("");
@@ -36,7 +39,7 @@ function decodeHandoff(value) {
36
39
  fields.push(value.slice(offset, offset + length));
37
40
  offset += length;
38
41
  }
39
- const [connector, owner, principal, origin, expiry, nonce, force, after] = fields;
42
+ const [connector, owner, principal, origin, expiry, nonce, force, flow] = fields;
40
43
  if (fields.length !== 8 || offset !== value.length || !/^[0-9a-z]+$/.test(expiry) || !/^[01]$/.test(force))
41
44
  throw new Error("Invalid handoff fields");
42
45
  return {
@@ -47,7 +50,7 @@ function decodeHandoff(value) {
47
50
  expiresAt: parseInt(expiry, 36),
48
51
  nonce: nonce,
49
52
  force: force === "1",
50
- ...(after ? { after } : {}),
53
+ ...(flow ? { flow } : {}),
51
54
  };
52
55
  }
53
56
  function encodeSegment(value) {
@@ -89,10 +92,10 @@ const oauthConnectUrlNative = /* @__PURE__ */ registerAuth(oauthConnectUrl, func
89
92
  ]))).url;
90
93
  });
91
94
  });
92
- export function oauthConnectLink(opts, baseUrl, connectorId, principal, force = false, opaque = false, after) {
93
- return runEdge(oauthConnectLinkNative(opts, baseUrl, connectorId, principal, force, opaque, after));
95
+ export function oauthConnectLink(opts, baseUrl, connectorId, principal, force = false, opaque = false, flow) {
96
+ return runEdge(oauthConnectLinkNative(opts, baseUrl, connectorId, principal, force, opaque, flow));
94
97
  }
95
- const oauthConnectLinkNative = /* @__PURE__ */ registerAuth(oauthConnectLink, function (opts, baseUrl, connectorId, principal, force = false, opaque = false, after) {
98
+ const oauthConnectLinkNative = /* @__PURE__ */ registerAuth(oauthConnectLink, function (opts, baseUrl, connectorId, principal, force = false, opaque = false, flow) {
96
99
  return authFlow(function* () {
97
100
  const unavailable = oauthConnectUnavailable(opts);
98
101
  if (unavailable)
@@ -108,7 +111,7 @@ const oauthConnectLinkNative = /* @__PURE__ */ registerAuth(oauthConnectLink, fu
108
111
  expiresAt: Date.now() + HANDOFF_TTL_MS,
109
112
  nonce: crypto.randomUUID(),
110
113
  force,
111
- ...(after ? { after } : {}),
114
+ ...(flow ? { flow } : {}),
112
115
  };
113
116
  const vault = opts.config.vault;
114
117
  if (opaque && (!vault.seal || !vault.open))
@@ -126,32 +129,14 @@ const oauthConnectLinkNative = /* @__PURE__ */ registerAuth(oauthConnectLink, fu
126
129
  });
127
130
  });
128
131
  /** A claim precedes OAuth reset; only a completed start can prove recovery. */
129
- export function oauthConnectLinkProgress(opts, baseUrl, id, nonce) {
130
- return runEdge(oauthConnectLinkProgressNative(opts, baseUrl, id, nonce));
132
+ export function oauthConnectLinkStarted(opts, baseUrl, id, nonce) {
133
+ return runEdge(oauthConnectLinkStartedNative(opts, baseUrl, id, nonce));
131
134
  }
132
- const oauthConnectLinkProgressNative = /* @__PURE__ */ registerAuth(oauthConnectLinkProgress, function (opts, baseUrl, id, nonce) {
135
+ const oauthConnectLinkStartedNative = /* @__PURE__ */ registerAuth(oauthConnectLinkStarted, function (opts, baseUrl, id, nonce) {
133
136
  return authFlow(function* () {
134
- const stage = yield* upwardStep(authStorage(opts.registry.contextFor(id, baseUrl).storage, "get", [
137
+ return ((yield* upwardStep(authStorage(opts.registry.contextFor(id, baseUrl).storage, "get", [
135
138
  oauthConnectKeys.used(nonce),
136
- ]));
137
- return stage === "used" ? "claimed" : stage === "started" || stage === "failed" ? stage : undefined;
138
- });
139
- });
140
- /** Make outstanding flow links single-use refusals before a completion check. */
141
- export function closeOAuthConnectLinks(opts, baseUrl, id, nonces) {
142
- return runEdge(closeOAuthConnectLinksNative(opts, baseUrl, id, nonces));
143
- }
144
- const closeOAuthConnectLinksNative = /* @__PURE__ */ registerAuth(closeOAuthConnectLinks, function (opts, baseUrl, id, nonces) {
145
- return authFlow(function* () {
146
- const storage = opts.registry.contextFor(id, baseUrl).storage;
147
- for (const nonce of nonces) {
148
- yield* upwardStep(authStorage(storage, "compareAndSet", [
149
- oauthConnectKeys.used(nonce),
150
- null,
151
- "closed",
152
- { ttlSeconds: HANDOFF_TTL_MS / 1000 },
153
- ]));
154
- }
139
+ ]))) === "started");
155
140
  });
156
141
  });
157
142
  /** Consuming a round prevents replay from creating sibling retry lineages. */
@@ -206,6 +191,9 @@ const verifyOAuthHandoffNative = /* @__PURE__ */ registerAuth(verifyOAuthHandoff
206
191
  atob(payload.slice(3)),
207
192
  ]))
208
193
  : atob(payload));
194
+ // A retired predecessor binding: nothing here can honor it, so it must not be ignored.
195
+ if ("after" in h)
196
+ return null;
209
197
  }
210
198
  const connector = opts.registry.getConnector(connectorId);
211
199
  if (!connector ||
@@ -221,8 +209,8 @@ const verifyOAuthHandoffNative = /* @__PURE__ */ registerAuth(verifyOAuthHandoff
221
209
  !h.nonce ||
222
210
  typeof h.force !== "boolean")
223
211
  return null;
224
- if (h.after !== undefined &&
225
- (typeof h.after !== "string" || !/^[a-f0-9-]{36}$/.test(h.after) || h.after === h.nonce || h.force))
212
+ if (h.flow !== undefined &&
213
+ (typeof h.flow !== "string" || !/^[a-f0-9-]{36}$/.test(h.flow) || h.flow === h.nonce || !h.force))
226
214
  return null;
227
215
  if (yield* upwardStep(authStorage(opts.registry.contextFor(connectorId, baseUrl).storage, "get", [
228
216
  oauthConnectKeys.used(h.nonce),
@@ -235,21 +223,34 @@ const verifyOAuthHandoffNative = /* @__PURE__ */ registerAuth(verifyOAuthHandoff
235
223
  }
236
224
  });
237
225
  });
238
- /** Claim only after browser identity and permissions pass, before starting OAuth. */
239
- export function consumeOAuthConnectLink(opts, handoff) {
240
- return runEdge(consumeOAuthConnectLinkNative(opts, handoff));
226
+ /**
227
+ * Claim only after browser identity and permissions pass, before starting OAuth.
228
+ * A forced flow resets once, whichever of its links arrives first. A later visitor
229
+ * continues that attempt, waits on it without spending its link, or, once it has
230
+ * failed, takes over the reset.
231
+ */
232
+ export function claimOAuthConnectStart(opts, handoff) {
233
+ return runEdge(claimOAuthConnectStartNative(opts, handoff));
241
234
  }
242
- const consumeOAuthConnectLinkNative = /* @__PURE__ */ registerAuth(consumeOAuthConnectLink, function (opts, handoff) {
235
+ const claimOAuthConnectStartNative = /* @__PURE__ */ registerAuth(claimOAuthConnectStart, function (opts, handoff) {
243
236
  return authFlow(function* () {
244
237
  if (handoff.expiresAt <= Date.now())
245
- return false;
238
+ return "refused";
246
239
  const storage = opts.registry.contextFor(handoff.connector, handoff.origin).storage;
247
- if (handoff.after &&
248
- (yield* upwardStep(authStorage(storage, "get", [oauthConnectKeys.used(handoff.after)]))) !==
249
- "started")
250
- return false;
240
+ const reset = handoff.force ? oauthConnectKeys.reset(handoff.flow ?? handoff.nonce) : undefined;
241
+ const prior = reset ? yield* upwardStep(authStorage(storage, "get", [reset])) : "started";
242
+ if (prior === "used")
243
+ return "pending";
244
+ const claiming = prior !== "started";
245
+ if (claiming &&
246
+ !(yield* upwardStep(authStorage(storage, "compareAndSet", [reset, prior, "used", FLOW_RESET_EXPIRY]))))
247
+ return "pending";
251
248
  const expiry = { ttlSeconds: Math.max(1, Math.ceil((handoff.expiresAt - Date.now()) / 1000)) };
252
- return yield* upwardStep(authStorage(storage, "compareAndSet", [oauthConnectKeys.used(handoff.nonce), null, "used", expiry]));
249
+ if (yield* upwardStep(authStorage(storage, "compareAndSet", [oauthConnectKeys.used(handoff.nonce), null, "used", expiry])))
250
+ return claiming ? "reset" : "continue";
251
+ if (claiming)
252
+ yield* upwardStep(authStorage(storage, "compareAndSet", [reset, "used", prior, FLOW_RESET_EXPIRY]));
253
+ return "refused";
253
254
  });
254
255
  });
255
256
  /** Terminal marker only after the start and every tracked reset have settled. */
@@ -258,9 +259,18 @@ export function finishOAuthConnectStart(opts, handoff, outcome) {
258
259
  }
259
260
  const finishOAuthConnectStartNative = /* @__PURE__ */ registerAuth(finishOAuthConnectStart, function (opts, handoff, outcome) {
260
261
  return authFlow(function* () {
262
+ const storage = opts.registry.contextFor(handoff.connector, handoff.origin).storage;
263
+ // Settled first, and whatever the link's expiry: a finished link proves its flow's
264
+ // reset finished, and only the visitor who claimed a reset can find it still claimed.
265
+ if (handoff.force)
266
+ yield* upwardStep(authStorage(storage, "compareAndSet", [
267
+ oauthConnectKeys.reset(handoff.flow ?? handoff.nonce),
268
+ "used",
269
+ outcome,
270
+ FLOW_RESET_EXPIRY,
271
+ ]));
261
272
  if (handoff.expiresAt <= Date.now())
262
273
  return false;
263
- const storage = opts.registry.contextFor(handoff.connector, handoff.origin).storage;
264
274
  const expiry = { ttlSeconds: Math.max(1, Math.ceil((handoff.expiresAt - Date.now()) / 1000)) };
265
275
  return yield* upwardStep(authStorage(storage, "compareAndSet", [oauthConnectKeys.used(handoff.nonce), "used", outcome, expiry]));
266
276
  });
@@ -0,0 +1,104 @@
1
+ // The root graph calls the optional recorder, never the /activity implementation.
2
+ import { Cause, Effect, Exit } from "effect";
3
+ import { InvocationFailure } from "./invocation-failure.js";
4
+ import { ExecutorAdmissionError } from "./executor-admission.js";
5
+ import { DiscoveryPolicyError } from "./catalog-service.js";
6
+ import { classificationCode, classifyCallError } from "./errors.js";
7
+ import { usesCallerIdentity } from "./connector-caller.js";
8
+ export function operationActivity(context, registry, name, work, counts, source) {
9
+ if (!context?.recordOperation)
10
+ return work;
11
+ return Effect.suspend(() => {
12
+ const started = performance.now();
13
+ const connectors = registry.listConnectors();
14
+ // Human operations stay owner-only, including discovery of private catalogs.
15
+ let personal = Boolean(context.principalActor);
16
+ try {
17
+ personal ||= connectors.some((connector) => connector.authScope === "personal" ||
18
+ !connector.staticTools ||
19
+ usesCallerIdentity(connector) ||
20
+ connector.describe?.().auth?.mode === "request");
21
+ }
22
+ catch {
23
+ personal = true;
24
+ }
25
+ return work.pipe(Effect.onExit((exit) => Effect.sync(() => {
26
+ const result = Exit.isSuccess(exit)
27
+ ? exit.value
28
+ : undefined;
29
+ const failed = Exit.isFailure(exit) || result?.isError === true;
30
+ const interrupted = Exit.isFailure(exit) && exit.cause.reasons.some(Cause.isInterruptReason);
31
+ const failure = Exit.isFailure(exit) ? Cause.squash(exit.cause) : undefined;
32
+ const errorCode = interrupted
33
+ ? "cancelled"
34
+ : classificationCode(Exit.isFailure(exit)
35
+ ? failure instanceof InvocationFailure ||
36
+ failure instanceof ExecutorAdmissionError ||
37
+ failure instanceof DiscoveryPolicyError
38
+ ? failure.code
39
+ : classifyCallError(failure).code
40
+ : result?.structuredContent?.error?.code);
41
+ try {
42
+ // Result pages already measure their payload. Other operations have no
43
+ // reusable measurement: never serialize discovery or media for telemetry.
44
+ const page = name === "result" && Exit.isSuccess(exit)
45
+ ? exit.value
46
+ : undefined;
47
+ const bytes = page?.structuredContent?.bytes ?? page?.bytes;
48
+ const resultBytes = typeof bytes === "number" && Number.isSafeInteger(bytes) && bytes >= 0 ? bytes : undefined;
49
+ context.recordOperation(context, {
50
+ operation: {
51
+ name,
52
+ connectors: connectors.map((connector) => connector.id),
53
+ personal,
54
+ ...(counts ? { hostCalls: counts() } : {}),
55
+ },
56
+ ...(source ? { source } : {}),
57
+ durationMs: performance.now() - started,
58
+ resultBytes,
59
+ outcome: !failed
60
+ ? "success"
61
+ : errorCode === "timeout"
62
+ ? "timeout"
63
+ : errorCode === "cancelled" || errorCode === "executor_cancelled"
64
+ ? "cancelled"
65
+ : "error",
66
+ errorCode,
67
+ });
68
+ }
69
+ catch {
70
+ /* Optional recording cannot replace the operation's result. */
71
+ }
72
+ })));
73
+ });
74
+ }
75
+ const OPERATION_NAMES = new Set(["search", "describe", "result", "skill", "program"]);
76
+ export function checkedActivityOperation(value) {
77
+ if (!value || typeof value !== "object")
78
+ return undefined;
79
+ const input = value;
80
+ if (!OPERATION_NAMES.has(input.name) ||
81
+ typeof input.personal !== "boolean" ||
82
+ !Array.isArray(input.connectors) ||
83
+ input.connectors.some((id) => typeof id !== "string" || !/^[a-zA-Z0-9_-]+$/.test(id)))
84
+ return undefined;
85
+ const counts = input.hostCalls;
86
+ if (counts &&
87
+ [counts.attempted, counts.admitted, counts.succeeded, counts.failed].some((n) => typeof n !== "number" || !Number.isSafeInteger(n) || n < 0))
88
+ return undefined;
89
+ return {
90
+ name: input.name,
91
+ connectors: [...new Set(input.connectors)],
92
+ personal: input.personal,
93
+ ...(counts
94
+ ? {
95
+ hostCalls: {
96
+ attempted: counts.attempted,
97
+ admitted: counts.admitted,
98
+ succeeded: counts.succeeded,
99
+ failed: counts.failed,
100
+ },
101
+ }
102
+ : {}),
103
+ };
104
+ }
@@ -193,6 +193,12 @@ export function registerConnectorVocabulary(connector, declarations) {
193
193
  if (entries.length)
194
194
  connectors.set(connector, new Map(entries));
195
195
  }
196
+ /** Give a connector that delegates to an api() connector the vocabulary its hatch registered there. */
197
+ export function carryConnectorVocabulary(source, target) {
198
+ const registered = connectors.get(source);
199
+ if (registered)
200
+ connectors.set(target, registered);
201
+ }
196
202
  /**
197
203
  * Read tools whose operation vocabulary matches every query word, and the
198
204
  * fully determined reads among those operations, each through a visible read
@@ -23,8 +23,15 @@ export declare function labelErrorClass(ctor: {
23
23
  * name is exactly one of those, else `DOMException`.
24
24
  */
25
25
  export declare function errorLabel(failure: unknown): string | undefined;
26
+ interface IdentityLookupFact {
27
+ outcome: "timeout" | "rejected";
28
+ durationMs: number;
29
+ cold: boolean;
30
+ inFlight: number;
31
+ }
26
32
  /** Facts connecta derived about a failure where it saw it. */
27
33
  export interface FailureFacts {
34
+ identityLookup?: IdentityLookupFact;
28
35
  /** Inbound refusal classification attached to its Response, never its body. */
29
36
  reason?: AuthDenialReason;
30
37
  step?: FailureStep;
@@ -64,6 +71,7 @@ export interface FailureSubject {
64
71
  declare const recordBrand: unique symbol;
65
72
  /** One failure as an operator may read it. Only `failureRecord` makes one. */
66
73
  export interface FailureRecord {
74
+ readonly identityLookup?: IdentityLookupFact;
67
75
  readonly [recordBrand]: true;
68
76
  readonly reason?: AuthDenialReason;
69
77
  readonly accessContext?: boolean;
@@ -110,7 +118,7 @@ export declare function failureRecord(subject: FailureSubject, failure?: unknown
110
118
  * The events an operator log reports failures as. Closed: a new one is a
111
119
  * reviewed line here, never a string assembled at the call site.
112
120
  */
113
- export type FailureEvent = "Authentication admission refused" | "Cloudflare Access verification unavailable" | "operator_refused" | "Authentication released after its pending work became unreachable" | "negotiation cache read failed" | "negotiation cache write failed" | "discovery health read failed" | "discovery health write failed" | "pool tool grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "credential verification record failed" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "MCP agent endpoint request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "operator agent slots read failed" | "result paging unavailable" | "native catalog unavailable" | "native name collision" | "native tools/list refused a pool over the 1,000-tool native cap" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
121
+ export type FailureEvent = "Authentication admission refused" | "Cloudflare Access verification unavailable" | "operator_refused" | "Authentication released after its pending work became unreachable" | "negotiation cache read failed" | "negotiation cache write failed" | "discovery health read failed" | "discovery health write failed" | "pool tool grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog chunk cleanup failed" | "catalog fence failed" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "credential verification record failed" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "MCP agent endpoint request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "operator agent slots read failed" | "result paging unavailable" | "native catalog unavailable" | "native name collision" | "native tools/list refused a pool over the 1,000-tool native cap" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
114
122
  /**
115
123
  * Log one failure as `[connecta] <event>` and its record. A record
116
124
  * `failureRecord` did not build is replaced, whatever its shape, by a fixed
@@ -47,6 +47,7 @@ const SOURCES = new Set([
47
47
  "call_destructive_tool",
48
48
  "batch_call",
49
49
  "execute_code",
50
+ "execute_read_code",
50
51
  "resume_execution",
51
52
  ]);
52
53
  const MODES = new Set(["continue", "restart"]);
@@ -165,6 +166,20 @@ export function errorLabel(failure) {
165
166
  }
166
167
  return undefined;
167
168
  }
169
+ function identityLookupFact(value) {
170
+ if (!value ||
171
+ (value.outcome !== "timeout" && value.outcome !== "rejected") ||
172
+ count(value.durationMs, 86_400_000) === undefined ||
173
+ typeof value.cold !== "boolean" ||
174
+ count(value.inFlight, 1_000) === undefined)
175
+ return undefined;
176
+ return {
177
+ outcome: value.outcome,
178
+ durationMs: Math.round(value.durationMs),
179
+ cold: value.cold,
180
+ inFlight: Math.round(value.inFlight),
181
+ };
182
+ }
168
183
  function origin(value) {
169
184
  if (typeof value !== "string")
170
185
  return undefined;
@@ -190,6 +205,7 @@ function checkedFacts(facts) {
190
205
  return {
191
206
  ...defined("reason", member(facts.reason, AUTH_DENIAL_REASONS)),
192
207
  ...defined("step", member(facts.step, STEPS)),
208
+ ...defined("identityLookup", identityLookupFact(facts.identityLookup)),
193
209
  ...defined("origin", origin(facts.origin)),
194
210
  ...defined("httpStatus", httpStatus(facts.httpStatus)),
195
211
  ...defined("oauthError", member(facts.oauthError, OAUTH_ERROR_CODES)),