@zackbart/connecta 0.41.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 (148) hide show
  1. package/CHANGELOG.md +61 -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 +2 -1
  7. package/dist/activity.js +5 -5
  8. package/dist/advertised-schema.js +15 -0
  9. package/dist/agent-routes.js +31 -5
  10. package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
  11. package/dist/auth/downstream-oauth.js +3 -0
  12. package/dist/auth/static-oauth.js +26 -18
  13. package/dist/auth-elicitation.js +118 -61
  14. package/dist/authorization-handoff.js +36 -3
  15. package/dist/catalog-cache.js +292 -24
  16. package/dist/catalog-freshness.js +12 -0
  17. package/dist/catalog-service.js +373 -70
  18. package/dist/catalog.js +147 -58
  19. package/dist/config-defaults.d.ts +2 -1
  20. package/dist/config-defaults.js +4 -1
  21. package/dist/config.d.ts +43 -17
  22. package/dist/config.js +26 -10
  23. package/dist/connectors/remote-mcp/catalog.js +52 -28
  24. package/dist/connectors/remote-mcp/connection.js +62 -5
  25. package/dist/connectors/remote-mcp.js +10 -1
  26. package/dist/describe-config.d.ts +3 -2
  27. package/dist/discovery-health.js +26 -11
  28. package/dist/errors.d.ts +52 -3
  29. package/dist/errors.js +86 -3
  30. package/dist/execute.js +476 -200
  31. package/dist/executor-admission.js +2 -0
  32. package/dist/executor-contract.js +6 -0
  33. package/dist/executor-result.js +2 -2
  34. package/dist/executors/quickjs-child.js +13 -4
  35. package/dist/executors/quickjs-runtime.d.ts +2 -0
  36. package/dist/executors/quickjs-runtime.js +36 -19
  37. package/dist/executors/quickjs.js +23 -6
  38. package/dist/guest-functions.js +5 -4
  39. package/dist/guest-runtime.js +37 -1
  40. package/dist/guest-types.d.ts +18 -0
  41. package/dist/http/client.js +7 -2
  42. package/dist/http/errors.js +9 -4
  43. package/dist/http/fields.js +17 -5
  44. package/dist/http/hatch.js +9 -3
  45. package/dist/http/paging.js +1 -1
  46. package/dist/http/provider.js +2 -6
  47. package/dist/http/vendor.js +6 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +5 -0
  50. package/dist/invocation.js +98 -34
  51. package/dist/meta-output.js +24 -18
  52. package/dist/meta-tool-names.d.ts +1 -1
  53. package/dist/meta-tool-names.js +1 -1
  54. package/dist/meta-tools.js +259 -60
  55. package/dist/native-recovery.js +4 -1
  56. package/dist/native-tools.js +166 -51
  57. package/dist/node.js +3 -1
  58. package/dist/oauth-handoff.js +53 -43
  59. package/dist/operation-activity.js +2 -1
  60. package/dist/operation-vocabulary.js +6 -0
  61. package/dist/operator-record.d.ts +1 -1
  62. package/dist/operator-record.js +1 -0
  63. package/dist/operator-ui/generated.js +2 -2
  64. package/dist/program-result.js +2 -0
  65. package/dist/program-source.js +1 -1
  66. package/dist/program-writes.js +112 -19
  67. package/dist/provider.d.ts +0 -13
  68. package/dist/provider.js +0 -3
  69. package/dist/providers/_shared/google/kit.js +2 -2
  70. package/dist/providers/breeze/api.js +2 -2
  71. package/dist/providers/breeze/skill.generated.js +1 -1
  72. package/dist/providers/ccb/api.js +2 -2
  73. package/dist/providers/ccb/skill.generated.js +1 -1
  74. package/dist/providers/cloudflare/api.js +2 -2
  75. package/dist/providers/cloudflare/index.js +2 -0
  76. package/dist/providers/cloudflare/skill.generated.js +1 -1
  77. package/dist/providers/docs/skill.generated.js +1 -1
  78. package/dist/providers/drive/skill.generated.js +1 -1
  79. package/dist/providers/forms/index.js +1 -0
  80. package/dist/providers/forms/skill.generated.js +1 -1
  81. package/dist/providers/github/api.js +98 -6
  82. package/dist/providers/github/index.js +7 -2
  83. package/dist/providers/github/lib/auth.d.ts +3 -1
  84. package/dist/providers/github/lib/auth.js +43 -12
  85. package/dist/providers/github/lib/discovery.js +11 -1
  86. package/dist/providers/github/lib/operations.js +8 -0
  87. package/dist/providers/github/safety.js +39 -0
  88. package/dist/providers/github/skill.generated.js +1 -1
  89. package/dist/providers/github/tools.js +12 -5
  90. package/dist/providers/gmail/index.js +1 -0
  91. package/dist/providers/gmail/skill.generated.js +1 -1
  92. package/dist/providers/granola/skill.generated.js +1 -1
  93. package/dist/providers/overflow/index.js +2 -0
  94. package/dist/providers/overflow/skill.generated.js +1 -1
  95. package/dist/providers/planning-center/index.js +11 -5
  96. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  97. package/dist/providers/planning-center/skill.generated.js +1 -1
  98. package/dist/providers/quickbooks/index.js +2 -5
  99. package/dist/providers/quickbooks/skill.generated.js +1 -1
  100. package/dist/providers/sheets/index.js +1 -0
  101. package/dist/providers/sheets/skill.generated.js +1 -1
  102. package/dist/providers/slides/index.js +2 -2
  103. package/dist/providers/slides/skill.generated.js +1 -1
  104. package/dist/providers/stripe/skill.generated.js +1 -1
  105. package/dist/providers/tithely/index.js +1 -0
  106. package/dist/providers/tithely/skill.generated.js +1 -1
  107. package/dist/providers/vercel/api.js +2 -2
  108. package/dist/providers/vercel/skill.generated.js +1 -1
  109. package/dist/registry.d.ts +36 -4
  110. package/dist/registry.js +127 -27
  111. package/dist/result-preview.js +210 -0
  112. package/dist/result-seal.js +168 -0
  113. package/dist/route-names.js +13 -4
  114. package/dist/routes/connect.js +21 -13
  115. package/dist/routes/mcp.js +55 -26
  116. package/dist/routes/shared.d.ts +6 -0
  117. package/dist/runtime/call.js +15 -10
  118. package/dist/runtime/request-scope.js +36 -1
  119. package/dist/runtime/run.js +4 -1
  120. package/dist/sent-secrets.d.ts +20 -0
  121. package/dist/sent-secrets.js +49 -1
  122. package/dist/skills.js +100 -33
  123. package/dist/storage/keys.js +9 -6
  124. package/dist/storage/sql-native.js +1 -1
  125. package/dist/tool-safety.d.ts +33 -0
  126. package/dist/tool-safety.js +51 -15
  127. package/dist/types.d.ts +31 -0
  128. package/dist/usage-guide.js +14 -13
  129. package/dist/validate.js +29 -10
  130. package/dist/version.d.ts +1 -1
  131. package/dist/version.js +1 -1
  132. package/dist/worker.js +28 -8
  133. package/documentation/architecture.md +104 -42
  134. package/documentation/auth.md +46 -19
  135. package/documentation/code-mode.md +378 -116
  136. package/documentation/deploying.md +10 -9
  137. package/documentation/hosted-mcp-migration.md +9 -3
  138. package/documentation/integrating.md +27 -5
  139. package/documentation/meta-tools.md +655 -153
  140. package/documentation/operating.md +5 -3
  141. package/documentation/providers.md +18 -6
  142. package/examples/worker/README.md +3 -3
  143. package/guides/how-it-works.md +2 -1
  144. package/guides/safety-and-trust.md +1 -3
  145. package/package.json +1 -1
  146. package/templates/node/AGENTS.md +3 -3
  147. package/templates/node/README.md +1 -1
  148. package/templates/node/package.json +1 -1
