@agent-native/core 0.159.5 → 0.160.1

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 (81) 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 +68 -31
  46. package/dist/agent/run-manager.js +30 -0
  47. package/dist/agent/thread-data-builder.js +4 -0
  48. package/dist/agent/tool-approval-migrations.d.ts +1 -0
  49. package/dist/agent/tool-approval-migrations.js +10 -0
  50. package/dist/agent/tool-approval-store.d.ts +5 -3
  51. package/dist/agent/tool-approval-store.js +11 -6
  52. package/dist/agent/types.d.ts +9 -0
  53. package/dist/cli/code-agent-connector.js +79 -7
  54. package/dist/cli/portal-transfer.d.ts +43 -0
  55. package/dist/cli/portal-transfer.js +201 -0
  56. package/dist/client/agent-chat-adapter.js +46 -15
  57. package/dist/client/error-format.js +25 -2
  58. package/dist/client/sse-event-processor.d.ts +2 -0
  59. package/dist/client/sse-event-processor.js +12 -1
  60. package/dist/collab/awareness.d.ts +2 -2
  61. package/dist/collab/struct-routes.d.ts +1 -1
  62. package/dist/jobs/background-automation-runner.js +9 -15
  63. package/dist/notifications/routes.d.ts +6 -6
  64. package/dist/observability/routes.d.ts +5 -5
  65. package/dist/resources/handlers.d.ts +1 -1
  66. package/dist/secrets/register-framework-secrets.js +25 -0
  67. package/dist/secrets/routes.d.ts +9 -9
  68. package/dist/server/agent-chat-plugin.js +9 -2
  69. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  70. package/dist/server/credential-provider.d.ts +80 -0
  71. package/dist/server/credential-provider.js +168 -1
  72. package/dist/server/google-realtime-session.d.ts +1 -1
  73. package/dist/server/google-realtime-session.js +6 -6
  74. package/dist/server/index.d.ts +1 -1
  75. package/dist/server/index.js +6 -1
  76. package/dist/server/realtime-token.d.ts +1 -1
  77. package/dist/server/realtime-voice.js +12 -6
  78. package/dist/server/transcribe-voice.d.ts +1 -1
  79. package/dist/server/transcribe-voice.js +18 -16
  80. package/dist/transcription/builder-transcription.js +8 -4
  81. package/package.json +3 -3
