@agent-native/core 0.223.0-nightly-20261102152511 → 0.223.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 (40) hide show
  1. package/dist/agent/actions/get-agentkit-capabilities.js +2 -2
  2. package/dist/agent/engine/error-detail.d.ts +13 -0
  3. package/dist/agent/engine/error-detail.js +92 -0
  4. package/dist/agent/production-agent.js +3 -5
  5. package/dist/agent/run-failure-retry.d.ts +7 -0
  6. package/dist/agent/run-failure-retry.js +21 -0
  7. package/dist/agent/thread-data-builder.js +2 -3
  8. package/dist/agentkit/capabilities.d.ts +1 -1
  9. package/dist/cli/templates-meta.js +0 -14
  10. package/dist/client/agent-run-status.d.ts +2 -0
  11. package/dist/client/agent-run-status.js +3 -0
  12. package/dist/client/error-format.js +2 -6
  13. package/dist/client/sse-event-processor.js +2 -75
  14. package/dist/collab/routes.d.ts +1 -1
  15. package/dist/collab/struct-routes.d.ts +1 -1
  16. package/dist/connections/catalog.d.ts +5 -5
  17. package/dist/connections/catalog.js +3 -22
  18. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  19. package/dist/file-upload/builder.js +4 -5
  20. package/dist/mcp/analytics.js +7 -0
  21. package/dist/mcp/build-server.js +7 -2
  22. package/dist/mcp/direct-action-call.js +3 -1
  23. package/dist/mcp/embed-redaction.d.ts +3 -0
  24. package/dist/mcp/embed-redaction.js +5 -0
  25. package/dist/mcp/execute-action-call.js +6 -2
  26. package/dist/org/index.d.ts +1 -1
  27. package/dist/progress/routes.d.ts +1 -1
  28. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  29. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  30. package/dist/provider-api/index.js +2 -9
  31. package/dist/server/agent-chat-plugin.js +7 -0
  32. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  33. package/dist/server/first-touch-route.d.ts +1 -1
  34. package/dist/server/transcribe-voice.d.ts +1 -1
  35. package/dist/vite/bounded-task-plugin.d.ts +12 -2
  36. package/dist/vite/bounded-task-plugin.js +34 -26
  37. package/dist/vite/client.js +162 -106
  38. package/dist/vite/plugin-hook.test-helpers.d.ts +10 -0
  39. package/dist/vite/plugin-hook.test-helpers.js +19 -0
  40. package/package.json +4 -4
