@agent-native/core 0.0.0-beta-20260819181349 → 0.0.0-beta-20260819183007

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 (126) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
  3. package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
  4. package/dist/a2a/task-store.d.ts +1 -0
  5. package/dist/a2a/task-store.js +1 -1
  6. package/dist/agent/observational-memory/store.d.ts +1 -0
  7. package/dist/agent/observational-memory/store.js +1 -1
  8. package/dist/agent/run-store.d.ts +1 -0
  9. package/dist/agent/run-store.js +1 -1
  10. package/dist/agent/tool-approval-store.d.ts +1 -0
  11. package/dist/agent/tool-approval-store.js +1 -1
  12. package/dist/application-state/store.d.ts +1 -0
  13. package/dist/application-state/store.js +1 -1
  14. package/dist/browser-sessions/store.d.ts +1 -0
  15. package/dist/browser-sessions/store.js +1 -1
  16. package/dist/checkpoints/store.d.ts +1 -0
  17. package/dist/checkpoints/store.js +1 -1
  18. package/dist/client/agent-chat-adapter.js +22 -10
  19. package/dist/client/chat/message-components.d.ts +4 -0
  20. package/dist/client/chat/message-components.js +28 -1
  21. package/dist/client/chat/run-recovery.js +26 -0
  22. package/dist/client/sse-event-processor.js +13 -8
  23. package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
  24. package/dist/coding-tools/sandbox/executions-store.js +1 -1
  25. package/dist/collab/awareness-store.d.ts +1 -0
  26. package/dist/collab/awareness-store.js +1 -1
  27. package/dist/collab/awareness.d.ts +2 -2
  28. package/dist/collab/routes.d.ts +1 -1
  29. package/dist/collab/storage.d.ts +1 -0
  30. package/dist/collab/storage.js +1 -1
  31. package/dist/collab/struct-routes.d.ts +1 -1
  32. package/dist/db/client.js +3 -3
  33. package/dist/db/ddl-guard.js +8 -0
  34. package/dist/db/migration-runtime.d.ts +23 -0
  35. package/dist/db/migration-runtime.js +41 -0
  36. package/dist/db/migrations.d.ts +1 -2
  37. package/dist/db/migrations.js +2 -24
  38. package/dist/email-catalog/log.d.ts +1 -0
  39. package/dist/email-catalog/log.js +1 -1
  40. package/dist/guards/index.d.ts +1 -0
  41. package/dist/guards/index.js +1 -0
  42. package/dist/guards/release-schema-complete.d.ts +22 -0
  43. package/dist/guards/release-schema-complete.js +144 -0
  44. package/dist/integrations/a2a-continuations-store.d.ts +1 -0
  45. package/dist/integrations/a2a-continuations-store.js +1 -1
  46. package/dist/integrations/awaiting-input-store.d.ts +1 -0
  47. package/dist/integrations/awaiting-input-store.js +1 -1
  48. package/dist/integrations/config-store.d.ts +1 -0
  49. package/dist/integrations/config-store.js +1 -1
  50. package/dist/integrations/controls-store.d.ts +1 -0
  51. package/dist/integrations/controls-store.js +1 -1
  52. package/dist/integrations/identity-links-store.d.ts +1 -0
  53. package/dist/integrations/identity-links-store.js +1 -1
  54. package/dist/integrations/installations-store.d.ts +1 -0
  55. package/dist/integrations/installations-store.js +1 -1
  56. package/dist/integrations/remote-commands-store.d.ts +1 -0
  57. package/dist/integrations/remote-commands-store.js +1 -1
  58. package/dist/integrations/remote-devices-store.d.ts +1 -0
  59. package/dist/integrations/remote-devices-store.js +1 -1
  60. package/dist/integrations/remote-push-store.d.ts +1 -0
  61. package/dist/integrations/remote-push-store.js +1 -1
  62. package/dist/integrations/remote-run-events-store.d.ts +1 -0
  63. package/dist/integrations/remote-run-events-store.js +1 -1
  64. package/dist/integrations/scope-store.d.ts +1 -0
  65. package/dist/integrations/scope-store.js +1 -1
  66. package/dist/integrations/thread-mapping-store.d.ts +1 -0
  67. package/dist/integrations/thread-mapping-store.js +1 -1
  68. package/dist/integrations/usage-budget-store.d.ts +1 -0
  69. package/dist/integrations/usage-budget-store.js +1 -1
  70. package/dist/jobs/run-history.d.ts +1 -0
  71. package/dist/jobs/run-history.js +1 -1
  72. package/dist/jobs/scheduler-health.d.ts +1 -0
  73. package/dist/jobs/scheduler-health.js +1 -1
  74. package/dist/mcp/approval-store.d.ts +1 -0
  75. package/dist/mcp/approval-store.js +1 -1
  76. package/dist/mcp/connect-store.d.ts +1 -0
  77. package/dist/mcp/connect-store.js +1 -1
  78. package/dist/mcp/oauth-store.d.ts +1 -0
  79. package/dist/mcp/oauth-store.js +1 -1
  80. package/dist/notifications/routes.d.ts +3 -3
  81. package/dist/notifications/store.d.ts +1 -0
  82. package/dist/notifications/store.js +1 -1
  83. package/dist/oauth-tokens/store.d.ts +1 -0
  84. package/dist/oauth-tokens/store.js +1 -1
  85. package/dist/observability/routes.d.ts +5 -5
  86. package/dist/progress/store.d.ts +1 -0
  87. package/dist/progress/store.js +1 -1
  88. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  89. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  90. package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
  91. package/dist/provider-api/corpus-jobs-store.js +1 -1
  92. package/dist/provider-api/custom-registry.d.ts +1 -0
  93. package/dist/provider-api/custom-registry.js +1 -1
  94. package/dist/provider-api/quota-governor.d.ts +1 -0
  95. package/dist/provider-api/quota-governor.js +1 -1
  96. package/dist/provider-api/staged-datasets-store.d.ts +1 -0
  97. package/dist/provider-api/staged-datasets-store.js +1 -1
  98. package/dist/resources/handlers.d.ts +1 -1
  99. package/dist/resources/store.d.ts +1 -0
  100. package/dist/resources/store.js +1 -1
  101. package/dist/search/index.js +3 -0
  102. package/dist/secrets/routes.d.ts +9 -9
  103. package/dist/secrets/storage.d.ts +1 -0
  104. package/dist/secrets/storage.js +1 -1
  105. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  106. package/dist/server/agent-teams-run-queue.d.ts +1 -0
  107. package/dist/server/agent-teams-run-queue.js +1 -1
  108. package/dist/server/auth.d.ts +1 -0
  109. package/dist/server/auth.js +1 -1
  110. package/dist/server/embed-session.d.ts +1 -0
  111. package/dist/server/embed-session.js +1 -1
  112. package/dist/server/identity-sso-store.d.ts +1 -0
  113. package/dist/server/identity-sso-store.js +1 -1
  114. package/dist/server/release-migrations.js +5 -0
  115. package/dist/server/release-schema.d.ts +46 -0
  116. package/dist/server/release-schema.js +307 -0
  117. package/dist/settings/store.d.ts +1 -0
  118. package/dist/settings/store.js +1 -1
  119. package/dist/styles/agent-native.css +17 -15
  120. package/dist/triggers/routes.d.ts +11 -0
  121. package/dist/triggers/routes.js +10 -0
  122. package/dist/usage/alerts-store.d.ts +1 -0
  123. package/dist/usage/alerts-store.js +1 -1
  124. package/dist/usage/store.d.ts +1 -0
  125. package/dist/usage/store.js +1 -1
  126. package/package.json +3 -3
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8458
34
+ - template files: 8459
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  SIDEBAR_STATE_CHANGE_EVENT,
3
- BuilderSetupCard,
4
3
  sendToAgentChat,