@@ -3,7 +3,7 @@ import { RequestScope, requestOwner } from "./runtime/request-scope.js";
3
3
  import { upwardStep } from "./upward.js";
4
4
  import { authAwait, authCall, authInvoke, authFlow, authMethod, registerAuthMethod, authOwnMethod, } from "./auth/native.js";
5
5
  import { registryProgram } from "./runtime/registry.js";
6
- import { enableAuthorizationHandoffs } from "./authorization-handoff.js";
6
+ import { authorizationHandoff, connectLinkFields, enableAuthorizationHandoffs } from "./authorization-handoff.js";
7
7
  import { createRequestStateCodec, inputRequired, ProtocolError, INVALID_PARAMS, PROTOCOL_VERSION_META_KEY, CLIENT_CAPABILITIES_META_KEY, } from "@modelcontextprotocol/server";
8
8
  import { Effect } from "effect";
9
9
  import { runEdge, withDeadlineEffect } from "./runtime/run.js";
@@ -17,6 +17,23 @@ import { DownstreamElicitation, MAX_RELAY_STATE_CHARS, } from "./downstream-inpu
17
17
  import { canonicalState as canonical, requestDigest as digest, stateObject as object, invalidRequestState as invalidState, REQUEST_STATE_TTL_MS as TTL_MS, MAX_INPUT_ROUNDS as MAX_ROUNDS, } from "./request-state.js";