@@ -0,0 +1,201 @@
1
+ import { serializeBoundedRemoteJson } from "../integrations/remote-json-safety.js";
2
+ import { appendCodeAgentTranscriptEvent } from "./code-agent-runs.js";
3
+ export const PORTAL_TRANSFER_SCHEMA_VERSION = 1;
4
+ export const PORTAL_TRANSFER_MAX_CONTEXT_BYTES = 900_000;
5
+ const BINARY_FIELD_NAMES = new Set([
6
+ "base64",
7
+ "dataurl",
8
+ "image",
9
+ "imagebase64",
10
+ "imagedata",
11
+ "imagebytes",
12
+ "screenshot",
13
+ "screenshotbase64",
14
+ "screenshotdata",
15
+ "bytes",
16
+ "buffer",
17
+ ]);
18
+ export function createPortalTransferContext(input) {
19
+ const sourceRunId = requireString(input.sourceRunId, "source run id");
20
+ const context = {
21
+ schemaVersion: PORTAL_TRANSFER_SCHEMA_VERSION,
22
+ sourceRunId,
23
+ ...(input.sourceStatus
24
+ ? { sourceStatus: requireString(input.sourceStatus, "source status") }
25
+ : {}),
26
+ ...(input.sourcePhase
27
+ ? { sourcePhase: requireString(input.sourcePhase, "source phase") }
28
+ : {}),
29
+ events: input.events.map((event, index) => normalizeSourceEvent(event, index)),
30
+ };
31
+ serializeBoundedRemoteJson(context, {
32
+ label: "Portal transcript context",
33
+ maxBytes: PORTAL_TRANSFER_MAX_CONTEXT_BYTES,
34
+ });
35
+ return context;
36
+ }
37
+ export function parsePortalTransferContext(value) {
38
+ if (value === undefined || value === null)
39
+ return null;
40
+ if (!isRecord(value) || value.schemaVersion !== 1) {
41
+ throw new Error("Portal transfer context has an unsupported version.");
42
+ }
43
+ const sourceRunId = requireString(value.sourceRunId, "source run id");
44
+ if (!Array.isArray(value.events)) {
45
+ throw new Error("Portal transfer context is missing transcript events.");
46
+ }
47
+ const events = value.events.map((event, index) => {
48
+ if (!isRecord(event)) {
49
+ throw new Error(`Portal transcript event ${index + 1} is invalid.`);
50
+ }
51
+ return normalizeSourceEvent(event, index);
52
+ });
53
+ const sourceStatus = optionalString(value.sourceStatus);
54
+ const sourcePhase = optionalString(value.sourcePhase);
55
+ const context = {
56
+ schemaVersion: 1,
57
+ sourceRunId,
58
+ ...(sourceStatus ? { sourceStatus } : {}),
59
+ ...(sourcePhase ? { sourcePhase } : {}),
60
+ events,
61
+ };
62
+ serializeBoundedRemoteJson(context, {
63
+ label: "Portal transcript context",
64
+ maxBytes: PORTAL_TRANSFER_MAX_CONTEXT_BYTES,
65
+ });
66
+ return context;
67
+ }
68
+ export function appendPortalTransferTranscript(context, runId) {
69
+ const targetRunId = requireString(runId, "target run id");
70
+ let appended = 0;
71
+ for (const event of context.events) {
72
+ appendCodeAgentTranscriptEvent({
73
+ id: event.id,
74
+ runId: targetRunId,
75
+ kind: event.kind,
76
+ message: event.message,
77
+ createdAt: event.createdAt,
78
+ ...(event.metadata
79
+ ? {
80
+ metadata: {
81
+ ...event.metadata,
82
+ portalTransfer: {
83
+ sourceRunId: context.sourceRunId,
84
+ sourceEventId: event.id,
85
+ },
86
+ },
87
+ }
88
+ : {
89
+ metadata: {
90
+ portalTransfer: {
91
+ sourceRunId: context.sourceRunId,
92
+ sourceEventId: event.id,
93
+ },
94
+ },
95
+ }),
96
+ ...(event.signal ? { signal: event.signal } : {}),
97
+ });
98
+ appended++;
99
+ }
100
+ return appended;
101
+ }
102
+ export function portalTransferContinuationPrompt(input) {
103
+ return [
104
+ "[Portal session continuation]",
105
+ `This coding session was moved to ${requireString(input.hostLabel, "Portal host label")}.`,
106
+ `Portal handoff: ${requireString(input.handoffId, "Portal handoff id")}`,
107
+ `The preceding ${input.eventCount} transcript event${input.eventCount === 1 ? "" : "s"} came from the original computer and is part of this session context.`,
108
+ "Review the transferred transcript and Portal workspace before acting. Continue the unfinished task from the latest state, and do not repeat work that is already complete.",
109
+ ].join("\n");
110
+ }
111
+ function normalizeSourceEvent(value, index) {
112
+ const id = requireString(value.id, `transcript event ${index + 1} id`);
113
+ const kind = normalizeKind(value.kind ?? value.type, index);
114
+ const messageValue = value.message ?? value.text;
115
+ if (typeof messageValue !== "string") {
116
+ throw new Error(`Portal transcript event ${index + 1} is missing text.`);
117
+ }
118
+ const createdAt = requireString(value.createdAt, `transcript event ${index + 1} timestamp`);
119
+ const metadata = sanitizeMetadata(value.metadata);
120
+ const signal = value.signal === "credential-gap" ? "credential-gap" : undefined;
121
+ return {
122
+ schemaVersion: 1,
123
+ id,
124
+ kind,
125
+ message: messageValue,
126
+ createdAt,
127
+ ...(metadata ? { metadata } : {}),
128
+ ...(signal ? { signal } : {}),
129
+ };
130
+ }
131
+ function normalizeKind(value, index) {
132
+ const kind = typeof value === "string" ? value.trim().toLowerCase() : "";
133
+ if (kind === "user" ||
134
+ kind === "system" ||
135
+ kind === "note" ||
136
+ kind === "artifact" ||
137
+ kind === "status") {
138
+ return kind;
139
+ }
140
+ if (kind === "assistant" || kind === "human" || kind === "prompt") {
141
+ return kind === "assistant" ? "system" : "user";
142
+ }
143
+ throw new Error(`Portal transcript event ${index + 1} has an invalid kind.`);
144
+ }
145
+ function sanitizeMetadata(value) {
146
+ if (!isRecord(value))
147
+ return undefined;
148
+ const sanitized = sanitizeObject(value);
149
+ return Object.keys(sanitized).length > 0 ? sanitized : undefined;
150
+ }
151
+ function sanitizeObject(value) {
152
+ const result = {};
153
+ let binaryOmitted = false;
154
+ for (const [key, child] of Object.entries(value)) {
155
+ if (isBinaryFieldName(key)) {
156
+ binaryOmitted = true;
157
+ continue;
158
+ }
159
+ const sanitized = sanitizeValue(child);
160
+ if (sanitized !== undefined)
161
+ result[key] = sanitized;
162
+ }
163
+ if (binaryOmitted)
164
+ result.binaryContentOmitted = true;
165
+ return result;
166
+ }
167
+ function sanitizeValue(value) {
168
+ if (value === null ||
169
+ typeof value === "string" ||
170
+ typeof value === "number" ||
171
+ typeof value === "boolean") {
172
+ return value;
173
+ }
174
+ if (Array.isArray(value)) {
175
+ return value
176
+ .map((item) => sanitizeValue(item))
177
+ .filter((item) => item !== undefined);
178
+ }
179
+ if (isRecord(value))
180
+ return sanitizeObject(value);
181
+ return undefined;
182
+ }
183
+ function isBinaryFieldName(key) {
184
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
185
+ return BINARY_FIELD_NAMES.has(normalized);
186
+ }
187
+ function isRecord(value) {
188
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
189
+ }
190
+ function requireString(value, label) {
191
+ const result = optionalString(value);
192
+ if (!result)
193
+ throw new Error(`Portal ${label} is missing.`);
194
+ return result;
195
+ }
196
+ function optionalString(value) {
197
+ if (typeof value !== "string")
198
+ return undefined;
199
+ const result = value.trim();
200
+ return result || undefined;
201
+ }
@@ -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;
@@ -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 })) ??
@@ -11,23 +11,23 @@
11
11
  * DELETE /_agent-native/notifications/:id — delete