5
4
  setAgentChatContextItem,
6
5
  useAgentEngineConfigured,
@@ -8466,23 +8465,12 @@ function CreatePlanDialog({
8466
8465
  </DialogDescription>
8467
8466
  </DialogHeader>
8468
8467
  <div className="grid gap-3">
8469
- {canCreate && agentMissing ? (
8470
- <BuilderSetupCard
8471
- fullWidth
8472
- onConnected={() =>
8473
- window.dispatchEvent(
8474
- new Event("agent-engine:configured-changed"),
8475
- )
8476
- }
8477
- />
8478
- ) : null}
8479
8468
  <div className="rounded-xl border border-border bg-background p-2 shadow-sm">
8480
8469
  <PromptComposer
8481
8470
  autoFocus
8482
8471
  disabled={composerLocked}
8483
8472
  attachmentsEnabled={false}
8484
8473
  showModelSelector={false}
8485
- modelStatusChecksEnabled={false}
8486
8474
  placeholder={t("plansPage.create.placeholder")}
8487
8475
  draftScope="plans:create-plan"
8488
8476
  initialText={promptSeed}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-19
4
+ ---
5
+
6
+ Plan prompts now guide you to connect AI before sending and offer a clear retry after setup.
@@ -1,6 +1,7 @@
1
1
  import type { Task, Message, TaskState, Artifact } from "./types.js";
2
2
  export declare const MAX_A2A_IDEMPOTENCY_KEY_CHARS = 128;
3
3
  export declare const A2A_PERSONAL_OWNER_SCOPE = "__personal__";
4
+ export declare function ensureTable(): Promise<void>;
4
5
  export interface A2AApprovalRecord {
5
6
  id: string;
6
7
  taskId: string;
@@ -5,7 +5,7 @@ let _initPromise;
5
5
  export const MAX_A2A_IDEMPOTENCY_KEY_CHARS = 128;
6
6
  const A2A_IDEMPOTENCY_INDEX = "idx_a2a_tasks_owner_scope_idempotency";
7
7
  export const A2A_PERSONAL_OWNER_SCOPE = "__personal__";
8
- async function ensureTable() {
8
+ export async function ensureTable() {
9
9
  if (!_initPromise) {
10
10
  _initPromise = (async () => {
11
11
  const client = getDbExec();
@@ -12,6 +12,7 @@
12
12
  * runtime even before the migration plugin is registered.
13
13
  */
14
14
  import type { ObservationalMemoryEntry, ObservationalMemoryOwner, ObservationalMemoryTier } from "./types.js";
15
+ export declare function ensureTable(): Promise<void>;
15
16
  /** Reset the cached ensureTable promise — test-only seam. */
16
17
  export declare function __resetObservationalMemoryTableCache(): void;
17
18
  export interface InsertObservationalMemoryInput extends ObservationalMemoryOwner {
@@ -14,7 +14,7 @@
14
14
  import { getDbExec, isPostgres } from "../../db/client.js";
15
15
  import { ensureTableExists, ensureIndexExists } from "../../db/ddl-guard.js";
16
16
  let tableReady = null;
17
- async function ensureTable() {
17
+ export async function ensureTable() {
18
18
  if (tableReady)
19
19
  return tableReady;
20
20
  tableReady = (async () => {
@@ -217,6 +217,7 @@ export declare const IN_FLIGHT_RUN_STALE_GRACE_MS: number;
217
217
  * is reaped like any other stale run.
218
218
  */
219
219
  export declare const IN_FLIGHT_GRACE_MAX_LIVENESS_GAP_MS = 120000;
220
+ export declare function ensureRunTables(): Promise<void>;
220
221
  /**
221
222
  * Persist a zombie tool-call completion to the ledger. Called by the detached
222
223
  * promise continuation after `Promise.race` abandons it. Best-effort — never
@@ -294,7 +294,7 @@ export const IN_FLIGHT_RUN_STALE_GRACE_MS = 14.5 * 60_000; // 870_000
294
294
  * is reaped like any other stale run.
295
295
  */
296
296
  export const IN_FLIGHT_GRACE_MAX_LIVENESS_GAP_MS = 120_000;
297
- async function ensureRunTables() {
297
+ export async function ensureRunTables() {
298
298
  if (!_initPromise) {
299
299
  _initPromise = (async () => {
300
300
  const client = getDbExec();
@@ -1,3 +1,4 @@
1
+ export declare function ensureAgentToolApprovalTable(): Promise<void>;
1
2
  export interface AgentToolApprovalBinding {
2
3
  ownerEmail: string;
3
4
  orgId?: string | null;
@@ -11,7 +11,7 @@ import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, A
11
11
  const APPROVAL_TTL_MS = 60 * 60_000;
12
12
  const APPROVAL_CLEANUP_AGE_MS = 24 * 60 * 60_000;
13
13
  let initPromise;
14
- async function ensureAgentToolApprovalTable() {
14
+ export async function ensureAgentToolApprovalTable() {
15
15
  if (!initPromise) {
16
16
  initPromise = (async () => {
17
17
  const createSql = AGENT_TOOL_APPROVAL_TABLE_SQL[isPostgres() ? "postgres" : "sqlite"];
@@ -1,4 +1,5 @@
1
1
  import type { StoreWriteOptions } from "../settings/store.js";
2
+ export declare function ensureTable(): Promise<void>;
2
3
  export declare function appStateGet(sessionId: string, key: string): Promise<Record<string, unknown> | null>;
3
4
  /**
4
5
  * Read several application-state keys for one session in a single SQL query,
@@ -11,7 +11,7 @@ const MAX_HOSTED_APP_STATE_VALUE_BYTES = 1024 * 1024;
11
11
  function escapeLike(s) {
12
12
  return s.replace(/[!%_]/g, (match) => `!${match}`);
13
13
  }
14
- async function ensureTable() {
14
+ export async function ensureTable() {
15
15
  if (!_initPromise) {
16
16
  _initPromise = (async () => {
17
17
  const client = getDbExec();
@@ -2,6 +2,7 @@ import type { AgentNativeBrowserSessionRecord, AgentNativeBrowserSessionRequest,
2
2
  export declare const DEFAULT_BROWSER_SESSION_TTL_MS = 45000;
3
3
  export declare const DEFAULT_BROWSER_SESSION_REQUEST_TIMEOUT_MS = 30000;
4
4
  export declare const DEFAULT_BROWSER_SESSION_REQUEST_POLL_MS = 250;
5
+ export declare function ensureTables(): Promise<void>;
5
6
  export declare function registerBrowserSession(ownerEmailInput: string, input: RegisterAgentNativeBrowserSessionInput): Promise<AgentNativeBrowserSessionRecord>;
6
7
  export declare function listBrowserSessions(ownerEmailInput: string, options?: {
7
8
  includeExpired?: boolean;
@@ -13,7 +13,7 @@ function nowMs() {
13
13
  function sleep(ms) {
14
14
  return new Promise((resolve) => setTimeout(resolve, ms));
15
15
  }
16
- async function ensureTables() {
16
+ export async function ensureTables() {
17
17
  if (!initPromise) {
18
18
  initPromise = (async () => {
19
19
  const client = getDbExec();
@@ -1,3 +1,4 @@
1
+ export declare function ensureCheckpointTable(): Promise<void>;
1
2
  export declare function insertCheckpoint(id: string, threadId: string, runId: string | null, commitSha: string, message: string): Promise<void>;
2
3
  export declare function getCheckpointsByThread(threadId: string): Promise<Array<{
3
4
  id: string;
@@ -1,7 +1,7 @@
1
1
  import { getDbExec, intType, isPostgres } from "../db/client.js";
2
2
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
3
3
  let _initPromise;
4
- async function ensureCheckpointTable() {
4
+ export async function ensureCheckpointTable() {
5
5
  if (!_initPromise) {
6
6
  _initPromise = (async () => {
7
7
  const client = getDbExec();
@@ -1429,6 +1429,16 @@ function isMissingCredentialMessage(message) {
1429
1429
  msg.includes("no llm provider") ||
1430
1430
  msg.includes("llm provider is connected"));
1431
1431
  }
1432
+ function isMissingProviderErrorMessage(message, errorCode) {
1433
+ const text = message.toLowerCase();
1434
+ const code = (errorCode ?? "").toLowerCase();
1435
+ return (text.includes("no llm provider") ||
1436
+ text.includes("missing credentials") ||
1437
+ text.includes("missing api key") ||
1438
+ text.includes("missing_api_key") ||
1439
+ ((code === "missing_credentials" || code === "missing_api_key") &&
1440
+ text.includes("llm provider")));
1441
+ }
1432
1442
  function missingCredentialFailure(message) {
1433
1443
  try {
1434
1444
  const parsed = JSON.parse(message);
@@ -1439,9 +1449,10 @@ function missingCredentialFailure(message) {
1439
1449
  : message;
1440
1450
  const errorCode = typeof parsed.errorCode === "string"
1441
1451
  ? parsed.errorCode
1442
- : "missing_credentials";
1452
+ : isMissingCredentialMessage(raw)
1453
+ ? "missing_credentials"
1454
+ : "authentication_error";
1443
1455
  return {
1444
- text: formatChatErrorText(raw, typeof parsed.upgradeUrl === "string" ? parsed.upgradeUrl : undefined, errorCode),
1445
1456
  runError: {
1446
1457
  message: normalizeChatError(raw).message,
1447
1458
  errorCode,
@@ -1450,10 +1461,11 @@ function missingCredentialFailure(message) {
1450
1461
  }
1451
1462
  catch {
1452
1463
  return {
1453
- text: formatChatErrorText(message, undefined, "missing_credentials"),
1454
1464
  runError: {
1455
1465
  message: normalizeChatError(message).message,
1456
- errorCode: "missing_credentials",
1466
+ errorCode: isMissingCredentialMessage(message)
1467
+ ? "missing_credentials"
1468
+ : "authentication_error",
1457
1469
  },
1458
1470
  };
1459
1471
  }
@@ -2290,10 +2302,12 @@ export function createAgentChatAdapter(options) {
2290
2302
  settleInterruptedToolCalls(content, undefined, {
2291
2303
  includeActivity: true,
2292
2304
  });
2293
- content.push({
2294
- type: "text",
2295
- text: formatChatErrorText(args.message, undefined, args.errorCode),
2296
- });
2305
+ if (!isMissingProviderErrorMessage(args.message, args.errorCode)) {
2306
+ content.push({
2307
+ type: "text",
2308
+ text: formatChatErrorText(args.message, undefined, args.errorCode),
2309
+ });
2310
+ }
2297
2311
  settleTerminalChatRun();
2298
2312
  yield {
2299
2313
  content: [...content],
@@ -3351,7 +3365,6 @@ export function createAgentChatAdapter(options) {
3351
3365
  detail: { ...failure.runError, tabId },
3352
3366
  }));
3353
3367
  }
3354
- content.push({ type: "text", text: failure.text });
3355
3368
  settleTerminalChatRun();
3356
3369
  yield {
3357
3370
  content: [...content],
@@ -3664,7 +3677,6 @@ export function createAgentChatAdapter(options) {
3664
3677
  detail: { ...failure.runError, tabId },
3665
3678
  }));
3666
3679
  }
3667
- content.push({ type: "text", text: failure.text });
3668
3680
  settleTerminalChatRun();
3669
3681
  yield {
3670
3682
  content: [...content],
@@ -71,6 +71,10 @@ export declare function MessageActionsMenu({ showRevert, onRevert, threadId, }?:
71
71
  export declare function UserMessage(): React.JSX.Element | null;
72
72
  export declare function messageTextFromContent(content: unknown): string;
73
73
  export declare function isMissingFinalResponseWarningText(text: string): boolean;
74
+ export declare function isMissingCredentialAssistantMessage(message: {
75
+ content?: unknown;
76
+ metadata?: unknown;
77
+ }): boolean;
74
78
  export declare function latestUserMessageText(messages: readonly unknown[]): string;
75
79
  export declare function userMessageTextBeforeAssistant(messages: readonly unknown[], assistantMessageId: string): string;
76
80
  export declare function assistantMessageHasUnresolvedTool(content: unknown): boolean;
@@ -539,6 +539,32 @@ function missingFinalResponseWarningText(content) {
539
539
  }
540
540
  return null;
541
541
  }
542
+ export function isMissingCredentialAssistantMessage(message) {
543
+ const metadata = message.metadata;
544
+ const runError = metadata?.custom?.runError ?? metadata?.runError;
545
+ const errorCode = typeof runError?.errorCode === "string"
546
+ ? runError.errorCode.toLowerCase()
547
+ : "";
548
+ const errorMessage = typeof runError?.message === "string" ? runError.message : "";
549
+ if (/no llm provider(?: key)? (?:is connected|was found)|missing credentials|missing api key|missing_api_key/i.test(errorMessage) ||
550
+ ((errorCode === "missing_credentials" || errorCode === "missing_api_key") &&
551
+ !errorMessage &&
552
+ !message.content)) {
553
+ return true;
554
+ }
555
+ const text = Array.isArray(message.content)
556
+ ? message.content
557
+ .filter((part) => Boolean(part) &&
558
+ typeof part === "object" &&
559
+ part.type === "text" &&
560
+ typeof part.text === "string")
561
+ .map((part) => part.text)
562
+ .join("\n")
563
+ : typeof message.content === "string"
564
+ ? message.content
565
+ : "";
566
+ return /^Error:\s*(?:No LLM provider(?: key)? (?:is connected|was found))/i.test(text.trim());
567
+ }
542
568
  export function latestUserMessageText(messages) {
543
569
  for (let index = messages.length - 1; index >= 0; index -= 1) {
544
570
  const message = messages[index];
@@ -847,6 +873,7 @@ export function AssistantMessage() {
847
873
  const activeTurnId = React.useContext(ChatRunningTurnIdContext);
848
874
  const lastRunDurationMs = React.useContext(ChatRunDurationContext);
849
875
  const msg = messageRuntime.getState();
876
+ const isMissingCredential = isMissingCredentialAssistantMessage(msg);
850
877
  const persistedDurationMs = getAssistantRunDurationMs(msg);
851
878
  const timestamp = formatMessageTimestamp(msg.createdAt, locale, t("agentChat.history.yesterday"));
852
879
  const isLast = msg.isLast;
@@ -1052,7 +1079,7 @@ export function AssistantMessage() {
1052
1079
  chatRunning,
1053
1080
  isLast,
1054
1081
  });
1055
- if (!hasRenderableContent)
1082
+ if (!hasRenderableContent || isMissingCredential)
1056
1083
  return null;
1057
1084
  return (_jsxs("div", { className: "group relative", style: { contentVisibility: isComplete ? "auto" : "visible" }, children: [_jsxs("div", { className: "w-full max-w-[95%] text-sm leading-relaxed text-foreground", children: [isComplete && (_jsx(McpConnectionSuggestion, { text: responseConnectionText, contextText: responseConnectionContext, variant: "response" })), _jsx(ToolCallStackMotion, { children: _jsx(MessagePrimitive.GroupedParts, { groupBy: groupAssistantWorkParts, children: ({ part, children }) => {
1058
1085
  switch (part.type) {
@@ -112,6 +112,14 @@ function isConnectionRecoveryRunError(info) {
112
112
  message.includes("connection kept failing") ||
113
113
  message.includes("automatic recovery attempts"));
114
114
  }
115
+ function isMissingLlmProviderRunError(info) {
116
+ const code = (info.errorCode ?? "").toLowerCase();
117
+ const text = [info.message, info.details].filter(Boolean).join("\n");
118
+ const hasCredentialSetupText = /no llm provider(?: key)? (?:is connected|was found)|missing credentials|missing api key|missing_api_key|(?:api[_ -]?key|auth[_ -]?token)\s*(?:(?:is|was)\s+)?(?:not\s+(?:set|configured|available|present|provided|found)|missing|unavailable|empty|unset|unconfigured)/i.test(text);
119
+ return (hasCredentialSetupText ||
120
+ ((code === "missing_credentials" || code === "missing_api_key") &&
121
+ !text.trim()));
122
+ }
115
123
  // ─── BuilderConnectCta ────────────────────────────────────────────────────────
116
124
  // Renders a single row with left-aligned copy and a right-aligned action.
117
125
  // Click opens the Builder OAuth popup via the shared
@@ -194,11 +202,15 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
194
202
  const [copyState, setCopyState] = useState("idle");
195
203
  const [forking, setForking] = useState(false);
196
204
  const [forkError, setForkError] = useState(null);
205
+ const [providerConnected, setProviderConnected] = useState(false);
206
+ const retryRequestedRef = useRef(false);
207
+ const [retryRequested, setRetryRequested] = useState(false);
197
208
  const builderReconnect = useBuilderConnectFlow({
198
209
  trackingSource: "assistant_chat_reconnect_error",
199
210
  });
200
211
  const canRecover = info.recoverable === true;
201
212
  const shouldShowBuilderReconnect = isBuilderReconnectRunError(info);
213
+ const shouldShowMissingProviderSetup = isMissingLlmProviderRunError(info);
202
214
  const isProviderAuthError = isProviderAuthenticationError([info.message, info.details].filter(Boolean).join("\n"), info.errorCode);
203
215
  // Blocked on something the reader goes and fixes elsewhere, then comes back
204
216
  // to. Without a retry the card is a dead end and its own copy ("then retry")
@@ -241,6 +253,17 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
241
253
  onRetry();
242
254
  onDismiss();
243
255
  }, [onDismiss, onProviderConnected, onRetry]);
256
+ const handleMissingProviderConnected = useCallback(() => {
257
+ setProviderConnected(true);
258
+ onProviderConnected?.();
259
+ }, [onProviderConnected]);
260
+ const handleMissingProviderRetry = useCallback(() => {
261
+ if (retryRequestedRef.current)
262
+ return;
263
+ retryRequestedRef.current = true;
264
+ setRetryRequested(true);
265
+ onRetry();
266
+ }, [onRetry]);
244
267
  const handleFork = useCallback(async () => {
245
268
  if (!onFork || forking)
246
269
  return;
@@ -264,6 +287,9 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
264
287
  onDismiss();
265
288
  }
266
289
  }, [builderReconnectResolved, onDismiss]);
290
+ if (shouldShowMissingProviderSetup) {
291
+ return (_jsxs("div", { className: "w-full", children: [_jsx(BuilderSetupCard, { fullWidth: true, layout: "sidebar", onConnected: handleMissingProviderConnected }), providerConnected ? (_jsx("div", { className: "flex justify-center px-3 pt-1", children: _jsxs("button", { type: "button", onClick: handleMissingProviderRetry, disabled: retryRequested, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-60", children: [_jsx(IconRefresh, { size: 13 }), t("agentChat.common.retry")] }) })) : null] }));
292
+ }
267
293
  return (_jsxs("div", { className: "rounded-lg border border-amber-500/25 bg-amber-500/[0.06] p-3 text-sm", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-500/10 text-amber-700 dark:text-amber-300", children: _jsx(IconAlertTriangle, { size: 14 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium text-foreground", children: runErrorHeadline(info, {
268
294
  recoverable: t("agentChat.error.stopped"),
269
295
  terminal: t("agentChat.error.failed"),
@@ -605,6 +605,12 @@ function isMissingCredentialText(message, errorCode) {
605
605
  msg.includes("no llm provider") ||
606
606
  msg.includes("llm provider is connected"));
607
607
  }
608
+ function isMissingProviderErrorText(message, errorCode) {
609
+ const code = String(errorCode ?? "").toLowerCase();
610
+ return (code === "missing_api_key" ||
611
+ code === "missing_credentials" ||
612
+ /no llm provider(?: key)? (?:is connected|was found)/i.test(message));
613
+ }
608
614
  function dispatchActivityClear(tabId) {
609
615
  if (typeof window === "undefined")
610
616
  return;
@@ -1389,10 +1395,6 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1389
1395
  // the last preparation activity, so do not leave its label mounted.
1390
1396
  dispatchActivityClear(tabId);
1391
1397
  settleInterruptedToolCalls(content, undefined, { includeActivity: true });
1392
- content.push({
1393
- type: "text",
1394
- text: formatChatErrorText(errMsg, undefined, errorCode),
1395
- });
1396
1398
  return {
1397
1399
  action: "missing_api_key",
1398
1400
  result: {
@@ -1472,6 +1474,7 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1472
1474
  };
1473
1475
  }
1474
1476
  const normalized = normalizeChatError(errMsg, ev.errorCode);
1477
+ const missingProviderError = isMissingProviderErrorText(errMsg, ev.errorCode);
1475
1478
  if (isMissingCredentialText(errMsg, ev.errorCode)) {
1476
1479
  dispatchMissingApiKey(tabId);
1477
1480
  }
@@ -1492,10 +1495,12 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1492
1495
  }));
1493
1496
  }
1494
1497
  settleInterruptedToolCalls(content, undefined, { includeActivity: true });
1495
- content.push({
1496
- type: "text",
1497
- text: formatChatErrorText(errMsg, ev.upgradeUrl, ev.errorCode),
1498
- });
1498
+ if (!missingProviderError) {
1499
+ content.push({
1500
+ type: "text",
1501
+ text: formatChatErrorText(errMsg, ev.upgradeUrl, ev.errorCode),
1502
+ });
1503
+ }
1499
1504
  return {
1500
1505
  action: "error",
1501
1506
  result: {
@@ -84,6 +84,7 @@ export declare const SANDBOX_EXECUTION_DEFAULT_MAX_ATTEMPTS = 2;
84
84
  * the caller's `maxOutputChars`) is applied first; this bounds even that.
85
85
  */
86
86
  export declare const SANDBOX_EXECUTION_MAX_STORED_OUTPUT_CHARS = 200000;
87
+ export declare function ensureTable(): Promise<void>;
87
88
  /** Test-only: forget the memoized init so a fresh in-memory DB re-creates. */
88
89
  export declare function resetSandboxExecutionsStoreForTests(): void;
89
90
  /** Enqueue a new execution row in `queued` state. */
@@ -40,7 +40,7 @@ export const SANDBOX_EXECUTION_DEFAULT_MAX_ATTEMPTS = 2;
40
40
  export const SANDBOX_EXECUTION_MAX_STORED_OUTPUT_CHARS = 200_000;
41
41
  const TABLE = "sandbox_executions";
42
42
  let _initPromise;
43
- async function ensureTable() {
43
+ export async function ensureTable() {
44
44
  if (!_initPromise) {
45
45
  _initPromise = _doEnsureTable().catch((err) => {
46
46
  // Don't cache the rejection — let the next caller retry a fresh init.
@@ -14,6 +14,7 @@
14
14
  * state hasn't changed; expired rows are purged opportunistically.
15
15
  */
16
16
  import type { AwarenessEntry } from "./awareness.js";
17
+ export declare function ensureTable(): Promise<void>;
17
18
  /**
18
19
  * Mirror a client's awareness state to SQL. Throttled: unchanged state is
19
20
  * rewritten at most every {@link WRITE_THROTTLE_MS} (to refresh last_seen).
@@ -22,7 +22,7 @@ const WRITE_THROTTLE_MS = 2_000;
22
22
  /** Minimum interval between opportunistic purges per document. */
23
23
  const PURGE_INTERVAL_MS = 30_000;
24
24
  let _initPromise;
25
- async function ensureTable() {
25
+ export async function ensureTable() {
26
26
  if (!_initPromise) {
27
27
  _initPromise = (async () => {
28
28
  const client = getDbExec();
@@ -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
  }>>;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -4,6 +4,7 @@
4
4
  * Uses a framework-level `_collab_docs` table (TEXT columns with base64
5
5
  * encoding for binary Yjs state) that works across SQLite and Postgres.
6
6
  */
7
+ export declare function ensureTable(): Promise<void>;
7
8
  export interface YDocStateRecord {
8
9
  state: Uint8Array;
9
10
  version: number;
@@ -33,7 +33,7 @@ async function executeWithSqliteRetry(client, statement) {
33
33
  }
34
34
  }
35
35
  }
36
- async function ensureTable() {
36
+ export async function ensureTable() {
37
37
  if (!_initPromise) {
38
38
  _initPromise = (async () => {
39
39
  const client = getDbExec();
@@ -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
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
package/dist/db/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import path from "path";
1
2
  /**
2
3
  * Central database client abstraction.
3
4
  *
@@ -8,7 +9,7 @@
8
9
  * (dynamic import) so this module can be loaded in any runtime (Node.js,
9
10
  * Cloudflare Workers, edge) without failing on missing native deps.
10
11
  */
11
- import path from "path";
12
+ import { isMigrationAuthorizedRuntime } from "./migration-runtime.js";
12
13
  import { beginDatabaseOperation, recordDatabaseRetry, } from "./request-telemetry.js";
13
14
  const recyclingPostgresPools = new WeakSet();
14
15
  const loggedNeonPools = new WeakSet();
@@ -803,9 +804,8 @@ export function isSchemaMutationStatement(statement) {
803
804
  * is the only supported production opt-in.
804
805
  */
805
806
  export function assertSchemaMutationAllowed(statement) {
806
- const migrationRuntime = globalThis.__AGENT_NATIVE_MIGRATION_RUNTIME__ === true;
807
807
  if (isProductionServerlessFunctionRuntime() &&
808
- !migrationRuntime &&
808
+ !isMigrationAuthorizedRuntime() &&
809
809
  isSchemaMutationStatement(statement)) {
810
810
  throw new Error("Schema mutation attempted in a production serverless request. Run migrations in the release job instead.");
811
811
  }
@@ -29,6 +29,7 @@
29
29
  * behaviour there.
30
30
  */
31
31
  import { getDbExec, isPostgres, isProductionServerlessFunctionRuntime, } from "./client.js";
32
+ import { isMigrationAuthorizedRuntime } from "./migration-runtime.js";
32
33
  const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
33
34
  /**
34
35
  * Skip on-demand schema DDL entirely. For deployments that run real migrations,
@@ -45,6 +46,13 @@ const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
45
46
  * down the DDL path this flag exists to avoid.
46
47
  */
47
48
  function schemaEnsureDisabled() {
49
+ // The release runner is the deployment that OWNS schema creation, so it is the
50
+ // one caller that must never be skipped. Without this, the skip below applies
51
+ // to `migrate:production` too — the Netlify build environment also sets
52
+ // `NETLIFY=true` — and the release step reports success having created
53
+ // nothing.
54
+ if (isMigrationAuthorizedRuntime())
55
+ return false;
48
56
  if (isProductionServerlessFunctionRuntime())
49
57
  return true;
50
58
  const raw = process.env.AGENT_NATIVE_SKIP_ENSURE_TABLES?.trim();
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Migration duty: the process-local claim that the current call is allowed to
3
+ * create or alter schema.
4
+ *
5
+ * Its own module, and deliberately dependency-free, because both `./client.js`
6
+ * and `./ddl-guard.js` need to read it. Putting the reader on `client.js` would
7
+ * mean every `vi.mock("../db/client.js")` in the codebase has to stub one more
8
+ * export to keep `ensureTable()` working — the exact coupling `ddl-guard.ts`
9
+ * was split out to avoid.
10
+ */
11
+ /**
12
+ * A runtime that is ALLOWED to migrate: release scripts, scheduled jobs, and
13
+ * durable background workers, which are off the request path and may take as
14
+ * long as they need. Claimed with {@link withMigrationRuntime}.
15
+ */
16
+ export declare function isMigrationAuthorizedRuntime(): boolean;
17
+ /**
18
+ * Run an explicit release-time migration job with migration duty enabled.
19
+ *
20
+ * The flag is process-local and restored even when the job fails, so a build
21
+ * step can opt in without creating a permanent escape hatch for request code.
22
+ */
23
+ export declare function withMigrationRuntime<T>(run: () => Promise<T>): Promise<T>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Migration duty: the process-local claim that the current call is allowed to
3
+ * create or alter schema.
4
+ *
5
+ * Its own module, and deliberately dependency-free, because both `./client.js`
6
+ * and `./ddl-guard.js` need to read it. Putting the reader on `client.js` would
7
+ * mean every `vi.mock("../db/client.js")` in the codebase has to stub one more
8
+ * export to keep `ensureTable()` working — the exact coupling `ddl-guard.ts`
9
+ * was split out to avoid.
10
+ */
11
+ /**
12
+ * A runtime that is ALLOWED to migrate: release scripts, scheduled jobs, and
13
+ * durable background workers, which are off the request path and may take as
14
+ * long as they need. Claimed with {@link withMigrationRuntime}.
15
+ */
16
+ export function isMigrationAuthorizedRuntime() {
17
+ return (globalThis
18
+ .__AGENT_NATIVE_MIGRATION_RUNTIME__ === true);
19
+ }
20
+ /**
21
+ * Run an explicit release-time migration job with migration duty enabled.
22
+ *
23
+ * The flag is process-local and restored even when the job fails, so a build
24
+ * step can opt in without creating a permanent escape hatch for request code.
25
+ */
26
+ export async function withMigrationRuntime(run) {
27
+ const runtime = globalThis;
28
+ const previous = runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__;
29
+ runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__ = true;
30
+ try {
31
+ return await run();
32
+ }
33
+ finally {
34
+ if (previous === undefined) {
35
+ delete runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__;
36
+ }
37
+ else {
38
+ runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__ = previous;
39
+ }
40
+ }
41
+ }