18
18
  const INPUT_KEY = "connecta_auth";
19
19
  const MESSAGE = "Connect this service in your browser, then retry the request.";
20
+ // A client may answer without showing anything, and the response cannot prove
21
+ // otherwise. Neither text claims a person saw the prompt, and neither names a
22
+ // route: an agent told to call again would only reach the same refusal.
23
+ const REFUSED = {
24
+ decline: {
25
+ code: "auth_declined",
26
+ message: "The client declined the connection prompt, possibly automatically, so the user may not have seen it. Nothing was retried.",
27
+ },
28
+ cancel: {
29
+ code: "auth_cancelled",
30
+ message: "The client cancelled the connection prompt, possibly automatically, so the user may not have seen it. Nothing was retried.",
31
+ },
32
+ };
33
+ // Only a repeat can recover a refusal whose link failed, and it asks for a new
34
+ // prompt, so a person's refusal stays final. Nothing on the server limits it to one.
35
+ const REFUSED_NO_LINK = " No connection link could be issued. If the user has not refused this connection, repeat this request once for a new link, then give it to them. Stop if that repeat also fails.";
36
+ const refusedPurpose = (recipient) => `Give this link to the user, unchanged, ${recipient}once, then stop. Do not prompt again, retry, or reach this service another way until they confirm. If they already refused this connection in this conversation, only mention that the link is available.`;
20
37
  function failure(code, message) {
21
38
  return toolError({ code, message, retryable: false }, { ok: false });
22
39
  }
@@ -45,14 +62,26 @@ export class AuthElicitation {
45
62
  context,
46
63
  ]));
47
64
  }
