@agent-native/core 0.159.3 → 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 (90) 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/chat-first/app-pane.js +1 -1
  51. package/dist/client/chat-first/copy.js +1 -0
  52. package/dist/client/error-format.js +25 -2
  53. package/dist/client/sse-event-processor.d.ts +2 -0
  54. package/dist/client/sse-event-processor.js +12 -1
  55. package/dist/feature-flags/index.d.ts +0 -2
  56. package/dist/feature-flags/index.js +3 -2
  57. package/dist/feature-flags/server.d.ts +2 -0
  58. package/dist/feature-flags/server.js +2 -0
  59. package/dist/index.d.ts +2 -1
  60. package/dist/index.js +2 -1
  61. package/dist/jobs/background-automation-runner.js +9 -15
  62. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  63. package/dist/notifications/routes.d.ts +3 -3
  64. package/dist/observability/routes.d.ts +2 -2
  65. package/dist/progress/routes.d.ts +1 -1
  66. package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
  67. package/dist/resources/handlers.d.ts +1 -1
  68. package/dist/secrets/register-framework-secrets.js +25 -0
  69. package/dist/secrets/routes.d.ts +6 -6
  70. package/dist/server/agent-chat-plugin.js +9 -2
  71. package/dist/server/auth.js +10 -0
  72. package/dist/server/credential-provider.d.ts +80 -0
  73. package/dist/server/credential-provider.js +168 -1
  74. package/dist/server/google-realtime-session.d.ts +1 -1
  75. package/dist/server/google-realtime-session.js +6 -6
  76. package/dist/server/index.d.ts +2 -2
  77. package/dist/server/index.js +7 -2
  78. package/dist/server/onboarding-html.js +16 -257
  79. package/dist/server/realtime-token.d.ts +1 -1
  80. package/dist/server/realtime-voice.js +12 -6
  81. package/dist/server/transcribe-voice.d.ts +1 -1
  82. package/dist/server/transcribe-voice.js +18 -16
  83. package/dist/shared/auth-copy.d.ts +34 -0
  84. package/dist/shared/auth-copy.js +293 -0
  85. package/dist/shared/index.d.ts +1 -0
  86. package/dist/shared/index.js +1 -0
  87. package/dist/transcription/builder-transcription.js +8 -4
  88. package/docs/content/actions-advanced.mdx +1 -1
  89. package/docs/content/portal.mdx +21 -8
  90. package/package.json +3 -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
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Skeleton } from "@agent-native/toolkit/ui";
3
3
  import { defaultChatFirstCopy } from "./copy.js";
