@agent-native/core 0.159.5 → 0.160.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 (76) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/assets/server/lib/generation.ts +10 -6
  3. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +18 -4
  4. package/corpus/templates/calendar/actions/create-event.ts +4 -2
  5. package/corpus/templates/calendar/actions/event-action-helpers.ts +56 -9
  6. package/corpus/templates/calendar/actions/update-event.ts +1 -0
  7. package/corpus/templates/calendar/agent-native.config.ts +1 -0
  8. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +8 -2
  9. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +102 -68
  10. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +150 -49
  11. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +57 -23
  12. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +4 -1
  13. package/corpus/templates/calendar/app/components/calendar/TimezoneSwitchDialog.tsx +70 -0
  14. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +127 -88
  15. package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +59 -25
  16. package/corpus/templates/calendar/app/hooks/use-event-drag.ts +55 -18
  17. package/corpus/templates/calendar/app/i18n-data.ts +60 -0
  18. package/corpus/templates/calendar/app/lib/all-day-layout.ts +16 -8
  19. package/corpus/templates/calendar/app/lib/calendar-drafts.ts +56 -0
  20. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +351 -0
  21. package/corpus/templates/calendar/app/lib/event-layout.ts +14 -8
  22. package/corpus/templates/calendar/app/lib/out-of-office.ts +29 -61
  23. package/corpus/templates/calendar/app/lib/working-location.ts +34 -2
  24. package/corpus/templates/calendar/app/pages/CalendarView.tsx +317 -139
  25. package/corpus/templates/calendar/changelog/2026-08-13-calendar-stays-pinned-to-the-saved-timezone-and-asks-before-.md +6 -0
  26. package/corpus/templates/calendar/changelog/2026-08-13-working-locations-can-be-added-from-calendar-days-with-a-ful.md +6 -0
  27. package/corpus/templates/calendar/changelog/2026-08-14-choosing-office-or-other-when-adding-a-working-location-creates.md +6 -0
  28. package/corpus/templates/calendar/changelog/2026-08-14-creating-an-other-working-location-now-keeps-the-custom-name.md +6 -0
  29. package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-keep-home-office-or-custom-titles.md +6 -0
  30. package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-now-keep-a-home-office-or-custom-title.md +6 -0
  31. package/corpus/templates/calendar/changelog/2026-08-14-turning-a-midnight-ending-working-location-back-to-all-day.md +6 -0
  32. package/corpus/templates/calendar/server/lib/google-calendar.ts +22 -1
  33. package/dist/agent/engine/builder-engine.d.ts +16 -2
  34. package/dist/agent/engine/builder-engine.js +178 -115
  35. package/dist/agent/engine/builtin.js +13 -4
  36. package/dist/agent/engine/credential-errors.d.ts +23 -0
  37. package/dist/agent/engine/credential-errors.js +19 -0
  38. package/dist/agent/engine/error-detail.d.ts +15 -0
  39. package/dist/agent/engine/error-detail.js +29 -0
  40. package/dist/agent/engine/registry.d.ts +14 -3
  41. package/dist/agent/engine/registry.js +106 -61
  42. package/dist/agent/engine/types.d.ts +16 -0
  43. package/dist/agent/engine/types.js +9 -0
  44. package/dist/agent/production-agent.d.ts +7 -0
  45. package/dist/agent/production-agent.js +58 -20
  46. package/dist/agent/run-manager.js +30 -0
  47. package/dist/agent/thread-data-builder.js +4 -0
  48. package/dist/agent/types.d.ts +9 -0
  49. package/dist/client/agent-chat-adapter.js +46 -15
  50. package/dist/client/error-format.js +25 -2
  51. package/dist/client/sse-event-processor.d.ts +2 -0
  52. package/dist/client/sse-event-processor.js +12 -1
  53. package/dist/collab/awareness.d.ts +2 -2
  54. package/dist/collab/struct-routes.d.ts +1 -1
  55. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  56. package/dist/jobs/background-automation-runner.js +9 -15
  57. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  58. package/dist/notifications/routes.d.ts +6 -6
  59. package/dist/observability/routes.d.ts +5 -5
  60. package/dist/resources/handlers.d.ts +1 -1
  61. package/dist/secrets/register-framework-secrets.js +25 -0
  62. package/dist/secrets/routes.d.ts +9 -9
  63. package/dist/server/agent-chat-plugin.js +9 -2
  64. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  65. package/dist/server/credential-provider.d.ts +80 -0
  66. package/dist/server/credential-provider.js +168 -1
  67. package/dist/server/google-realtime-session.d.ts +1 -1
  68. package/dist/server/google-realtime-session.js +6 -6
  69. package/dist/server/index.d.ts +1 -1
  70. package/dist/server/index.js +6 -1
  71. package/dist/server/realtime-token.d.ts +1 -1
  72. package/dist/server/realtime-voice.js +12 -6
  73. package/dist/server/transcribe-voice.d.ts +1 -1
  74. package/dist/server/transcribe-voice.js +18 -16
  75. package/dist/transcription/builder-transcription.js +8 -4
  76. package/package.json +1 -1