48
- browserProgress(state) {
49
- return runEdge(authOwnMethod(AuthElicitation.prototype, this, "browserProgress", [
50
- state,
51
- ]));
65
+ browserCompleted(state) {
66
+ return runEdge(authOwnMethod(AuthElicitation.prototype, this, "browserCompleted", [state]));
52
67
  }
53
68
  connected(id, context) {
54
69
  return runEdge(authOwnMethod(AuthElicitation.prototype, this, "connected", [id, context]));
55
70
  }
71
+ refused(action, state, force) {
72
+ return runEdge(authOwnMethod(AuthElicitation.prototype, this, "refused", [action, state, force]));
73
+ }
74
+ /** The one gate before any recovery link: this caller may connect this connector here. */
75
+ mayConnect(id) {
76
+ const { registry, principal, publicUrl, canManage, unavailable, credentialUi } = this.options;
77
+ const connector = typeof id === "string" ? registry.getConnector(id) : undefined;
78
+ return (typeof id === "string" &&
79
+ /^[a-z0-9_-]+$/.test(id) &&
80
+ Boolean(connector?.startAuth || (connector?.credential && credentialUi)) &&
81
+ Boolean(principal && publicUrl) &&
82
+ !unavailable &&
83
+ canManage(id));
84
+ }
56
85
  bind(context) {
57
86
  this.downstream.bind(context);
58
87
  }
@@ -114,12 +143,8 @@ export class AuthElicitation {
114
143
  !(yield* upwardStep(authInvoke(self.options, "claimRetry", [previous.connector, previous.browserNonces.at(-1)])))) {
115
144
  return failure("invalid_request_state", "Invalid or expired requestState");
116
145
  }
117
- if (previous && object(response)) {
118
- if (response.action === "decline")
119
- return failure("auth_declined", "Connection was declined. The request was not retried.");
120
- if (response.action === "cancel")
121
- return failure("auth_cancelled", "Connection was cancelled. The request was not retried.");
122
- }
146
+ if (previous && object(response) && (response.action === "decline" || response.action === "cancel"))
147
+ return yield* upwardStep(authInvoke(self, "refused", [response.action, previous, args.force]));
123
148
  if (previous?.reads.length) {
124
149
  const catalog = new CatalogService(self.options.registry, self.options.publicUrl, {
125
150
  requestScope: self.options.requestScope,
@@ -156,18 +181,12 @@ export class AuthElicitation {
156
181
  }
157
182
  bindReplayReads(self.options.requestScope, previous.reads);
158
183
  }
159
- const browser = previous && tool === "authorize_connector"
160
- ? yield* upwardStep(authInvoke(self, "browserProgress", [previous]))
161
- : undefined;
162
- if (browser?.completed && previous) {
163
- // Retire unspent links before the status read. A browser claim racing
164
- // these CASes either wins and is observed as pending, or is refused.
165
- yield* upwardStep(authInvoke(self.options, "closeLinks", [previous.connector, previous.browserNonces]));
166
- if ((yield* upwardStep(authInvoke(self, "browserProgress", [previous]))).completed &&
167
- (yield* upwardStep(authInvoke(self, "connected", [previous.connector, context])))) {
168
- const structuredContent = { connector: previous.connector, status: "ok" };
169
- return { structuredContent, content: [{ type: "text", text: JSON.stringify(structuredContent) }] };
170
- }
184
+ if (previous &&
185
+ tool === "authorize_connector" &&
186
+ (yield* upwardStep(authInvoke(self, "browserCompleted", [previous]))) &&
187
+ (yield* upwardStep(authInvoke(self, "connected", [previous.connector, context])))) {
188
+ const structuredContent = { connector: previous.connector, status: "ok" };
189
+ return { structuredContent, content: [{ type: "text", text: JSON.stringify(structuredContent) }] };
171
190
  }
172
191
  const envelope = context.mcpReq.envelope;
173
192
  const capabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY];
@@ -216,16 +235,8 @@ export class AuthElicitation {
216
235
  content: [{ type: "text", text: JSON.stringify(structuredContent) }],
217
236
  };
218
237
  }
219
- const { registry, vault, principal, publicUrl, canManage, unavailable } = self.options;
220
- const connector = typeof id === "string" ? registry.getConnector(id) : undefined;
221
- const recoverable = connector?.startAuth || (connector?.credential && self.options.credentialUi);
222
- if (typeof id !== "string" ||
223
- !/^[a-z0-9_-]+$/.test(id) ||
224
- !recoverable ||
225
- !principal ||
226
- !publicUrl ||
227
- unavailable ||
228
- !canManage(id))
238
+ const { vault, principal } = self.options;
239
+ if (!self.mayConnect(id) || !principal)
229
240
  return result;
230
241
  // Incapable hosts already received the shared explicit-authorization handoff.
231
242
  if (!capable)
@@ -237,9 +248,9 @@ export class AuthElicitation {
237
248
  ...result.structuredContent,
238
249
  error: {
239
250
  ...result.structuredContent?.error,
240
- authorizationUrl: error.authorizationUrl ??
251
+ ...connectLinkFields(error, id, error.authorizationUrl ??
241
252
  (yield* upwardStep(authInvoke(self.options, "connectLink", [id])))
242
- .url,
253
+ .url),
243
254
  },
244
255
  };
245
256
  return {
@@ -250,13 +261,13 @@ export class AuthElicitation {
250
261
  }
251
262
  if (previous && previous.round >= MAX_ROUNDS)
252
263
  return failure("auth_round_limit", "Connection is still required after three prompts. Connect explicitly, then start a new request.");
253
- // Once a verified browser has started this attempt, retries continue it.
254
- // A pending consent must never be reset by another force=true prompt.
264
+ // Accept is consent to open the link, not proof it opened, so every round's
265
+ // link stays live and they share one reset (claimOAuthConnectStart).
255
266
  const forced = explicit && args.force === true;
256
267
  const link = yield* upwardStep(authInvoke(self.options, "connectLink", [
257
268
  id,
258
- forced && !browser?.predecessor,
259
- forced ? browser?.predecessor : undefined,
269
+ forced,
270
+ forced ? previous?.browserNonces[0] : undefined,
260
271
  ]));
261
272
  const state = {
262
273
  version: 1,
@@ -285,7 +296,7 @@ export class AuthElicitation {
285
296
  ...result.structuredContent,
286
297
  error: {
287
298
  ...result.structuredContent?.error,
288
- authorizationUrl: link.url,
299
+ ...connectLinkFields(result.structuredContent?.error, id, link.url),
289
300
  },
290
301
  };
291
302
  return {
@@ -305,6 +316,59 @@ export class AuthElicitation {
305
316
  });
306
317
  }), this.options.requestSignal).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
307
318
  }, AuthElicitation.prototype.run);