@@ -10,8 +10,8 @@ export default defineAction({
10
10
  toolCallable: false,
11
11
  run: async (_args, ctx) => {
12
12
  const appId = ctx?.appId;
13
- if (appId !== "design" && appId !== "slides" && appId !== "slides-2") {
14
- fail("AgentKit capabilities are only available in Design, Slides, and Slides 2.", {
13
+ if (appId !== "design" && appId !== "slides") {
14
+ fail("AgentKit capabilities are only available in Design and Slides.", {
15
15
  errorCode: "agentkit_capabilities_unsupported_app",
16
16
  statusCode: 400,
17
17
  });
@@ -6,6 +6,19 @@ export declare const BUILDER_GATEWAY_INTERNAL_ERROR_CODE = "builder_gateway_inte
6
6
  export declare const PROVIDER_TRANSIENT_REJECTION_ERROR_CODE = "provider_transient_rejection";
7
7
  export declare const PROVIDER_RATE_LIMITED_ERROR_CODE = "provider_rate_limited";
8
8
  export declare function isCreditsLimitErrorCode(errorCode?: string): boolean;
9
+ /**
10
+ * The provider refused the request itself (a tool schema, an image source), so
11
+ * the model never ran and sending the same request again fails the same way.
12
+ */
13
+ export declare function isRejectedRequestErrorCode(errorCode?: string): boolean;
14
+ /**
15
+ * Codes after which sending the same request again cannot help: the request,
16
+ * credential, or model selection is at fault, the server already spent its own
17
+ * recovery path, or the run's outcome is unknown and a replay could duplicate
18
+ * side effects. The SSE auto-continue gate and the run-status retry verdict
19
+ * both read this, so the two cannot drift.
20
+ */
21
+ export declare function isTerminalRecoveryErrorCode(errorCode?: string): boolean;
9
22
  export declare function isBareProviderRejectionMessage(message: string): boolean;
10
23
  export declare function isBuilderGatewayInternalErrorMessage(message: string): boolean;
11
24
  export declare function isGenericInternalServerErrorMessage(message: string): boolean;
@@ -60,6 +60,98 @@ export function isCreditsLimitErrorCode(errorCode) {
60
60
  const code = errorCode?.trim().toLowerCase();
61
61
  return code === "http_402" || code?.startsWith("credits-limit") === true;
62
62
  }
63
+ /**
64
+ * The provider refused the request itself (a tool schema, an image source), so
65
+ * the model never ran and sending the same request again fails the same way.
66
+ */
67
+ export function isRejectedRequestErrorCode(errorCode) {
68
+ const code = errorCode?.trim().toLowerCase();
69
+ return code === "invalid_request" || code === "invalid_request_error";
70
+ }
71
+ /**
72
+ * Codes after which sending the same request again cannot help: the request,
73
+ * credential, or model selection is at fault, the server already spent its own
74
+ * recovery path, or the run's outcome is unknown and a replay could duplicate
75
+ * side effects. The SSE auto-continue gate and the run-status retry verdict
76
+ * both read this, so the two cannot drift.
77
+ */
78
+ export function isTerminalRecoveryErrorCode(errorCode) {
79
+ const code = errorCode?.trim().toLowerCase() ?? "";
80
+ return (code === "context_length_exceeded" ||
81
+ code === "input_too_long" ||
82
+ isCreditsLimitErrorCode(code) ||
83
+ code === "billing_error" ||
84
+ code === "unauthorized" ||
85
+ code === "authentication_error" ||
86
+ code === "permission_error" ||
87
+ code === "builder_auth_error" ||
88
+ // The account cannot use this model; another POST picks the same one.
89
+ code === "builder_model_unauthorized" ||
90
+ code === "http_401" ||
91
+ code === "http_403" ||
92
+ code === "rate_limit_exceeded" ||
93
+ code === "gateway_not_enabled" ||
94
+ code === "missing_api_key" ||
95
+ code === "missing_credentials" ||
96
+ isRejectedRequestErrorCode(code) ||
97
+ code === "request_too_large" ||
98
+ code === "not_found_error" ||
99
+ code === "model_not_found" ||
100
+ code === "invalid_attachment" ||
101
+ // The server now owns rate-limit recovery end to end (in-loop retries,
102
+ // sibling-model fallback, one cooled continuation, then a terminal
103
+ // `provider_rate_limited`) and caps the continuation chain it hands back
104
+ // to the client. Auto-recovering a bare `http_429`/`http_529` here would
105
+ // let an exhausted rate-limit error re-POST as a client continuation,
106
+ // bypassing that one-hop cap and restarting the retry/fallback budget
107
+ // the server just spent. Render with the manual Retry affordance like
108
+ // `provider_rate_limited` below, not auto-continued.
109
+ code === "http_429" ||
110
+ code === "http_529" ||
111
+ // The gateway's own throttle codes, same reasoning.
112
+ code === "rate_limited" ||
113
+ code === "too_many_concurrent_requests" ||
114
+ code === "provider_rate_limited" ||
115
+ // The server already retried the bare-403 load-shedding signature before
116
+ // this reached the client; another automatic POST would just hammer the
117
+ // same throttle. Renders with the manual Retry affordance like
118
+ // `provider_rate_limited` above, not auto-continued.
119
+ code === PROVIDER_TRANSIENT_REJECTION_ERROR_CODE ||
120
+ // `builder_gateway_error` is the no-detail fallback the Builder engine
121
+ // emits when the gateway returns `{type:"stop",reason:"error"}` with no
122
+ // explanation — almost always the upstream provider giving up (model
123
+ // quota hit, account misconfiguration, opaque downstream failure). The
124
+ // production-agent already retries this synchronously up to MAX_RETRIES
125
+ // before the error escapes to the SSE stream, so by the time the client
126
+ // sees it, retrying again with another POST /agent-chat will hit the
127
+ // same wall. This used to send the chat into a 32-continuation runaway
128
+ // (each turn cleared+regenerated visible content) for users hitting a
129
+ // misbehaving Builder route. Surface the error instead.
130
+ code === "builder_gateway_error" ||
131
+ // The hosted run exhausted its in-invocation continuation budget without
132
+ // finishing (run-loop-with-resume.ts). It's flagged `recoverable: true` so
133
+ // the recovery banner reads "stopped before finishing", but it must NOT
134
+ // auto-continue: another POST would hit the same ~40s wall and churn. The
135
+ // user retries deliberately (ideally as a single bulk action).
136
+ code === "run_budget_exhausted" ||
137
+ // `aborted_<reason>` is emitted by `terminalEventForAbortReason` for every
138
+ // abort that is neither a user stop nor a continuation boundary — a Slack
139
+ // cancel, a stuck-banner auto-retry, an operator kill. Whoever aborted the
140
+ // run owns the retry; auto-continuing here restarts work someone just
141
+ // stopped (and double-fires alongside the stuck banner's own retry). They
142
+ // stay `recoverable: true` so the banner still reads "stopped before
143
+ // finishing".
144
+ code.startsWith("aborted_") ||
145
+ // The run's outcome is genuinely UNKNOWN: its row is gone, or it left
146
+ // 'running' in a state the server has no terminal event for. Both stay
147
+ // `recoverable: true` so the banner offers a manual Retry, but an
148
+ // automatic re-POST would assert the turn did not finish — and it may
149
+ // well have, side effects included. Replaying it would duplicate them.
150
+ // The user decides, which is exactly what these errors say to do.
151
+ code === "run_record_missing" ||
152
+ code === "unknown_run_status" ||
153
+ code === "run_terminal_lookup_failed");
154
+ }
63
155
  export function isBareProviderRejectionMessage(message) {
64
156
  const trimmed = message.trim();
65
157
  return (trimmed === "" ||
@@ -78,7 +78,7 @@ import { applyContextXrayTransformForIteration } from "./engine/context-directiv
78
78
  import { attemptContinuationDispatch } from "./engine/continuation-dispatch-retry.js";
79
79
  import { formatLlmCredentialErrorMessage, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
80
80
  import { getEngineCredentialOwner } from "./engine/credential-owner.js";
81
- import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, isContextOverflowCode, isContextOverflowMessage, isInvalidAttachmentProviderMessage, isProviderConnectionErrorMessage, PROVIDER_RATE_LIMITED_ERROR_CODE, PROVIDER_TRANSIENT_REJECTION_ERROR_CODE, } from "./engine/error-detail.js";
81
+ import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, isContextOverflowCode, isContextOverflowMessage, isInvalidAttachmentProviderMessage, isProviderConnectionErrorMessage, isRejectedRequestErrorCode, PROVIDER_RATE_LIMITED_ERROR_CODE, PROVIDER_TRANSIENT_REJECTION_ERROR_CODE, } from "./engine/error-detail.js";
82
82
  import { resolveEngine, explicitEngineName, registerBuiltinEngines, getStoredModelForEngine, normalizeModelForEngine, isResolvedEngineUsableForRequest, } from "./engine/index.js";
83
83
  import { limitProviderTools, MAX_PROVIDER_TOOLS, } from "./engine/limit-provider-tools.js";
84
84
  import { resolveEmptyResponseRetryMaxOutputTokens, resolveMainChatMaxOutputTokens, resolveMaxOutputTokensForEngine, } from "./engine/output-tokens.js";
@@ -3065,8 +3065,7 @@ export function isResumableEngineError(err) {
3065
3065
  return false;
3066
3066
  const code = err instanceof EngineError ? (err.errorCode ?? "").toLowerCase() : "";
3067
3067
  if ((err instanceof EngineError && err.providerRetryable === false) ||
3068
- code === "invalid_request" ||
3069
- code === "invalid_request_error") {
3068
+ isRejectedRequestErrorCode(code)) {
3070
3069
  return false;
3071
3070
  }
3072
3071
  if (code === "builder_gateway_timeout" ||
@@ -8571,8 +8570,7 @@ export function isRecoverableContinuationError(event) {
8571
8570
  if (event.providerRetryable === false ||
8572
8571
  event.recoverable === false ||
8573
8572
  code === "builder_gateway_error" ||
8574
- code === "invalid_request" ||
8575
- code === "invalid_request_error") {
8573
+ isRejectedRequestErrorCode(code)) {
8576
8574
  return false;
8577
8575
  }
8578
8576
  return (event.recoverable === true ||
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether resending the same prompt can succeed after an errored run, judged
3
+ * from its stored `terminal_reason` (`error:<code>`). Clients use it to decide
4
+ * on an automatic retry; a manual retry stays available either way, since the
5
+ * user may change the model or the prompt first.
6
+ */
7
+ export declare function isRunFailureRetryable(terminalReason: string): boolean;
@@ -0,0 +1,21 @@
1
+ import { isLlmCredentialError } from "./engine/credential-errors.js";
2
+ import { isTerminalRecoveryErrorCode } from "./engine/error-detail.js";
3
+ /**
4
+ * Whether resending the same prompt can succeed after an errored run, judged
5
+ * from its stored `terminal_reason` (`error:<code>`). Clients use it to decide
6
+ * on an automatic retry; a manual retry stays available either way, since the
7
+ * user may change the model or the prompt first.
8
+ */
9
+ export function isRunFailureRetryable(terminalReason) {
10
+ const code = (terminalReason.startsWith("error:")
11
+ ? terminalReason.slice("error:".length)
12
+ : terminalReason)
13
+ .trim()
14
+ .toLowerCase();
15
+ // The gateway's masked "internal error" stays retryable on purpose: it is
16
+ // sometimes transient, and a missed retry costs more than an extra one.
17
+ return (!isTerminalRecoveryErrorCode(code) &&
18
+ // Covers the bare `missing_api_key` reason, which has no `error:` prefix.
19
+ code !== "missing_api_key" &&
20
+ !isLlmCredentialError(undefined, code));
21
+ }
@@ -7,7 +7,7 @@ import { parseBase64DataUrl } from "../shared/data-url.js";
7
7
  import { toolInputFingerprint, toolInputsMatch, } from "../shared/tool-inputs.js";
8
8
  import { approvalKeyForHistory, approvalKeyForPrivateHistory, } from "./approval-key.js";
9
9
  import { isAgentInternalContinuationPrompt } from "./continuation-prompt.js";
10
- import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE } from "./engine/error-detail.js";
10
+ import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, isRejectedRequestErrorCode, } from "./engine/error-detail.js";
11
11
  import { INTERRUPTED_TOOL_RESULT_MARKER as INTERRUPTED_TOOL_RESULT, stringifyToolUseInputForGateway, } from "./engine/translate-anthropic.js";
12
12
  import { parseFollowUpSuggestions } from "./follow-up-suggestions.js";
13
13
  import { isContinuationTerminalReason, isUnknownWriteCause } from "./types.js";
@@ -107,8 +107,7 @@ function isInternalContinuationError(event) {
107
107
  if (event.providerRetryable === false ||
108
108
  event.recoverable === false ||
109
109
  code === "builder_gateway_error" ||
110
- code === "invalid_request" ||
111
- code === "invalid_request_error") {
110
+ isRejectedRequestErrorCode(code)) {
112
111
  return false;
113
112
  }
114
113
  return (event.recoverable === true ||
@@ -1,5 +1,5 @@
1
1
  import type { CredentialContext } from "../credentials/index.js";
2
- export type AgentKitCapabilityAppId = "design" | "slides" | "slides-2";
2
+ export type AgentKitCapabilityAppId = "design" | "slides";
3
3
  export interface AgentKitIntegrationCapability {
4
4
  id: string;
5
5
  label: string;
@@ -50,20 +50,6 @@ export const TEMPLATES = [
50
50
  requiredPackages: ["creative-context"],
51
51
  core: true,
52
52
  },
53
- {
54
- name: "slides-2",
55
- label: "Slides 2",
56
- hint: "Agent-Native presentation editor for generating and editing React presentations",
57
- icon: "GalleryHorizontal",
58
- color: "#EC4899",
59
- colorRgb: "236 72 153",
60
- devPort: 8087,
61
- prodUrl: "https://slides-2.agent-native.com",
62
- defaultMode: "dev",
63
- hidden: true,
64
- requiredPackages: ["creative-context"],
65
- core: false,
66
- },
67
53
  {
68
54
  name: "analytics",
69
55
  label: "Analytics",
@@ -7,6 +7,8 @@ export type AgentRunStatusRead = {
7
7
  terminalReason?: string;
8
8
  /** A truncated run whose continuation the server has not started yet. */
9
9
  awaitingRedispatch?: boolean;
10
+ /** False when resending the same prompt would fail the same way. */
11
+ retryable?: boolean;
10
12
  } | {
11
13
  ok: false;
12
14
  };
@@ -41,6 +41,9 @@ export async function readAgentRunStatus(input) {
41
41
  ...(state.awaitingRedispatch === true
42
42
  ? { awaitingRedispatch: true }
43
43
  : {}),
44
+ ...(typeof state.retryable === "boolean"
45
+ ? { retryable: state.retryable }
46
+ : {}),
44
47
  };
45
48
  }
46
49
  catch {
@@ -1,6 +1,6 @@
1
1
  import { GATEWAY_UNAVAILABLE_VISITOR_MESSAGE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
2
2
  import { AMBIGUOUS_HTTP_CREDENTIAL_CODES, credentialStateForErrorCode, } from "../agent/engine/credential-state.js";
3
- import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, isBuilderGatewayInternalErrorMessage, isContextOverflowMessage, PROVIDER_TRANSIENT_REJECTION_ERROR_CODE, } from "../agent/engine/error-detail.js";
3
+ import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, isBuilderGatewayInternalErrorMessage, isContextOverflowMessage, isRejectedRequestErrorCode, PROVIDER_TRANSIENT_REJECTION_ERROR_CODE, } from "../agent/engine/error-detail.js";
4
4
  import { RUN_FAILED_MESSAGE, RUN_INTERRUPTED_MESSAGE, RUN_SIGNED_OUT_MESSAGE, RUN_UNVERIFIED_MESSAGE, } from "./chat/run-outcome.js";
5
5
  export { isCreditsLimitErrorCode } from "../agent/engine/error-detail.js";
6
6
  export const BUILDER_SPACE_SETTINGS_URL = "https://builder.io/account/space?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=space_settings";
@@ -58,10 +58,6 @@ const MALFORMED_REQUEST_MESSAGE = "The model provider rejected this request as m
58
58
  export const CHAT_REQUEST_TOO_LARGE_MESSAGE = "This request exceeded the server's size limit (HTTP 413). Start a new chat or remove large attachments or references, then retry.";
59
59
  export const CHAT_CAPABILITY_UNAVAILABLE_MESSAGE = "We couldn't send your message right now. Please try again shortly.";
60
60
  export const CHAT_CAPABILITY_UNSUPPORTED_MESSAGE = "This app can't safely retry your saved prompt yet. Update the app, then try again.";
61
- const MALFORMED_REQUEST_CODES = new Set([
62
- "invalid_request",
63
- "invalid_request_error",
64
- ]);
65
61
  const ATTACHMENT_REJECTION_PATTERN = /\b(?:file_url|image_url|file_data|input_file|media_type|mime\s?type|image\.source|document\s+block)\b/i;
66
62
  function isSafeUpgradeUrl(url) {
67
63
  try {
@@ -417,7 +413,7 @@ export function normalizeChatError(errorMessage, errorCode) {
417
413
  details: text,
418
414
  };
419
415
  }
420
- if (MALFORMED_REQUEST_CODES.has(code) && !isContextOverflowMessage(text)) {
416
+ if (isRejectedRequestErrorCode(code) && !isContextOverflowMessage(text)) {
421
417
  return {
422
418
  message: ATTACHMENT_REJECTION_PATTERN.test(text)
423
419
  ? MALFORMED_REQUEST_ATTACHMENT_MESSAGE
@@ -1,5 +1,5 @@
1
1
  import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
2
- import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, PROVIDER_TRANSIENT_REJECTION_ERROR_CODE, } from "../agent/engine/error-detail.js";
2
+ import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, isTerminalRecoveryErrorCode, } from "../agent/engine/error-detail.js";
3
3
  import { normalizeConnectRequiredResult } from "../shared/connect-required.js";
4
4
  import { isNonProgressEventType } from "../shared/progress-events.js";
5
5
  import { toolInputsMatch } from "../shared/tool-inputs.js";
@@ -565,80 +565,7 @@ function isAutoRecoverableError(ev, errMsg) {
565
565
  msg.includes("stopped before it finished")) {
566
566
  return false;
567
567
  }
568
- if (code === "context_length_exceeded" ||
569
- code === "input_too_long" ||
570
- code.startsWith("credits-limit") ||
571
- code === "billing_error" ||
572
- code === "unauthorized" ||
573
- code === "authentication_error" ||
574
- code === "permission_error" ||
575
- code === "builder_auth_error" ||
576
- // The account cannot use this model; another POST picks the same one.
577
- code === "builder_model_unauthorized" ||
578
- code === "http_401" ||
579
- code === "http_403" ||
580
- code === "rate_limit_exceeded" ||
581
- code === "gateway_not_enabled" ||
582
- code === "missing_api_key" ||
583
- code === "missing_credentials" ||
584
- code === "invalid_request" ||
585
- code === "invalid_request_error" ||
586
- code === "request_too_large" ||
587
- code === "not_found_error" ||
588
- code === "model_not_found" ||
589
- // The server now owns rate-limit recovery end to end (in-loop retries,
590
- // sibling-model fallback, one cooled continuation, then a terminal
591
- // `provider_rate_limited`) and caps the continuation chain it hands back
592
- // to the client. Auto-recovering a bare `http_429`/`http_529` here would
593
- // let an exhausted rate-limit error re-POST as a client continuation,
594
- // bypassing that one-hop cap and restarting the retry/fallback budget
595
- // the server just spent. Render with the manual Retry affordance like
596
- // `provider_rate_limited` below, not auto-continued.
597
- code === "http_429" ||
598
- code === "http_529" ||
599
- // The gateway's own throttle codes, same reasoning.
600
- code === "rate_limited" ||
601
- code === "too_many_concurrent_requests" ||
602
- code === "provider_rate_limited" ||
603
- // The server already retried the bare-403 load-shedding signature before
604
- // this reached the client; another automatic POST would just hammer the
605
- // same throttle. Renders with the manual Retry affordance like
606
- // `provider_rate_limited` above, not auto-continued.
607
- code === PROVIDER_TRANSIENT_REJECTION_ERROR_CODE ||
608
- // `builder_gateway_error` is the no-detail fallback the Builder engine
609
- // emits when the gateway returns `{type:"stop",reason:"error"}` with no
610
- // explanation — almost always the upstream provider giving up (model
611
- // quota hit, account misconfiguration, opaque downstream failure). The
612
- // production-agent already retries this synchronously up to MAX_RETRIES
613
- // before the error escapes to the SSE stream, so by the time the client
614
- // sees it, retrying again with another POST /agent-chat will hit the
615
- // same wall. This used to send the chat into a 32-continuation runaway
616
- // (each turn cleared+regenerated visible content) for users hitting a
617
- // misbehaving Builder route. Surface the error instead.
618
- code === "builder_gateway_error" ||
619
- // The hosted run exhausted its in-invocation continuation budget without
620
- // finishing (run-loop-with-resume.ts). It's flagged `recoverable: true` so
621
- // the recovery banner reads "stopped before finishing", but it must NOT
622
- // auto-continue: another POST would hit the same ~40s wall and churn. The
623
- // user retries deliberately (ideally as a single bulk action).
624
- code === "run_budget_exhausted" ||
625
- // `aborted_<reason>` is emitted by `terminalEventForAbortReason` for every
626
- // abort that is neither a user stop nor a continuation boundary — a Slack
627
- // cancel, a stuck-banner auto-retry, an operator kill. Whoever aborted the
628
- // run owns the retry; auto-continuing here restarts work someone just
629
- // stopped (and double-fires alongside the stuck banner's own retry). They
630
- // stay `recoverable: true` so the banner still reads "stopped before
631
- // finishing".
632
- code.startsWith("aborted_") ||
633
- // The run's outcome is genuinely UNKNOWN: its row is gone, or it left
634
- // 'running' in a state the server has no terminal event for. Both stay
635
- // `recoverable: true` so the banner offers a manual Retry, but an
636
- // automatic re-POST would assert the turn did not finish — and it may
637
- // well have, side effects included. Replaying it would duplicate them.
638
- // The user decides, which is exactly what these errors say to do.
639
- code === "run_record_missing" ||
640
- code === "unknown_run_status" ||
641
- code === "run_terminal_lookup_failed") {
568
+ if (isTerminalRecoveryErrorCode(code)) {
642
569
  return false;
643
570
  }
644
571
  if (code === "builder_gateway_network_error" ||
@@ -25,9 +25,9 @@ export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import
25
25
  ok: boolean;
26
26
  }>>;
27
27
  export declare const postCollabText: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
+ text?: undefined;
28
29
  ok?: undefined;
29
30
  error: string;
30
- text?: undefined;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -13,9 +13,9 @@ export declare const postCollabPatch: import("h3").EventHandlerWithFetch<import(
13
13
  ok: boolean;
14
14
  }>>;
15
15
  export declare const getCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- data?: undefined;
17
16
  error: string;
18
17
  docId?: undefined;
18
+ data?: undefined;
19
19
  } | {
20
20
  error?: undefined;
21
21
  docId: string;
@@ -1,5 +1,5 @@
1
1
  export type WorkspaceConnectionCapability = "search" | "import" | "messages" | "meetings" | "crm" | "code" | "docs";
2
- export type WorkspaceConnectionTemplateUse = "analytics" | "brain" | "calendar" | "clips" | "content" | "crm" | "design" | "dispatch" | "factory" | "forms" | "mail" | "slides" | "slides-2";
2
+ export type WorkspaceConnectionTemplateUse = "analytics" | "brain" | "calendar" | "clips" | "content" | "crm" | "design" | "dispatch" | "factory" | "forms" | "mail" | "slides";
3
3
  export type WorkspaceConnectionProviderId = "slack" | "github" | "figma" | "notion" | "gmail" | "google_calendar" | "google_drive" | "bigquery" | "google_docs" | "google_sheets" | "google_slides" | "hubspot" | "salesforce" | "jira" | "sentry" | "granola" | "clips" | "dbt" | "sigma" | "anthropic-managed-agents" | "generic";
4
4
  export interface WorkspaceConnectionCredentialKey {
5
5
  key: string;
@@ -84,7 +84,7 @@ export declare const WORKSPACE_CONNECTION_PROVIDERS: readonly [{
84
84
  readonly scopes: readonly ["current_user:read", "file_content:read", "file_metadata:read", "projects:read"];
85
85
  };
86
86
  readonly capabilities: readonly ["search", "import", "docs"];
87
- readonly recommendedTemplateUses: readonly ["brain", "design", "slides", "slides-2", "content"];
87
+ readonly recommendedTemplateUses: readonly ["brain", "design", "slides", "content"];
88
88
  }, {
89
89
  readonly id: "notion";
90
90
  readonly label: "Notion";
@@ -184,7 +184,7 @@ export declare const WORKSPACE_CONNECTION_PROVIDERS: readonly [{
184
184
  readonly required: true;
185
185
  }];
186
186
  readonly capabilities: readonly ["search", "import", "docs"];
187
- readonly recommendedTemplateUses: readonly ["brain", "content", "slides", "slides-2", "dispatch", "analytics"];
187
+ readonly recommendedTemplateUses: readonly ["brain", "content", "slides", "dispatch", "analytics"];
188
188
  readonly oauth: {
189
189
  authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth";
190
190
  tokenUrl: "https://oauth2.googleapis.com/token";
@@ -207,7 +207,7 @@ export declare const WORKSPACE_CONNECTION_PROVIDERS: readonly [{
207
207
  readonly required: true;
208
208
  }];
209
209
  readonly capabilities: readonly ["search", "import", "docs"];
210
- readonly recommendedTemplateUses: readonly ["brain", "content", "slides", "slides-2", "dispatch"];
210
+ readonly recommendedTemplateUses: readonly ["brain", "content", "slides", "dispatch"];
211
211
  readonly oauth: {
212
212
  authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth";
213
213
  tokenUrl: "https://oauth2.googleapis.com/token";
@@ -253,7 +253,7 @@ export declare const WORKSPACE_CONNECTION_PROVIDERS: readonly [{
253
253
  readonly required: true;
254
254
  }];
255
255
  readonly capabilities: readonly ["search", "import", "docs"];
256
- readonly recommendedTemplateUses: readonly ["brain", "content", "slides", "slides-2", "dispatch"];
256
+ readonly recommendedTemplateUses: readonly ["brain", "content", "slides", "dispatch"];
257
257
  readonly oauth: {
258
258
  authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth";
259
259
  tokenUrl: "https://oauth2.googleapis.com/token";
@@ -92,13 +92,7 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
92
92
  ],
93
93
  },
94
94
  capabilities: ["search", "import", "docs"],
95
- recommendedTemplateUses: [
96
- "brain",
97
- "design",
98
- "slides",
99
- "slides-2",
100
- "content",
101
- ],
95
+ recommendedTemplateUses: ["brain", "design", "slides", "content"],
102
96
  }),
103
97
  defineWorkspaceConnectionProvider({
104
98
  id: "notion",
@@ -167,7 +161,6 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
167
161
  "brain",
168
162
  "content",
169
163
  "slides",
170
- "slides-2",
171
164
  "dispatch",
172
165
  "analytics",
173
166
  ],
@@ -183,13 +176,7 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
183
176
  description: "Google Docs files for source material, document search, and agent-assisted writing workflows.",
184
177
  credentialKeys: GOOGLE_CREDENTIAL_KEYS,
185
178
  capabilities: ["search", "import", "docs"],
186
- recommendedTemplateUses: [
187
- "brain",
188
- "content",
189
- "slides",
190
- "slides-2",
191
- "dispatch",
192
- ],
179
+ recommendedTemplateUses: ["brain", "content", "slides", "dispatch"],
193
180
  oauth: googleOAuth([
194
181
  ...GOOGLE_IDENTITY_SCOPES,
195
182
  "https://www.googleapis.com/auth/documents.readonly",
@@ -210,13 +197,7 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
210
197
  description: "Google Slides presentations for reference imports, visual context, and presentation workflows.",
211
198
  credentialKeys: GOOGLE_CREDENTIAL_KEYS,
212
199
  capabilities: ["search", "import", "docs"],
213
- recommendedTemplateUses: [
214
- "brain",
215
- "content",
216
- "slides",
217
- "slides-2",
218
- "dispatch",
219
- ],
200
+ recommendedTemplateUses: ["brain", "content", "slides", "dispatch"],
220
201
  oauth: googleOAuth([
221
202
  ...GOOGLE_IDENTITY_SCOPES,
222
203
  "https://www.googleapis.com/auth/presentations.readonly",
@@ -33,9 +33,9 @@ declare const _default: import("../../action.js").ActionDefinition<{
33
33
  idempotencyKey: string;
34
34
  deleted: boolean;
35
35
  } | {
36
- id?: undefined;
37
36
  error: string;
38
37
  url?: undefined;
38
+ id?: undefined;
39
39
  provider?: undefined;
40
40
  configured?: undefined;
41
41
  } | {
@@ -45,8 +45,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
45
45
  provider: string;
46
46
  configured?: undefined;
47
47
  } | {
48
- id?: undefined;
49
48
  url?: undefined;
49
+ id?: undefined;
50
50
  provider?: undefined;
51
51
  error: string;
52
52
  configured: boolean;
@@ -70,12 +70,11 @@ async function withAssetAuthorization(run, held = {}) {
70
70
  if (error?.status !== 401)
71
71
  throw error;
72
72
  const refreshed = await assetAuthorization({ forceRefresh: true });
73
- // The same token again (no refresh token, a key credential) would only
74
- // repeat the 401; report the original refusal instead.
73
+ // A refresh that hands back the same token (no refresh token, a key
74
+ // credential) cannot change the outcome: Builder has refused the only
75
+ // credential we hold, so the connection needs reconnecting.
75
76
  if (refreshed.authorization === auth.authorization) {
76
- if (auth.apiKey)
77
- throw confirmedBuilderAuthorizationFailure(error);
78
- throw error;
77
+ throw confirmedBuilderAuthorizationFailure(error);
79
78
  }
80
79
  held.auth = refreshed;
81
80
  try {
@@ -1,6 +1,7 @@
1
1
  import { getAppConfig } from "../app-config/store.js";
2
2
  import { safeValue } from "../tracking/redaction.js";
3
3
  import { listTrackingProviders, track } from "../tracking/registry.js";
4
+ import { DATABASE_FAILURE_MESSAGE, isDatabaseQueryFailure, } from "./embed-redaction.js";
4
5
  const CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo";
5
6
  const PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion";
6
7
  export const MCP_ANALYTICS_EVENTS = {
@@ -203,6 +204,12 @@ export function trackMcpOAuthLoginWallShown(ctx, args) {
203
204
  });
204
205
  }
205
206
  export function describeMcpError(err) {
207
+ if (isDatabaseQueryFailure(err)) {
208
+ return {
209
+ errorType: "DrizzleQueryError",
210
+ errorMessage: DATABASE_FAILURE_MESSAGE,
211
+ };
212
+ }
206
213
  if (err && typeof err === "object") {
207
214
  const record = err;
208
215
  const code = record.errorCode ?? record.code;
@@ -2570,7 +2570,9 @@ export async function createMCPServerForRequest(config, identity, requestMeta) {
2570
2570
  catch (err) {
2571
2571
  failure = describeMcpError(err);
2572
2572
  return {
2573
- content: [{ type: "text", text: `Error: ${err.message}` }],
2573
+ content: [
2574
+ { type: "text", text: `Error: ${failure.errorMessage}` },
2575
+ ],
2574
2576
  isError: true,
2575
2577
  };
2576
2578
  }
@@ -2604,7 +2606,10 @@ export async function createMCPServerForRequest(config, identity, requestMeta) {
2604
2606
  failure = describeMcpError(err);
2605
2607
  return {
2606
2608
  content: [
2607
- { type: "text", text: `Error: ${err.message}` },
2609
+ {
2610
+ type: "text",
2611
+ text: `Error: ${failure.errorMessage}`,
2612
+ },
2608
2613
  ],
2609
2614
  isError: true,
2610
2615
  };
@@ -8,7 +8,7 @@ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../ser
8
8
  import { describeMcpError } from "./analytics.js";
9
9
  import { actionApprovalError, actionApprovalReviewResult, buildLinkArtifacts, conciseMcpAppToolText, conciseToolResultText, getMcpDirectoryWidgetResourceUri, hasVerifiedMcpUserIdentity, MCP_ACTION_APPROVAL_INPUT_KEY, mcpAppEmbedOpenLinkMeta, mcpAppOnlyEmbedStartMeta, mcpAppStructuredContent, mcpAppWidgetsEnabledForIdentity, mcpDirectoryWidgetCapabilityForTool, mcpDirectoryWidgetSourceMeta, mcpResponseStructuredContent, mcpResultHasContent, metadataObject, modelVisibleToolResponse, openAiToolResultMeta, PRESERVE_MCP_OBJECT_RESULT, resolveMcpAppResourceSafely, returnsQueryPayload, textPagingInputs, upstreamStatusText, withheldWriteResponse, withoutMcpAppEmbedTicket, withServerMintedMcpAppEmbedStart, } from "./build-server.js";
10
10
  import { mcpCallAttribution } from "./call-attribution.js";
11
- import { describeThrownForLog, HIDDEN_EMBED_URL, isEmbedStartUrl, redactEmbedText, } from "./embed-redaction.js";
11
+ import { DATABASE_FAILURE_MESSAGE, describeThrownForLog, HIDDEN_EMBED_URL, isDatabaseQueryFailure, isEmbedStartUrl, redactEmbedText, } from "./embed-redaction.js";
12
12
  import { embedCredentialSources, mcpActionErrorCode, redactEmbedCredentials, returnedResult, runMcpActionCallUnredacted, toJsonData, UnreadableResultError, } from "./execute-action-call.js";
13
13
  import { mcpResponseOutputCheck } from "./output-contract.js";
14
14
  export function createDirectActionCall(scope) {
@@ -44,6 +44,8 @@ export function createDirectActionCall(scope) {
44
44
  ? undefined
45
45
  : callableActions[name]?.mcpOutputContract;
46
46
  const errorText = (err) => {
47
+ if (isDatabaseQueryFailure(err))
48
+ return DATABASE_FAILURE_MESSAGE;
47
49
  const projectedError = directoryCatalog && config.directoryProfile?.projectResult
48
50
  ? config.directoryProfile.projectResult(name, err.message)
49
51
  : err.message;
@@ -10,6 +10,9 @@
10
10
  * that a value was hidden rather than finding its key missing.
11
11
  */
12
12
  export declare const HIDDEN_EMBED_URL = "[hidden embed URL]";
13
+ export declare const DATABASE_FAILURE_MESSAGE = "The database rejected the request.";
14
+ /** Drizzle puts a failed query's SQL and bound parameters in its message. */
15
+ export declare function isDatabaseQueryFailure(error: unknown): boolean;
13
16
  /** Replaces a value already being serialized higher up the same result. */
14
17
  export declare const CIRCULAR_RESULT = "[circular result]";
15
18
  /** Keys removed from every result object, whatever their value. */
@@ -11,6 +11,11 @@ import { decodeHTML } from "entities/decode";
11
11
  * that a value was hidden rather than finding its key missing.
12
12
  */
13
13
  export const HIDDEN_EMBED_URL = "[hidden embed URL]";
14
+ export const DATABASE_FAILURE_MESSAGE = "The database rejected the request.";
15
+ /** Drizzle puts a failed query's SQL and bound parameters in its message. */
16
+ export function isDatabaseQueryFailure(error) {
17
+ return error instanceof Error && error.message.startsWith("Failed query:");
18
+ }
14
19
  /** Replaces a value already being serialized higher up the same result. */
15
20
  export const CIRCULAR_RESULT = "[circular result]";
16
21
  /** Keys removed from every result object, whatever their value. */