@@ -14,7 +14,7 @@ import { isMcpToolAllowedForRequest } from "../mcp-client/visibility.js";
14
14
  import { shouldInferSentimentForTurn } from "../observability/sentiment.js";
15
15
  import { completeRun as completeProgressRun, startRun as startProgressRun, updateRunProgress, } from "../progress/registry.js";
16
16
  import { isRuntimeVisibleScope, parseSkillFrontmatter, } from "../server/agent-chat/skill-frontmatter.js";
17
- import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, readDeployCredentialEnv, } from "../server/credential-provider.js";
17
+ import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, isBuilderGatewayDeployConfigured, readDeployCredentialEnv, } from "../server/credential-provider.js";
18
18
  import { readBody } from "../server/h3-helpers.js";
19
19
  import { resolveHostedHarnessPolicy } from "../server/hosted-harness-policy.js";
20
20
  import { assertRequestActionSurfaceIsolation, getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
@@ -27,8 +27,8 @@ import { buildSystemManifestSections, readContextXraySystemSections, } from "./c
27
27
  import { AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, backgroundRuntimeDiagnosticDetail, dispatchPathTargetsNetlifyBackgroundFunction, isAgentChatDurableBackgroundEnabled, isAgentChatForegroundSelfChainEnabled, isInBackgroundFunctionRuntime, resolveAgentChatProcessRunDispatchPath, shouldUseBackgroundFunctionTimeoutForWorker, } from "./durable-background.js";
28
28
  import { applyContextXrayTransformForIteration } from "./engine/context-directives-transform.js";
29
29
  import { attemptContinuationDispatch } from "./engine/continuation-dispatch-retry.js";
30
- import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
31
- import { isProviderConnectionErrorMessage } from "./engine/error-detail.js";
30
+ import { formatLlmCredentialErrorMessage, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
31
+ import { isContextOverflowCode, isContextOverflowMessage, isProviderConnectionErrorMessage, } from "./engine/error-detail.js";
32
32
  import { resolveEngine, explicitEngineName, registerBuiltinEngines, getStoredModelForEngine, normalizeModelForEngine, isResolvedEngineUsableForRequest, } from "./engine/index.js";
33
33
  import { resolveEmptyResponseRetryMaxOutputTokens, resolveMainChatMaxOutputTokens, resolveMaxOutputTokensForEngine, } from "./engine/output-tokens.js";
34
34
  import { PROVIDER_TO_ENV } from "./engine/provider-env-vars.js";
@@ -840,20 +840,17 @@ function toolInputActivityLabel(toolName) {
840
840
  export function isContextTooLongError(err) {
841
841
  if (!(err instanceof Error))
842
842
  return false;
843
- const msg = err.message.toLowerCase();
844
- if (msg.includes("context_length_exceeded") ||
845
- msg.includes("input_too_long") ||
846
- msg.includes("too many tokens") ||
847
- msg.includes("prompt is too long") ||
848
- msg.includes("reduce the length") ||
849
- // Gemini phrasing
850
- msg.includes("input token count exceeds") ||
851
- msg.includes("request too large"))
843
+ // The engine's structural verdict comes first: it was read off the provider's
844
+ // own reply, which is not always the message delivered here — a
845
+ // Builder-credits deployment answers every gateway rejection with one visitor
846
+ // line, and the gateway reports an overflow as a plain `invalid_request_error`
847
+ // whose prose was the only signal.
848
+ if (err instanceof EngineError && err.contextOverflow === true)
849
+ return true;
850
+ if (isContextOverflowMessage(err.message))
851
+ return true;
852
+ if (err instanceof EngineError && isContextOverflowCode(err.errorCode)) {
852
853
  return true;
853
- if (err instanceof EngineError) {
854
- const code = (err.errorCode ?? "").toLowerCase();
855
- if (code.includes("context_length") || code.includes("input_too_long"))
856
- return true;
857
854
  }
858
855
  return false;
859
856
  }
@@ -1425,8 +1422,10 @@ export async function callConnectedAgentReference(input) {
1425
1422
  orgSecret: callerAuth.orgSecret,
1426
1423
  onUpdate: relay.observePollUpdate,
1427
1424
  });
1428
- const responseText = userFacingLlmCredentialError(response, { agentName: input.agent }) ??
1429
- response;
1425
+ const responseText = userFacingLlmCredentialError(response, {
1426
+ agentName: input.agent,
1427
+ visitorFacing: isBuilderGatewayDeployConfigured(),
1428
+ }) ?? response;
1430
1429
  relay.emitResponseText(responseText);
1431
1430
  relay.finish("done");
1432
1431
  return responseText;
@@ -1564,6 +1563,11 @@ export function isResumableEngineError(err) {
1564
1563
  const code = err instanceof EngineError ? (err.errorCode ?? "").toLowerCase() : "";
1565
1564
  if (code === "builder_gateway_timeout" ||
1566
1565
  code === "builder_gateway_network_error" ||
1566
+ // A gateway stream that stopped mid-turn. It reached here as
1567
+ // `builder_gateway_network_error` until the engine named it, and a
1568
+ // continuation is precisely its recovery: the partial turn is preserved and
1569
+ // the agent is nudged to finish it, where an in-call retry would `clear` it.
1570
+ code === "builder_gateway_stream_ended" ||
1567
1571
  code === "provider_network_error") {
1568
1572
  return true;
1569
1573
  }
@@ -1626,6 +1630,13 @@ export function continuationReasonForResumableError(err) {
1626
1630
  const code = err instanceof EngineError ? (err.errorCode ?? "").toLowerCase() : "";
1627
1631
  if (code === "builder_gateway_timeout")
1628
1632
  return "gateway_timeout";
1633
+ // A gateway/proxy timeout status, read structurally. The prose below says the
1634
+ // same thing on most lanes, but not on a Builder-credits deployment, where the
1635
+ // message is one visitor line.
1636
+ if (err instanceof EngineError &&
1637
+ (err.statusCode === 408 || err.statusCode === 504)) {
1638
+ return "gateway_timeout";
1639
+ }
1629
1640
  const text = err instanceof Error ? err.message.toLowerCase() : "";
1630
1641
  if (text.includes("gateway timeout") ||
1631
1642
  text.includes("upstream timeout") ||
@@ -3737,6 +3748,7 @@ export async function runAgentLoop(opts) {
3737
3748
  upgradeUrl: event.upgradeUrl,
3738
3749
  statusCode: event.statusCode,
3739
3750
  providerRetryable: event.providerRetryable,
3751
+ contextOverflow: event.contextOverflow,
3740
3752
  });
3741
3753
  }
3742
3754
  }
@@ -5132,7 +5144,8 @@ function backgroundChatProgressRunId(turnId) {
5132
5144
  .slice(0, 160);
5133
5145
  return `agent-chat-${normalized || "turn"}`;
5134
5146
  }
5135
- function isRecoverableContinuationError(event) {
5147
+ /** @internal exported for unit tests only */
5148
+ export function isRecoverableContinuationError(event) {
5136
5149
  const code = String(event.errorCode ?? "").toLowerCase();
5137
5150
  const message = event.error.toLowerCase();
5138
5151
  if (code === "builder_gateway_error")
@@ -5147,12 +5160,26 @@ function isRecoverableContinuationError(event) {
5147
5160
  return (event.recoverable === true ||
5148
5161
  code === "builder_gateway_timeout" ||
5149
5162
  code === "builder_gateway_network_error" ||
5163
+ // Server-driven background continuation for a truncated stream. The message
5164
+ // used to carry this ("stream ended without a stop event", classified into
5165
+ // `builder_gateway_network_error` above); the code carries it now, and on a
5166
+ // Builder-credits deployment the code is all that is left.
5167
+ code === "builder_gateway_stream_ended" ||
5150
5168
  code === "provider_network_error" ||
5151
5169
  code === "stale_run" ||
5152
5170
  code === "timeout" ||
5153
5171
  code === "timeout_error" ||
5154
5172
  code === "http_408" ||
5155
5173
  code === "http_429" ||
5174
+ // The 5xx family the message clauses below used to reach by prose alone
5175
+ // ("temporarily unavailable", "gateway timeout"). The client's own
5176
+ // continuation list (sse-event-processor) has always carried these codes;
5177
+ // the server read the sentence instead, which a Builder-credits deployment
5178
+ // replaces with one visitor line.
5179
+ code === "http_500" ||
5180
+ code === "http_502" ||
5181
+ code === "http_503" ||
5182
+ code === "http_504" ||
5156
5183
  code === "http_529" ||
5157
5184
  code === "run_timeout" ||
5158
5185
  message.includes("timeout") ||
@@ -6514,11 +6541,20 @@ export function createProductionAgentHandler(options) {
6514
6541
  setResponseHeader(event, "Cache-Control", "no-cache");
6515
6542
  setResponseHeader(event, "Connection", "keep-alive");
6516
6543
  const encoder = new TextEncoder();
6544
+ // A Builder-credits deployment serves this chat to visitors with no
6545
+ // account, so the owner-facing "connect a provider" copy is unactionable
6546
+ // for them. It is also what they see most of the time once a gateway auth
6547
+ // failure arms the 15-minute marker: the credits lane stops resolving,
6548
+ // engine selection falls back to the anthropic default, and this branch —
6549
+ // not the engine's own error — is what answers the turn.
6550
+ const missingCredentialsError = formatLlmCredentialErrorMessage({
6551
+ visitorFacing: isBuilderGatewayDeployConfigured(),
6552
+ });
6517
6553
  return new ReadableStream({
6518
6554
  start(controller) {
6519
6555
  controller.enqueue(encoder.encode(`data: ${JSON.stringify({
6520
6556
  type: "error",
6521
- error: LLM_MISSING_CREDENTIALS_MESSAGE,
6557
+ error: missingCredentialsError,
6522
6558
  errorCode: LLM_MISSING_CREDENTIALS_ERROR_CODE,
6523
6559
  })}\n\n`));
6524
6560
  controller.close();
@@ -7630,6 +7666,7 @@ export function createProductionAgentHandler(options) {
7630
7666
  });
7631
7667
  const message = userFacingLlmCredentialError(err, {
7632
7668
  agentName: ref.name,
7669
+ visitorFacing: isBuilderGatewayDeployConfigured(),
7633
7670
  }) ?? `Failed to run ${ref.name}: ${err?.message}`;
7634
7671
  return `<agent-response name="${ref.name}" id="${ref.refId}" type="custom-agent" error="true">\n${message}\n</agent-response>`;
7635
7672
  }
@@ -7670,6 +7707,7 @@ export function createProductionAgentHandler(options) {
7670
7707
  catch (err) {
7671
7708
  const message = userFacingLlmCredentialError(err, {
7672
7709
  agentName: ref.name,
7710
+ visitorFacing: isBuilderGatewayDeployConfigured(),
7673
7711
  }) ?? `Failed to reach ${ref.name}: ${err?.message}`;
7674
7712
  return `<agent-response name="${ref.name}" id="${ref.refId}" error="true">\n${message}\n</agent-response>`;
7675
7713
  }
@@ -323,6 +323,10 @@ function shouldCaptureRunError(err) {
323
323
  const normalizedCode = errorCode.toLowerCase();
324
324
  return (!normalizedCode.startsWith("credits-limit") &&
325
325
  normalizedCode !== "builder_gateway_network_error" &&
326
+ // A truncated gateway stream, which the client continues. It arrived here as
327
+ // `builder_gateway_network_error` until the engine gave it its own code, so
328
+ // omitting it would turn a routine recovered interruption into Sentry noise.
329
+ normalizedCode !== "builder_gateway_stream_ended" &&
326
330
  normalizedCode !== PROVIDER_NETWORK_ERROR_CODE &&
327
331
  normalizedCode !== "provider_rate_limited" &&
328
332
  normalizedCode !== "rate_limit_exceeded");
@@ -1173,6 +1177,32 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1173
1177
  ...(err instanceof EngineError && err.upgradeUrl
1174
1178
  ? { upgradeUrl: err.upgradeUrl }
1175
1179
  : {}),
1180
+ // The engine's own retry verdict, carried structurally. The client
1181
+ // decides auto-continue from this, `recoverable` and the error code, and
1182
+ // a deployment whose visitor copy replaces the message (Builder credits)
1183
+ // leaves it nothing else to read: without this a provider throttle the
1184
+ // engine marked retryable ends the turn on those sites only.
1185
+ //
1186
+ // NOT `recoverable`. That field is the server's own "this run stopped at
1187
+ // an internal continuation boundary" signal, read by
1188
+ // `isInternalContinuationError` (thread-data-builder) to drop the error
1189
+ // from the persisted turn and by `isRecoverableContinuationError`
1190
+ // (production-agent) to self-chain the next background chunk. Neither
1191
+ // lists `rate_limited` or `too_many_concurrent_requests` today, so
1192
+ // feeding them from the engine verdict would newly chain up to
1193
+ // MAX_BACKGROUND_RUN_CONTINUATIONS invocations into a live throttle — on
1194
+ // every lane, not just Builder credits.
1195
+ ...(err instanceof EngineError && err.providerRetryable === true
1196
+ ? { providerRetryable: true }
1197
+ : {}),
1198
+ // Same reasoning as `providerRetryable`: on the credits lane the message
1199
+ // is the visitor line and the code is `invalid_request_error`, so this
1200
+ // flag is the only thing left that says "trim and retry once". Dropping
1201
+ // it here turns a recoverable overflow into a terminal failure on exactly
1202
+ // the sites that cannot read the real reason.
1203
+ ...(err instanceof EngineError && err.contextOverflow === true
1204
+ ? { contextOverflow: true }
1205
+ : {}),
1176
1206
  });
1177
1207
  })
1178
1208
  .finally(async () => {
@@ -10,6 +10,10 @@ function isInternalContinuationError(event) {
10
10
  return false;
11
11
  return (event.recoverable === true ||
12
12
  code === "builder_gateway_timeout" ||
13
+ // Carries what `msg.includes("stream ended")` below used to: a
14
+ // Builder-credits deployment replaces that sentence with one visitor line,
15
+ // and the code is the only thing left that says the turn was truncated.
16
+ code === "builder_gateway_stream_ended" ||
13
17
  code === "stale_run" ||
14
18
  code === "timeout" ||
15
19
  code === "timeout_error" ||
@@ -375,6 +375,15 @@ export type AgentChatEvent = {
375
375
  details?: string;
376
376
  /** True when the user can reasonably continue/retry from partial work. */
377
377
  recoverable?: boolean;
378
+ /**
379
+ * The engine's own verdict that another attempt at the same request may
380
+ * succeed (`EngineError.providerRetryable`). Distinct from `recoverable`,
381
+ * which the server's continuation classifiers read as "this run ended at
382
+ * an internal boundary, fold it and chain the next chunk". A provider
383
+ * throttle is retryable without being a boundary — conflating them makes
384
+ * a rate limit self-chain background continuations.
385
+ */
386
+ providerRetryable?: boolean;
378
387
  }
379
388
  /**
380
389
  * Legacy SSE terminal event. New streams emit
@@ -1,6 +1,6 @@
1
1
  import { unwrapAttachmentEnvelope } from "@agent-native/toolkit/composer/pasted-text";
2
2
  import { actionPreparationContinuationNote } from "../agent/action-continuation-guidance.js";
3
- import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
3
+ import { formatLlmCredentialErrorMessage, isLlmCredentialError, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
4
4
  import { ANALYTICS_CLIENT_PLATFORM_HEADER } from "../shared/analytics-platform.js";
5
5
  import { clearPendingTurnIfMatches, getActiveRun, setActiveRun, updateActiveRunSeq, clearActiveRun, clearActiveRunIfMatches, setPendingTurn, } from "./active-run-state.js";
6
6
  import { getAnalyticsClientPlatform } from "./analytics-platform.js";
@@ -199,6 +199,25 @@ const BACKGROUND_TERMINAL_REASON_MESSAGES = {
199
199
  missing_credentials: LLM_MISSING_CREDENTIALS_MESSAGE,
200
200
  turn_continuation_budget_exhausted: "This request needed more automatic continuations than allowed and was stopped. Try breaking it into smaller steps.",
201
201
  };
202
+ /**
203
+ * Re-decide the mapped copy for whoever is reading it.
204
+ *
205
+ * The map above is client-authored copy for a server-produced code, and for the
206
+ * credential reasons that duplicates a decision the server already makes with
207
+ * `formatLlmCredentialErrorMessage({ visitorFacing })` — without the one input
208
+ * the decision needs. Earlier notes called this an unfixable residual gap on the
209
+ * grounds that the client cannot know the lane; the framing was wrong, because
210
+ * the lane is a fact about the deployment that the run snapshot can carry
211
+ * (`deploymentPaysForAi` on `/runs/active`). Everything else in the map is
212
+ * background-handoff copy that reads the same to either party.
213
+ */
214
+ function laneAwareTerminalReasonMessage(mapped, terminalReason, deploymentPaysForAi) {
215
+ if (!mapped || !isLlmCredentialError(mapped, terminalReason))
216
+ return mapped;
217
+ return formatLlmCredentialErrorMessage({
218
+ visitorFacing: deploymentPaysForAi,
219
+ });
220
+ }
202
221
  /**
203
222
  * `agent_runs.terminal_reason` values that mark a CHUNK boundary, not the end
204
223
  * of the turn — `markBackgroundContinuationChunkTerminal` (production-agent.ts)
@@ -2280,38 +2299,50 @@ export function createAgentChatAdapter(options) {
2280
2299
  const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
2281
2300
  const terminalReason = rawTerminalReason.replace(/^error:/, "");
2282
2301
  const mappedMessage = terminalReason
2283
- ? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
2302
+ ? laneAwareTerminalReasonMessage(BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason], terminalReason, lastKnown?.deploymentPaysForAi === true)
2284
2303
  : undefined;
2285
2304
  const mappedErrorCode = terminalReason === "missing_api_key" ||
2286
2305
  terminalReason === LLM_MISSING_CREDENTIALS_ERROR_CODE
2287
2306
  ? LLM_MISSING_CREDENTIALS_ERROR_CODE
2288
2307
  : terminalReason;
2289
- if (mappedMessage) {
2290
- yield* emitBackgroundTerminalError({
2291
- message: mappedMessage,
2292
- errorCode: mappedErrorCode,
2293
- details: `terminal_reason: ${rawTerminalReason}`,
2294
- });
2295
- return;
2296
- }
2297
2308
  if (hasErrorTerminalReason) {
2298
- if (lastRecoverableRunError) {
2309
+ // Never REPLACE the wording the server chose for this failure: a
2310
+ // deployment that pays for its own AI answers whoever is chatting
2311
+ // with one visitor line, and the map only knows the owner copy.
2312
+ // Only the captured error whose code IS the terminal reason is that
2313
+ // wording — an earlier auto-recoverable error in the same run is
2314
+ // stale (the cursor reset only clears it when the followed run
2315
+ // changes), and letting it outrank the terminal reason reports a
2316
+ // transient blip for a run that died of something else.
2317
+ const serverChosenError = lastRecoverableRunError?.errorCode === terminalReason
2318
+ ? lastRecoverableRunError
2319
+ : null;
2320
+ if (serverChosenError) {
2299
2321
  yield* emitBackgroundTerminalError({
2300
- message: lastRecoverableRunError.message,
2301
- errorCode: lastRecoverableRunError.errorCode ||
2322
+ message: serverChosenError.message,
2323
+ errorCode: serverChosenError.errorCode ||
2302
2324
  mappedErrorCode ||
2303
2325
  "background_run_failed",
2304
- details: lastRecoverableRunError.details,
2326
+ details: serverChosenError.details,
2305
2327
  });
2306
2328
  return;
2307
2329
  }
2308
2330
  yield* emitBackgroundTerminalError({
2309
- message: "The agent's background run failed before its final response could be recovered. You can retry from the preserved chat context.",
2331
+ message: mappedMessage ??
2332
+ "The agent's background run failed before its final response could be recovered. You can retry from the preserved chat context.",
2310
2333
  errorCode: mappedErrorCode || "background_run_failed",
2311
2334
  details: `terminal_reason: ${rawTerminalReason}`,
2312
2335
  });
2313
2336
  return;
2314
2337
  }
2338
+ if (mappedMessage) {
2339
+ yield* emitBackgroundTerminalError({
2340
+ message: mappedMessage,
2341
+ errorCode: mappedErrorCode,
2342
+ details: `terminal_reason: ${rawTerminalReason}`,
2343
+ });
2344
+ return;
2345
+ }
2315
2346
  if (status === "completed") {
2316
2347
  // The turn finished server-side; the events we managed to fold are
2317
2348
  // the durable transcript. Never turn a terminal tool-only chunk
@@ -1,3 +1,4 @@
1
+ import { GATEWAY_UNAVAILABLE_VISITOR_MESSAGE } from "../agent/engine/credential-errors.js";
1
2
  /**
2
3
  * Append a Builder CTA markdown link to gateway errors that users can fix
3
4
  * outside the app. Used by both
@@ -33,8 +34,9 @@ function isSafeUpgradeUrl(url) {
33
34
  }
34
35
  export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
35
36
  const normalized = normalizeChatError(errorMessage, errorCode);
36
- if (errorCode === "gateway_not_enabled" ||
37
- /space has not enabled the LLM gateway/i.test(normalized.message)) {
37
+ if (!isServerChosenVisitorMessage(normalized.message) &&
38
+ (errorCode === "gateway_not_enabled" ||
39
+ /space has not enabled the LLM gateway/i.test(normalized.message))) {
38
40
  return `Error: ${normalized.message}\n\n[${OPEN_BUILDER_SPACE_SETTINGS_LABEL}](${BUILDER_SPACE_SETTINGS_URL})`;
39
41
  }
40
42
  if (errorCode === "builder_gateway_error") {
@@ -49,6 +51,22 @@ export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
49
51
  }
50
52
  return `Error: ${normalized.message}\n\n[${UPGRADE_AT_BUILDER_LABEL}](${upgradeUrl})`;
51
53
  }
54
+ /**
55
+ * True when the server already decided what this reader may be told.
56
+ *
57
+ * A Builder-credits deployment answers every gateway rejection with one visitor
58
+ * line and keeps the real reason on `errorCode` for its owner. Every mapping
59
+ * below is keyed on that code, so re-deriving copy from it hands the visitor
60
+ * back the owner instruction the server just removed — "reconnect Builder in
61
+ * Settings" to someone with no account. This is an identity check against the
62
+ * exported constant, not a keyword match: the rewrite is the whole message.
63
+ *
64
+ * Deliberately not a `KNOWN_CHAT_ERROR_KEYS` entry: that map localizes copy,
65
+ * while this returns before any mapping runs at all.
66
+ */
67
+ function isServerChosenVisitorMessage(text) {
68
+ return text === GATEWAY_UNAVAILABLE_VISITOR_MESSAGE;
69
+ }
52
70
  const KNOWN_CHAT_ERROR_KEYS = new Map([
53
71
  [
54
72
  "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io or add a provider key.",
@@ -200,6 +218,11 @@ export function normalizeChatError(errorMessage, errorCode) {
200
218
  const looksHtml = /<html[\s>]|<body[\s>]|<head[\s>]/i.test(raw);
201
219
  const text = looksHtml ? htmlToText(raw) : raw.trim();
202
220
  const providerPayload = looksHtml ? null : parseProviderErrorPayload(text);
221
+ // Ahead of every mapping below, including the provider-payload fallback: the
222
+ // server already chose this reader's message, and any re-derivation from a
223
+ // code hands a visitor the owner instruction it deliberately removed.
224
+ if (isServerChosenVisitorMessage(text))
225
+ return { message: text };
203
226
  const code = normalizeErrorCode(errorCode ?? providerPayload?.errorCode);
204
227
  const providerMessage = providerPayload?.errorCode === "overloaded_error"
205
228
  ? "The model provider is overloaded right now. Wait a moment, then retry."
@@ -91,6 +91,8 @@ export interface SSEEvent {
91
91
  upgradeUrl?: string;
92
92
  details?: string;
93
93
  recoverable?: boolean;
94
+ /** The engine said another attempt may succeed — see `AgentChatEvent`. */
95
+ providerRetryable?: boolean;
94
96
  maxIterations?: number;
95
97
  }
96
98
  export type AgentAutoContinueReason = "run_timeout" | "loop_limit" | "no_progress" | "stream_ended" | "stale_run";
@@ -534,7 +534,12 @@ function isAutoRecoverableError(ev, errMsg) {
534
534
  code === "http_504" ||
535
535
  code === "rate_limited" ||
536
536
  code === "too_many_concurrent_requests" ||
537
- code === "overloaded_error") {
537
+ code === "overloaded_error" ||
538
+ // A gateway stream that ended without a stop event. The partial turn is
539
+ // real, so this continues rather than retrying: the code carries what the
540
+ // message used to (`msg.includes("stream ended")` below), which a
541
+ // Builder-credits deployment replaces with its one visitor line.
542
+ code === "builder_gateway_stream_ended") {
538
543
  return true;
539
544
  }
540
545
  if (ev.recoverable === true)
@@ -548,6 +553,12 @@ function isAutoRecoverableError(ev, errMsg) {
548
553
  return false;
549
554
  if (msg.includes("daily gateway request cap"))
550
555
  return false;
556
+ // The engine's structural verdict, checked after every terminal code above so
557
+ // it can never revive a quota or auth rejection. It is the only retry signal
558
+ // left once the message is one visitor line: an upstream "Overloaded" carries
559
+ // no code, and its text is what the rewrite removed.
560
+ if (ev.providerRetryable === true)
561
+ return true;
551
562
  // "gateway error" intentionally absent — that's the no-detail Builder
552
563
  // gateway fallback and the production-agent already retries it
553
564
  // synchronously up to MAX_RETRIES before the error escapes here. Treating
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
- error?: undefined;
66
65
  states: {
67
66
  clientId: number;
68
67
  state: string;
69
68
  }[];
69
+ error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
- error?: undefined;
81
80
  users: {
82
81
  clientId: number;
83
82
  lastSeen: number;
84
83
  }[];
84
+ error?: undefined;
85
85
  }>>;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
+ error?: undefined;
20
21
  configured?: undefined;
21
22
  connectPath?: undefined;
22
23
  url: string;
23
24
  id: string;
24
25
  provider: string;
25
- error?: undefined;
26
26
  }>;
27
27
  export default _default;
@@ -1,5 +1,4 @@
1
1
  import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
2
- import { createBuilderEngine } from "../agent/engine/builder-engine.js";
3
2
  import { getStoredModelForEngine, normalizeModelForEngine, resolveEngine, } from "../agent/engine/index.js";
4
3
  import { actionsToEngineTools, filterInitialEngineTools, getOwnerActiveApiKey, } from "../agent/production-agent.js";
5
4
  import { runAgentLoopDirectWithSoftTimeout } from "../agent/run-loop-with-resume.js";
@@ -10,7 +9,6 @@ import { resolveAutomationExecutionIdentity, } from "../automations/service.js";
10
9
  import { createThread } from "../chat-threads/store.js";
11
10
  import { queryOrgMembers } from "../org/context.js";
12
11
  import { organizationIdFromResourceOwner, organizationResourceOwner, } from "../resources/store.js";
13
- import { resolveBuilderCredentialsDetailed } from "../server/credential-provider.js";
14
12
  import { runWithRequestContext, } from "../server/request-context.js";
15
13
  import { attachAutomationRunThread, finishAutomationRun, startAutomationRun, } from "./run-history.js";
16
14
  const BACKGROUND_RUN_STUCK_MS = 10 * 60_000;
@@ -238,23 +236,19 @@ async function executeBackgroundAutomation(options, deps, historyId) {
238
236
  const availableTools = actionsToEngineTools(actions);
239
237
  const tools = filterInitialEngineTools(availableTools, initialToolNames);
240
238
  const userApiKey = await getOwnerActiveApiKey(ownerEmail);
241
- const resolvedEngine = deps.engine ??
239
+ // The run manager invokes its detached callback after the scheduler's
240
+ // setup stack has yielded, so the engine's credentials must be captured
241
+ // now, while the owner/org identity is explicit. Passing
242
+ // `credentialIdentity` is what makes resolveEngine capture them, on the
243
+ // same gateway lane the interactive path uses — a Builder-credits site has
244
+ // no per-user connection to find, so resolving that lane by hand here once
245
+ // left every scheduled automation dead while chat still worked.
246
+ const engine = deps.engine ??
242
247
  (await resolveEngine({
243
248
  apiKey: userApiKey ?? deps.apiKey,
244
249
  appId: deps.appId,
250
+ credentialIdentity: { userEmail: ownerEmail, orgId },
245
251
  }));
246
- // The run manager invokes its detached callback after the scheduler's
247
- // setup stack has yielded. Capture the verified Builder pair while the
248
- // owner/org identity is explicit, then keep it on the engine so a
249
- // concurrent serverless run cannot make this call look unauthenticated.
250
- const engine = !deps.engine && resolvedEngine.name === "builder"
251
- ? createBuilderEngine({
252
- credentials: await resolveBuilderCredentialsDetailed({
253
- userEmail: ownerEmail,
254
- orgId,
255
- }),
256
- })
257
- : resolvedEngine;
258
252
  const modelCandidate = automation.meta.model ??
259
253
  deps.model ??
260
254
  (await getStoredModelForEngine(engine, { appId: deps.appId })) ??
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
137
137
  inputSchema: {
138
138
  type: string;
139
139
  properties: {
140
+ count?: undefined;
140
141
  query?: undefined;
141
142
  minutes?: undefined;
142
143
  limit?: undefined;
143
144
  clientHint?: undefined;
144
145
  timestamp?: undefined;
145
146
  chapterId?: undefined;
146
- count?: undefined;
147
147
  startAt?: undefined;
148
148
  endAt?: undefined;
149
149
  reason?: undefined;
@@ -159,6 +159,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
159
159
  inputSchema: {
160
160
  type: string;
161
161
  properties: {
162
+ count?: undefined;
162
163
  query: {
163
164
  type: string;
164
165
  description: string;
@@ -174,7 +175,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
174
175
  clientHint?: undefined;
175
176
  timestamp?: undefined;
176
177
  chapterId?: undefined;
177
- count?: undefined;
178
178
  startAt?: undefined;
179
179
  endAt?: undefined;
180
180
  reason?: undefined;
@@ -190,6 +190,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
190
190
  inputSchema: {
191
191
  type: string;
192
192
  properties: {
193
+ count?: undefined;
193
194
  minutes: {
194
195
  type: string;
195
196
  description: string;
@@ -199,7 +200,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
199
200
  clientHint?: undefined;
200
201
  timestamp?: undefined;
201
202
  chapterId?: undefined;
202
- count?: undefined;
203
203
  startAt?: undefined;
204
204
  endAt?: undefined;
205
205
  reason?: undefined;
@@ -216,6 +216,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
216
216
  type: string;
217
217
  required: string[];
218
218
  properties: {
219
+ count?: undefined;
219
220
  query: {
220
221
  type: string;
221
222
  description: string;
@@ -234,7 +235,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
234
235
  };
235
236
  timestamp?: undefined;
236
237
  chapterId?: undefined;
237
- count?: undefined;
238
238
  startAt?: undefined;
239
239
  endAt?: undefined;
240
240
  reason?: undefined;
@@ -250,6 +250,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
250
250
  type: string;
251
251
  required: string[];
252
252
  properties: {
253
+ count?: undefined;
253
254
  query?: undefined;
254
255
  minutes?: undefined;
255
256
  limit?: undefined;
@@ -263,7 +264,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
263
264
  description: string;
264
265
  };
265
266
  chapterId?: undefined;
266
- count?: undefined;
267
267
  startAt?: undefined;
268
268
  endAt?: undefined;
269
269
  includeMicrophone?: undefined;
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
314
314
  type: string;
315
315
  required: string[];
316
316
  properties: {
317
+ count?: undefined;
317
318
  query?: undefined;
318
319
  minutes?: undefined;
319
320
  limit?: undefined;
320
321
  clientHint?: undefined;
321
322
  timestamp?: undefined;
322
323
  chapterId?: undefined;
323
- count?: undefined;
324
324
  startAt: {
325
325
  type: string;
326
326
  description: string;
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
349
349
  type: string;
350
350
  required: string[];
351
351
  properties: {
352
+ count?: undefined;
352
353
  query?: undefined;
353
354
  minutes?: undefined;
354
355
  limit?: undefined;
355
356
  clientHint?: undefined;
356
357
  timestamp?: undefined;
357
358
  chapterId?: undefined;
358
- count?: undefined;
359
359
  startAt?: undefined;
360
360
  endAt?: undefined;
361
361
  reason?: undefined;