319
+ registerAuthMethod(AuthElicitation.prototype, "refused", function (action, state, force) {
320
+ const self = this;
321
+ return authAwait(authFlow(function* () {
322
+ const { code, message } = REFUSED[action];
323
+ const { connector: id, tool } = state;
324
+ if (!self.mayConnect(id))
325
+ return failure(code, message);
326
+ const { registry, publicUrl, canManage, unavailable, credentialHandoffUrl, routes, requestScope } = self.options;
327
+ try {
328
+ // The signed state bound these arguments, so this is the restart the
329
+ // caller asked for, sharing the prompt's reset if that prompt was opened.
330
+ const forced = tool === "authorize_connector" && force === true;
331
+ // The ordinary handoff, except that the link is the sealed form this
332
+ // client was already shown: the agent sees no principal in it.
333
+ const handoff = yield* upwardStep(authCall(authorizationHandoff, [
334
+ registry,
335
+ publicUrl,
336
+ requestScope,
337
+ {
338
+ canManageAuth: canManage,
339
+ credentialHandoffUrl,
340
+ oauthConnectUnavailable: unavailable,
341
+ routes,
342
+ oauthConnectUrl: authMethod(() => authInvoke(self.options, "connectLink", [
343
+ id,
344
+ forced,
345
+ forced ? state.browserNonces[0] : undefined,
346
+ ]).pipe(Effect.map((link) => link.url))),
347
+ },
348
+ { connector: id },
349
+ ]));
350
+ const url = handoff.authorizationUrl ?? handoff.operatorUrl;
351
+ if (!url)
352
+ return failure(code, message);
353
+ if (handoff.authorizationUrl)
354
+ sentSecretsForRequest(requestScope).mintedState(handoff.authorizationUrl);
355
+ return toolError({
356
+ ...handoff,
357
+ code,
358
+ message,
359
+ retryable: false,
360
+ nextAction: {
361
+ url,
362
+ purpose: refusedPurpose(handoff.authorizationUrl ? "" : "for the operator to open, "),
363
+ },
364
+ }, { ok: false });
365
+ }
366
+ catch {
367
+ // A signing or storage failure cannot replace the refusal itself.
368
+ return failure(code, message + REFUSED_NO_LINK);
369
+ }
370
+ }), this.options.requestSignal).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
371
+ }, AuthElicitation.prototype.refused);
308
372
  registerAuthMethod(AuthElicitation.prototype, "connected", function (id, context) {
309
373
  const self = this;
310
374
  return authAwait(authFlow(function* () {
@@ -331,29 +395,16 @@ export class AuthElicitation {
331
395
  }
332
396
  }), this.options.requestSignal).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
333
397
  }, AuthElicitation.prototype.connected);