4
4
  export function ChatFirstAppPane({ app, status, embedUrl, errorMessage, onRetry, renderEmbed, copy = defaultChatFirstCopy, }) {
5
- return (_jsx("section", { "data-chat-first-app-pane": true, className: "flex h-full min-h-0 w-full flex-col overflow-hidden bg-background", "aria-label": app ? `${app.name} app` : copy("appUnavailable"), children: status === "loading" ? (_jsxs("div", { className: "space-y-3 p-4", children: [_jsx(Skeleton, { className: "h-5 w-40" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-2/3" })] })) : status === "ready" && app && embedUrl ? (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: renderEmbed({ url: embedUrl, title: app.name }) })) : status === "error" ? (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: errorMessage || copy("appUnavailable") }), onRetry ? (_jsx("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-xs text-foreground hover:bg-accent", onClick: onRetry, children: copy("retry") })) : null] })) : (_jsx("div", { className: "flex h-full items-center justify-center p-6 text-center text-sm text-muted-foreground", children: copy("appUnavailable") })) }));
5
+ return (_jsx("section", { "data-chat-first-app-pane": true, className: "flex h-full min-h-0 w-full flex-col overflow-hidden bg-background", "aria-label": app ? `${app.name} app` : copy("appUnavailable"), children: status === "loading" ? (_jsxs("div", { className: "space-y-3 p-4", children: [_jsx(Skeleton, { className: "h-5 w-40" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-2/3" })] })) : status === "ready" && app && embedUrl ? (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: renderEmbed({ url: embedUrl, title: app.name }) })) : status === "error" ? (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: errorMessage || copy("appLoadError") }), onRetry ? (_jsx("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-xs text-foreground hover:bg-accent", onClick: onRetry, children: copy("retry") })) : null] })) : (_jsx("div", { className: "flex h-full items-center justify-center p-6 text-center text-sm text-muted-foreground", children: copy("appUnavailable") })) }));
6
6
  }
@@ -44,6 +44,7 @@ const DEFAULT_COPY = {
44
44
  browserPreviewStarting: "Starting preview…",
45
45
  browserPreviewError: "Preview unavailable",
46
46
  appUnavailable: "This workspace app is no longer available.",
47
+ appLoadError: "This app could not be loaded",
47
48
  appLoading: "Loading app",
48
49
  agentActivityEyebrow: "Workspace",
49
50
  agentActivityTitle: "Agent activity",
@@ -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
@@ -1,4 +1,2 @@
1
1
  export { defineFeatureFlag, defineFeatureFlags, getFeatureFlagDefinition, listFeatureFlags, registerFeatureFlags, type FeatureFlagDefinition, } from "./registry.js";
2
2
  export { defaultFeatureFlagRules, evaluateFeatureFlag, evaluateFeatureFlagRules, hasActiveFeatureFlagRollout, isFeatureFlagEnabled, getFeatureFlagRules, normalizeFeatureFlagRules, type FeatureFlagMode, type FeatureFlagRules, type FeatureFlagScope, } from "./store.js";
3
- export { createFeatureFlagsPlugin } from "./plugin.js";
4
- export { createFeatureFlagA2AActionRouteAuth } from "./a2a-action-route.js";
@@ -1,4 +1,5 @@
1
1
  export { defineFeatureFlag, defineFeatureFlags, getFeatureFlagDefinition, listFeatureFlags, registerFeatureFlags, } from "./registry.js";
2
2
  export { defaultFeatureFlagRules, evaluateFeatureFlag, evaluateFeatureFlagRules, hasActiveFeatureFlagRollout, isFeatureFlagEnabled, getFeatureFlagRules, normalizeFeatureFlagRules, } from "./store.js";
3
- export { createFeatureFlagsPlugin } from "./plugin.js";
4
- export { createFeatureFlagA2AActionRouteAuth } from "./a2a-action-route.js";
3
+ // Plugin and A2A auth stay on `./server` and `@agent-native/core/server`.
4
+ // Re-exporting them here pulls HMAC Node builtins into any Vite client that
5
+ // imports this barrel for `defineFeatureFlag` / `isFeatureFlagEnabled`.
@@ -0,0 +1,2 @@
1
+ export { createFeatureFlagsPlugin } from "./plugin.js";
2
+ export { createFeatureFlagA2AActionRouteAuth } from "./a2a-action-route.js";
@@ -0,0 +1,2 @@
1
+ export { createFeatureFlagsPlugin } from "./plugin.js";
2
+ export { createFeatureFlagA2AActionRouteAuth } from "./a2a-action-route.js";
package/dist/index.d.ts CHANGED
@@ -11,7 +11,8 @@ export { createAgentChatPlugin, defaultAgentChatPlugin, type AgentChatPluginOpti
11
11
  export { createAgentNativeEmbeddedPlugin, mountAgentNativeEmbedded, type AgentNativeEmbeddedPluginOptions, } from "./server/embedded.js";
12
12
  export { BUILT_IN_INTEGRATION_CATALOG, INTEGRATION_CATEGORIES, getIntegrationCatalogEntry, listBuiltInChannelIntegrations, listIntegrationCatalog, type AutomationCapabilities, type BuiltInChannelId, type ChannelCapabilities, type IntegrationAvailability, type IntegrationCatalogEntry, type IntegrationCategory, type IntegrationCredentialRequirement, type IntegrationIconKey, type IntegrationSupportMaturity, } from "./integrations/catalog.js";
13
13
  export { AutomationConnectorError, createAutomationCallbackHandler, createAutomationRuntime, createInvokeAutomationWorkflowAction, type AutomationCallbackAuthentication, type AutomationCallbackInput, type AutomationCallbackResult, type AutomationInboundDefinition, type AutomationInvocation, type AutomationInvocationResult, type AutomationInvocationStatus, type AutomationOutboundDefinition, type AutomationResponseMode, type AutomationRetryPolicy, type AutomationRuntime, type AutomationRuntimeOptions, type AutomationWorkflowCapabilities, type AutomationWorkflowDefinition, type JsonSchema, } from "./automation/index.js";
14
- export { createFeatureFlagsPlugin, defineFeatureFlag, defineFeatureFlags, evaluateFeatureFlag, getFeatureFlagDefinition, getFeatureFlagRules, hasActiveFeatureFlagRollout, isFeatureFlagEnabled, listFeatureFlags, registerFeatureFlags, type FeatureFlagDefinition, type FeatureFlagMode, type FeatureFlagRules, type FeatureFlagScope, } from "./feature-flags/index.js";
14
+ export { defineFeatureFlag, defineFeatureFlags, evaluateFeatureFlag, getFeatureFlagDefinition, getFeatureFlagRules, hasActiveFeatureFlagRollout, isFeatureFlagEnabled, listFeatureFlags, registerFeatureFlags, type FeatureFlagDefinition, type FeatureFlagMode, type FeatureFlagRules, type FeatureFlagScope, } from "./feature-flags/index.js";
15
+ export { createFeatureFlagA2AActionRouteAuth, createFeatureFlagsPlugin, } from "./feature-flags/server.js";
15
16
  export { createServer, createSSEHandler, defineNitroPlugin, autoMountAuth, getSession, type CreateServerOptions, type SSEHandlerOptions, type AuthSession, type AuthOptions, } from "./server/index.js";
16
17
  export { agentChat, type AgentChatCallOptions, type AgentChatResponse, } from "./shared/index.js";
17
18
  export { EMBED_MODE_QUERY_PARAM, EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "./shared/embed-auth.js";
package/dist/index.js CHANGED
@@ -16,7 +16,8 @@ export { createAgentChatPlugin, defaultAgentChatPlugin, } from "./server/agent-c
16
16
  export { createAgentNativeEmbeddedPlugin, mountAgentNativeEmbedded, } from "./server/embedded.js";
17
17
  export { BUILT_IN_INTEGRATION_CATALOG, INTEGRATION_CATEGORIES, getIntegrationCatalogEntry, listBuiltInChannelIntegrations, listIntegrationCatalog, } from "./integrations/catalog.js";
18
18
  export { AutomationConnectorError, createAutomationCallbackHandler, createAutomationRuntime, createInvokeAutomationWorkflowAction, } from "./automation/index.js";
19
- export { createFeatureFlagsPlugin, defineFeatureFlag, defineFeatureFlags, evaluateFeatureFlag, getFeatureFlagDefinition, getFeatureFlagRules, hasActiveFeatureFlagRollout, isFeatureFlagEnabled, listFeatureFlags, registerFeatureFlags, } from "./feature-flags/index.js";
19
+ export { defineFeatureFlag, defineFeatureFlags, evaluateFeatureFlag, getFeatureFlagDefinition, getFeatureFlagRules, hasActiveFeatureFlagRollout, isFeatureFlagEnabled, listFeatureFlags, registerFeatureFlags, } from "./feature-flags/index.js";
20
+ export { createFeatureFlagA2AActionRouteAuth, createFeatureFlagsPlugin, } from "./feature-flags/server.js";
20
21
  // Server
21
22
  export { createServer, createSSEHandler, defineNitroPlugin, autoMountAuth, getSession, } from "./server/index.js";
22
23
  // Client
@@ -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 })) ??