12
12
  */
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
- error?: undefined;
15
14
  count: number;
16
15
  updated?: undefined;
16
+ error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
- error?: undefined;
19
+ count?: undefined;
20
20
  updated: number;
21
+ error?: undefined;
21
22
  ok?: undefined;
22
- count?: undefined;
23
23
  } | {
24
+ count?: undefined;
24
25
  updated?: undefined;
25
26
  error: string;
26
27
  ok?: undefined;
27
- count?: undefined;
28
28
  } | {
29
- error?: undefined;
29
+ count?: undefined;
30
30
  updated?: undefined;
31
+ error?: undefined;
31
32
  ok: boolean;
32
- count?: undefined;
33
33
  }>>;
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
+ error?: undefined;
45
+ ok?: undefined;
44
46
  summary: import("./types.js").TraceSummary;
45
47
  spans: import("./types.js").TraceSpan[];
46
48
  id?: undefined;
49
+ } | {
47
50
  error?: undefined;
48
51
  ok?: undefined;
49
- } | {
50
52
  summary?: undefined;
51
53
  spans?: undefined;
52
54
  id: string;
53
- error?: undefined;
54
- ok?: undefined;
55
55
  } | {
56
+ ok?: undefined;
56
57
  summary?: undefined;
57
58
  spans?: undefined;
58
59
  id?: undefined;
59
60
  error: any;
60
- ok?: undefined;
61
61
  } | {
62
+ error?: undefined;
62
63
  summary?: undefined;
63
64
  spans?: undefined;
64
65
  id?: undefined;
65
66
  ok: boolean;
66
- error?: undefined;
67
67
  }>>;
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- ok?: undefined;
52
51
  error: string;
52
+ ok?: undefined;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -107,6 +107,31 @@ export function registerFrameworkSecrets() {
107
107
  });
108
108
  }
109
109
  }
110
+ if (!getRequiredSecret("ANTHROPIC_API_KEY")) {
111
+ registerRequiredSecret({
112
+ key: "ANTHROPIC_API_KEY",
113
+ label: "Anthropic API key",
114
+ description: "Bring your own Claude key instead of routing model calls through Builder.io.",
115
+ docsUrl: "https://console.anthropic.com/settings/keys",
116
+ scope: "user",
117
+ kind: "api-key",
118
+ required: false,
119
+ validator: async (value) => {
120
+ const response = await fetch("https://api.anthropic.com/v1/models", {
121
+ headers: {
122
+ "x-api-key": value,
123
+ "anthropic-version": "2023-06-01",
124
+ },
125
+ });
126
+ return response.ok
127
+ ? { ok: true }
128
+ : {
129
+ ok: false,
130
+ error: `Anthropic rejected the key (HTTP ${response.status}).`,
131
+ };
132
+ },
133
+ });
134
+ }
110
135
  if (!getRequiredSecret("OPENAI_API_KEY")) {
111
136
  registerRequiredSecret({
112
137
  key: "OPENAI_API_KEY",
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- status?: undefined;
38
37
  ok?: undefined;
38
+ status?: undefined;
39
39
  } | {
40
+ error?: undefined;
40
41
  ok: boolean;
41
42
  status: string;
42
- error?: undefined;
43
43
  } | {
44
+ ok?: undefined;
44
45
  error: string;
45
46
  removed?: undefined;
46
- ok?: undefined;
47
47
  } | {
48
+ error?: undefined;
48
49
  ok: boolean;
49
50
  removed: boolean;
50
- error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
54
54
  * or the current stored value without changing anything.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
+ ok?: undefined;
57
58
  error: string;
58
59
  note?: undefined;
59
- ok?: undefined;
60
60
  } | {
61
+ error?: undefined;
61
62
  ok: boolean;
62
63
  note?: undefined;
63
- error?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  ok: boolean;
66
67
  note: string;
67
- error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
+ error?: undefined;
98
99
  ok: boolean;
99
100
  key: string;
100
- error?: undefined;
101
101
  } | {
102
+ error?: undefined;
102
103
  ok: boolean;
103
104
  removed: boolean;
104
- error?: undefined;
105
105
  }>>;