334
- registerAuthMethod(AuthElicitation.prototype, "browserProgress", function (state) {
398
+ registerAuthMethod(AuthElicitation.prototype, "browserCompleted", function (state) {
335
399
  const self = this;
336
400
  return authAwait(authFlow(function* () {
337
- let started;
338
- let claimed;
339
- let completed = false;
340
- let pending = false;
341
- for (const nonce of state.browserNonces) {
342
- const progress = yield* upwardStep(authInvoke(self.options, "linkProgress", [
343
- state.connector,
344
- nonce,
345
- ]));
346
- if (progress === "started")
347
- started = nonce;
348
- if (progress === "claimed")
349
- claimed = nonce;
350
- completed ||= progress === "started";
351
- pending ||= progress === "claimed";
352
- }
353
- const predecessor = started ?? claimed;
354
- return { ...(predecessor ? { predecessor } : {}), completed: completed && !pending };
401
+ // A reset never leaves "started", so no reset can be running behind this link.
402
+ for (const nonce of state.browserNonces)
403
+ if (yield* upwardStep(authInvoke(self.options, "linkStarted", [state.connector, nonce])))
404
+ return true;
405
+ return false;
355
406
  }), this.options.requestSignal).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
356
- }, AuthElicitation.prototype.browserProgress);
407
+ }, AuthElicitation.prototype.browserCompleted);
357
408
  registerAuthMethod(AuthElicitation.prototype, "verify", function (wire, context) {
358
409
  const self = this;
359
410
  return authAwait(authFlow(function* () {
@@ -380,7 +431,13 @@ export class AuthElicitation {
380
431
  !Number.isInteger(state.round) ||
381
432
  state.round < 1 ||
382
433
  state.round > MAX_ROUNDS ||
383
- !["call_tool", "call_destructive_tool", "execute_code", "authorize_connector"].includes(String(state.tool)) ||
434
+ ![
435
+ "call_tool",
436
+ "call_destructive_tool",
437
+ "execute_code",
438
+ "execute_read_code",
439
+ "authorize_connector",
440
+ ].includes(String(state.tool)) ||
384
441
  typeof state.wireTool !== "string" ||
385
442
  typeof state.digest !== "string" ||
386
443
  !/^[a-f0-9]{64}$/.test(state.digest) ||
@@ -1,4 +1,4 @@
1
- import { routePhrases } from "./agent-routes.js";
1
+ import { handoffInHand, routePhrases } from "./agent-routes.js";
2
2
  import { upwardStep } from "./upward.js";
3
3
  import { authCall, authInvoke, authFlow, registerAuth } from "./auth/native.js";
4
4
  import { Context, Deferred, Effect, Scheduler } from "effect";
@@ -15,6 +15,38 @@ function outcomeAware(build) {
15
15
  afterUnknownWrite.set(handoff, build(true));
16
16
  return handoff;
17
17
  }
18
+ /**
19
+ * What to do with a handoff the error already carries. Passing it on is the
20
+ * whole step: calling authorize_connector would only return the same thing.
21
+ * After a write of unknown outcome the follow-up reconciles, never retries.
22
+ */
23
+ function handoffAction(handoff, unknownWrite) {
24
+ const then = unknownWrite
25
+ ? "After they confirm, re-read the write's target before any fresh call."
26
+ : "Retry after they confirm.";
27
+ const url = handoff.authorizationUrl ?? handoff.operatorUrl;
28
+ if (!url)
29
+ return {
30
+ action: "contact_operator",
31
+ purpose: `Tell the user the operator must fix this connector, then stop. ${then}`,
32
+ };
33
+ return {
34
+ url,
35
+ purpose: `Give this link to the user, unchanged, ${handoff.authorizationUrl ? "" : "for the operator to open, "}then stop. ${then}`,
36
+ };
37
+ }
38
+ const unknownWriteError = (error) => error.writeOutcome === "unknown" || error.reconciliationRequired === true;
39
+ /**
40
+ * The fields for an auth error given only a connect link, where a capable host's
41
+ * prompt could not carry it: the same direct step as a full handoff.
42
+ */
43
+ export function connectLinkFields(error, connector, authorizationUrl) {
44
+ return {
45
+ ...(typeof error.message === "string" ? { message: handoffInHand(error.message, connector) } : {}),
46
+ authorizationUrl,
47
+ nextAction: handoffAction({ connector, recovery: "oauth", authorizationUrl }, unknownWriteError(error)),
48
+ };
49
+ }
18
50
  function oauthFollowUp(connectorId, unknownWrite, opts) {
19
51
  const then = `Then ${unknownWrite ? RECONCILE : "retry the original call"}`;
20
52
  // Without programs, no connecta.search can confirm the catalog.
@@ -95,7 +127,7 @@ const authorizationHandoffNative = /* @__PURE__ */ registerAuth(authorizationHan
95
127
  recovery: "oauth",
96
128
  status: "auth_required",
97
129
  authorizationUrl,
98
- instructions: "Copy authorizationUrl exactly as returned; do not decode or re-encode it. Open it in a browser and sign in as the user who requested this connection. Connecta verifies your identity and permission before starting consent. " +
130
+ instructions: "For the person opening this link: sign in as the user who requested this connection; Connecta verifies identity and permission before starting consent. For the agent: pass authorizationUrl on exactly as returned. " +
99
131
  oauthFollowUp(connector.id, unknownWrite, opts),
100
132
  }));
101
133
  });
@@ -180,7 +212,8 @@ export function withAuthorizationHandoffEffect(scope, error) {
180
212
  return {
181
213
  ...error,
182
214
  ...value,
183
- message: error.message,
215
+ message: handoffInHand(error.message, connector),
216
+ nextAction: handoffAction(value, unknownWriteError(error)),
184
217
  ...(value.message ? { instructions: value.message } : {}),
185
218
  };
186
219
  }),