@agent-native/core 0.191.0 → 0.192.0-nightly-20260926045737

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 (78) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +4 -0
  3. package/corpus/templates/analytics/app/components/layout/Layout.tsx +41 -12
  4. package/corpus/templates/analytics/app/lib/chat-handoff.ts +62 -0
  5. package/corpus/templates/analytics/changelog/2026-09-25-chat-sidebar-stays-closed-in-settings.md +6 -0
  6. package/corpus/templates/analytics/changelog/2026-09-25-show-one-recent-prompt-per-chat-turn.md +6 -0
  7. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +74 -11
  8. package/corpus/templates/clips/app/i18n/en-US.ts +6 -0
  9. package/corpus/templates/clips/changelog/2026-09-25-one-click-builder-storage.md +6 -0
  10. package/corpus/templates/design/actions/get-design.schema.ts +19 -0
  11. package/corpus/templates/design/actions/get-design.ts +16 -19
  12. package/corpus/templates/mail/actions/apply-ai-filter.ts +8 -0
  13. package/corpus/templates/mail/actions/create-automation.ts +7 -1
  14. package/corpus/templates/mail/actions/manage-ai-filter-rule-undo.ts +34 -0
  15. package/corpus/templates/mail/actions/manage-automations.ts +21 -44
  16. package/corpus/templates/mail/app/components/email/EmailList.tsx +35 -0
  17. package/corpus/templates/mail/app/components/onboarding/AiInboxSetup.tsx +116 -34
  18. package/corpus/templates/mail/app/components/settings/AiFilterSection.tsx +108 -70
  19. package/corpus/templates/mail/app/components/settings/AiRulePromptField.tsx +8 -1
  20. package/corpus/templates/mail/app/components/settings/JevConnectionPrompt.tsx +171 -0
  21. package/corpus/templates/mail/app/hooks/use-automations.ts +24 -0
  22. package/corpus/templates/mail/app/i18n/en-US.ts +8 -2
  23. package/corpus/templates/mail/app/pages/InboxPage.tsx +12 -2
  24. package/corpus/templates/mail/changelog/2026-09-25-mail-now-guides-users-to-connect-jev-before-setting-up-triag.md +6 -0
  25. package/corpus/templates/mail/server/db/schema.ts +17 -0
  26. package/corpus/templates/mail/server/lib/ai-filter-rule-undo.ts +151 -0
  27. package/corpus/templates/mail/server/lib/automations.ts +34 -0
  28. package/corpus/templates/mail/server/plugins/db.ts +18 -0
  29. package/corpus/templates/mail/server/plugins/mail-jobs.ts +6 -0
  30. package/dist/client/AssistantChat.js +20 -3
  31. package/dist/client/BuilderReferralInviteRow.d.ts +3 -0
  32. package/dist/client/BuilderReferralInviteRow.js +16 -0
  33. package/dist/client/agent-chat-adapter.js +6 -5
  34. package/dist/client/chat/run-recovery.js +6 -5
  35. package/dist/client/settings/UsageSection.js +2 -1
  36. package/dist/client/settings/index.d.ts +1 -1
  37. package/dist/client/settings/index.js +1 -1
  38. package/dist/client/settings/useBuilderStatus.d.ts +4 -1
  39. package/dist/client/settings/useBuilderStatus.js +32 -16
  40. package/dist/client/use-agent-chat-running-threads.d.ts +2 -0
  41. package/dist/collab/struct-routes.d.ts +1 -1
  42. package/dist/framework-tools.d.ts +3 -1
  43. package/dist/framework-tools.js +2 -0
  44. package/dist/localization/core-messages/ar-SA.js +4 -0
  45. package/dist/localization/core-messages/de-DE.js +4 -0
  46. package/dist/localization/core-messages/en-US.d.ts +4 -0
  47. package/dist/localization/core-messages/en-US.js +4 -0
  48. package/dist/localization/core-messages/es-ES.js +4 -0
  49. package/dist/localization/core-messages/fr-FR.js +4 -0
  50. package/dist/localization/core-messages/hi-IN.js +4 -0
  51. package/dist/localization/core-messages/ja-JP.js +4 -0
  52. package/dist/localization/core-messages/ko-KR.js +4 -0
  53. package/dist/localization/core-messages/pt-BR.js +4 -0
  54. package/dist/localization/core-messages/zh-CN.js +4 -0
  55. package/dist/localization/core-messages/zh-TW.js +4 -0
  56. package/dist/localization/core-messages.d.ts +4 -0
  57. package/dist/notifications/routes.d.ts +3 -3
  58. package/dist/observability/routes.d.ts +5 -5
  59. package/dist/progress/routes.d.ts +1 -1
  60. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  61. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  62. package/dist/secrets/routes.d.ts +3 -3
  63. package/dist/server/action-discovery.js +4 -0
  64. package/dist/server/agent-chat/plugin-options.d.ts +1 -1
  65. package/dist/server/agent-chat-plugin.js +6 -4
  66. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  67. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  68. package/dist/server/core-routes-plugin.d.ts +1 -1
  69. package/dist/server/fusion-app.d.ts +3 -0
  70. package/dist/server/fusion-app.js +17 -0
  71. package/dist/shared/builder-referrals.d.ts +10 -0
  72. package/dist/shared/builder-referrals.js +29 -0
  73. package/dist/usage/actions/get-builder-referral-info.d.ts +9 -0
  74. package/dist/usage/actions/get-builder-referral-info.js +26 -0
  75. package/dist/usage/metrics-store.js +142 -30
  76. package/dist/vite/action-types-plugin.js +4 -0
  77. package/docs/content/actions-agent-tools.mdx +2 -1
  78. package/package.json +3 -2
@@ -65,6 +65,23 @@ export const automationRules = table("automation_rules", {
65
65
  updatedAt: integer("updated_at").notNull(),
66
66
  });
67
67
 
68
+ export const aiFilterRuleUndo = table(
69
+ "mail_ai_filter_rule_undo",
70
+ {
71
+ id: text("id").primaryKey(),
72
+ ownerEmail: text("owner_email").notNull(),
73
+ rulesJson: text("rules_json").notNull(),
74
+ expiresAt: integer("expires_at").notNull(),
75
+ },
76
+ (t) => [
77
+ index("mail_ai_filter_rule_undo_owner_expiry_idx").on(
78
+ t.ownerEmail,
79
+ t.expiresAt,
80
+ ),
81
+ index("mail_ai_filter_rule_undo_expires_idx").on(t.expiresAt),
82
+ ],
83
+ );
84
+
68
85
  export const emailTracking = table("email_tracking", {
69
86
  pixelToken: text("pixel_token").primaryKey(),
70
87
  messageId: text("message_id").notNull(),
@@ -0,0 +1,151 @@
1
+ import { fail } from "@agent-native/core/action";
2
+ import { and, eq, gt, inArray, lt } from "drizzle-orm";
3
+ import { nanoid } from "nanoid";
4
+
5
+ import { db, schema } from "../db/index.js";
6
+
7
+ const undoLifetimeSeconds = 60;
8
+
9
+ export async function purgeExpiredMailAiFilterRuleUndoSnapshots(): Promise<void> {
10
+ const now = Math.floor(Date.now() / 1_000);
11
+ await db
12
+ .delete(schema.aiFilterRuleUndo)
13
+ .where(lt(schema.aiFilterRuleUndo.expiresAt, now))
14
+ .returning({ id: schema.aiFilterRuleUndo.id });
15
+ }
16
+
17
+ export async function clearMailAiFilterRules(
18
+ ownerEmail: string,
19
+ ids: string[],
20
+ ): Promise<string> {
21
+ const uniqueIds = [...new Set(ids)];
22
+ if (
23
+ uniqueIds.length !== ids.length ||
24
+ uniqueIds.length === 0 ||
25
+ uniqueIds.length > 32
26
+ ) {
27
+ fail("AI-filter rule ids must be unique and contain 1 to 32 ids.", {
28
+ errorCode: "invalid_rule_ids",
29
+ statusCode: 400,
30
+ });
31
+ }
32
+
33
+ const now = Math.floor(Date.now() / 1_000);
34
+ const undoId = nanoid(24);
35
+ return db.transaction(async (tx: any) => {
36
+ await tx
37
+ .delete(schema.aiFilterRuleUndo)
38
+ .where(
39
+ and(
40
+ eq(schema.aiFilterRuleUndo.ownerEmail, ownerEmail),
41
+ lt(schema.aiFilterRuleUndo.expiresAt, now),
42
+ ),
43
+ )
44
+ .returning({ id: schema.aiFilterRuleUndo.id });
45
+
46
+ const rules = await tx
47
+ .select()
48
+ .from(schema.automationRules)
49
+ .where(
50
+ and(
51
+ eq(schema.automationRules.ownerEmail, ownerEmail),
52
+ eq(schema.automationRules.domain, "mail"),
53
+ eq(schema.automationRules.kind, "ai-filter"),
54
+ eq(schema.automationRules.enabled, 1),
55
+ inArray(schema.automationRules.id, uniqueIds),
56
+ ),
57
+ )
58
+ .for("update");
59
+ if (rules.length !== uniqueIds.length) {
60
+ fail("Some AI-filter rules changed before they could be cleared.", {
61
+ errorCode: "rules_changed",
62
+ statusCode: 409,
63
+ });
64
+ }
65
+
66
+ await tx.insert(schema.aiFilterRuleUndo).values({
67
+ id: undoId,
68
+ ownerEmail,
69
+ rulesJson: JSON.stringify(rules),
70
+ expiresAt: now + undoLifetimeSeconds,
71
+ });
72
+
73
+ const deleted = await tx
74
+ .delete(schema.automationRules)
75
+ .where(
76
+ and(
77
+ eq(schema.automationRules.ownerEmail, ownerEmail),
78
+ eq(schema.automationRules.domain, "mail"),
79
+ eq(schema.automationRules.kind, "ai-filter"),
80
+ eq(schema.automationRules.enabled, 1),
81
+ inArray(schema.automationRules.id, uniqueIds),
82
+ ),
83
+ )
84
+ .returning({ id: schema.automationRules.id });
85
+ if (deleted.length !== uniqueIds.length) {
86
+ fail("Some AI-filter rules changed before they could be cleared.", {
87
+ errorCode: "rules_changed",
88
+ statusCode: 409,
89
+ });
90
+ }
91
+ return undoId;
92
+ });
93
+ }
94
+
95
+ export async function restoreMailAiFilterRules(
96
+ ownerEmail: string,
97
+ undoId: string,
98
+ ): Promise<void> {
99
+ const now = Math.floor(Date.now() / 1_000);
100
+ await db.transaction(async (tx: any) => {
101
+ const [undo] = await tx
102
+ .select()
103
+ .from(schema.aiFilterRuleUndo)
104
+ .where(
105
+ and(
106
+ eq(schema.aiFilterRuleUndo.id, undoId),
107
+ eq(schema.aiFilterRuleUndo.ownerEmail, ownerEmail),
108
+ gt(schema.aiFilterRuleUndo.expiresAt, now),
109
+ ),
110
+ )
111
+ .for("update");
112
+ if (!undo) {
113
+ fail("This rule undo has expired or was already used.", {
114
+ errorCode: "undo_expired",
115
+ statusCode: 410,
116
+ });
117
+ }
118
+
119
+ const rules = JSON.parse(undo.rulesJson);
120
+ if (
121
+ !Array.isArray(rules) ||
122
+ rules.some(
123
+ (rule) =>
124
+ rule.ownerEmail !== ownerEmail ||
125
+ rule.domain !== "mail" ||
126
+ rule.kind !== "ai-filter" ||
127
+ rule.enabled !== 1,
128
+ )
129
+ ) {
130
+ throw new Error("AI-filter undo snapshot is invalid");
131
+ }
132
+
133
+ await tx.insert(schema.automationRules).values(rules);
134
+ const consumed = await tx
135
+ .delete(schema.aiFilterRuleUndo)
136
+ .where(
137
+ and(
138
+ eq(schema.aiFilterRuleUndo.id, undoId),
139
+ eq(schema.aiFilterRuleUndo.ownerEmail, ownerEmail),
140
+ gt(schema.aiFilterRuleUndo.expiresAt, now),
141
+ ),
142
+ )
143
+ .returning({ id: schema.aiFilterRuleUndo.id });
144
+ if (consumed.length !== 1) {
145
+ fail("This rule undo has expired or was already used.", {
146
+ errorCode: "undo_expired",
147
+ statusCode: 410,
148
+ });
149
+ }
150
+ });
151
+ }
@@ -1,9 +1,24 @@
1
+ import { fail } from "@agent-native/core/action";
2
+ import {
3
+ getJevContextCredentials,
4
+ isJevEnabled,
5
+ } from "@agent-native/core/server";
1
6
  import { and, eq, inArray } from "drizzle-orm";
2
7
  import { nanoid } from "nanoid";
3
8
 
4
9
  import type { AutomationAction, AutomationRule } from "../../shared/types.js";
5
10
  import { db, schema } from "../db/index.js";
6
11
 
12
+ export async function assertMailJevEnabled(ownerEmail: string): Promise<void> {
13
+ const credentials = await getJevContextCredentials(ownerEmail);
14
+ if (!(await isJevEnabled(credentials))) {
15
+ fail("Jev is not enabled for this account.", {
16
+ errorCode: "jev_not_enabled",
17
+ statusCode: 403,
18
+ });
19
+ }
20
+ }
21
+
7
22
  export function toApiRule(row: any): AutomationRule {
8
23
  const kind = row.kind ?? "automation";
9
24
  if (kind !== "automation" && kind !== "ai-filter") {
@@ -85,6 +100,24 @@ export async function updateAutomationRule(
85
100
  kind?: "automation" | "ai-filter";
86
101
  },
87
102
  ): Promise<AutomationRule> {
103
+ const [existing] = await db
104
+ .select()
105
+ .from(schema.automationRules)
106
+ .where(ownedRule(ownerEmail, id));
107
+ if (!existing) throw new Error("Rule not found");
108
+
109
+ const existingIsMailAiFilter =
110
+ existing.domain === "mail" &&
111
+ (existing.kind ?? "automation") === "ai-filter";
112
+ const nextDomain = patch.domain ?? existing.domain;
113
+ const nextKind = patch.kind ?? existing.kind ?? "automation";
114
+ const disableOnly =
115
+ Object.keys(patch).length === 1 && patch.enabled === false;
116
+ const nextIsMailAiFilter = nextDomain === "mail" && nextKind === "ai-filter";
117
+ if ((existingIsMailAiFilter || nextIsMailAiFilter) && !disableOnly) {
118
+ await assertMailJevEnabled(ownerEmail);
119
+ }
120
+
88
121
  const updates: Record<string, any> = {
89
122
  updatedAt: Math.floor(Date.now() / 1_000),
90
123
  };
@@ -134,6 +167,7 @@ export async function consolidateAutomationRules(
134
167
  actions: AutomationAction[];
135
168
  },
136
169
  ): Promise<boolean> {
170
+ await assertMailJevEnabled(ownerEmail);
137
171
  const ids = [input.id, ...input.duplicateIds];
138
172
  if (new Set(ids).size !== ids.length) return false;
139
173
 
@@ -300,6 +300,24 @@ CREATE INDEX IF NOT EXISTS idx_mail_inbox_push_invalidations_owner_account
300
300
  ALTER TABLE mail_sync_accounts
301
301
  ADD COLUMN IF NOT EXISTS last_push_generation BIGINT NOT NULL DEFAULT 0;`,
302
302
  },
303
+ {
304
+ version: 27,
305
+ name: "mail-ai-filter-rule-undo",
306
+ sql: `CREATE TABLE IF NOT EXISTS mail_ai_filter_rule_undo (
307
+ id TEXT PRIMARY KEY,
308
+ owner_email TEXT NOT NULL,
309
+ rules_json TEXT NOT NULL,
310
+ expires_at BIGINT NOT NULL
311
+ );
312
+ CREATE INDEX IF NOT EXISTS mail_ai_filter_rule_undo_owner_expiry_idx
313
+ ON mail_ai_filter_rule_undo(owner_email, expires_at);`,
314
+ },
315
+ {
316
+ version: 28,
317
+ name: "mail-ai-filter-rule-undo-expiry-index",
318
+ sql: `CREATE INDEX IF NOT EXISTS mail_ai_filter_rule_undo_expires_idx
319
+ ON mail_ai_filter_rule_undo(expires_at);`,
320
+ },
303
321
  ],
304
322
  { table: "mail_migrations" },
305
323
  );
@@ -3,6 +3,7 @@ import { listOAuthAccounts } from "@agent-native/core/oauth-tokens";
3
3
  import { startIntervalJob } from "@agent-native/core/server/interval-job";
4
4
  import { z } from "zod";
5
5
 
6
+ import { purgeExpiredMailAiFilterRuleUndoSnapshots } from "../lib/ai-filter-rule-undo.js";
6
7
  import { processAutomations } from "../lib/automation-engine.js";
7
8
  import { getClientForAccount, startWatch } from "../lib/google-auth.js";
8
9
  import {
@@ -136,6 +137,11 @@ export default () => {
136
137
  // overlap the next one and send duplicate mail.
137
138
  startIntervalJob(
138
139
  async () => {
140
+ try {
141
+ await purgeExpiredMailAiFilterRuleUndoSnapshots();
142
+ } catch (err) {
143
+ console.error("[mail-jobs] AI-filter undo cleanup failed:", err);
144
+ }
139
145
  try {
140
146
  await processJobs();
141
147
  } catch (err) {
@@ -2582,6 +2582,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2582
2582
  : getActiveRun()?.runId === runId
2583
2583
  ? (getActiveRun()?.turnId ?? undefined)
2584
2584
  : undefined;
2585
+ const reconnectEventIdentity = {
2586
+ runId,
2587
+ ...(logicalTurnId ? { turnId: logicalTurnId } : {}),
2588
+ };
2585
2589
  if (reconnectRunIdRef.current === runId)
2586
2590
  return true;
2587
2591
  // SINGLE-READER OWNERSHIP: never start a second reader while the
@@ -2664,7 +2668,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2664
2668
  setReconnectFrozen(false);
2665
2669
  setReconnectContent([]);
2666
2670
  window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
2667
- detail: { isRunning: true, tabId: tabId || threadId },
2671
+ detail: {
2672
+ isRunning: true,
2673
+ tabId: tabId || threadId,
2674
+ ...reconnectEventIdentity,
2675
+ },
2668
2676
  }));
2669
2677
  const abortCtrl = new AbortController();
2670
2678
  reconnectAbortRef.current = abortCtrl;
@@ -2979,7 +2987,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2979
2987
  : RECONNECT_EMPTY_RETRY_MESSAGE,
2980
2988
  });
2981
2989
  window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
2982
- detail: { isRunning: false, tabId: tabId || threadId },
2990
+ detail: {
2991
+ isRunning: false,
2992
+ tabId: tabId || threadId,
2993
+ ...reconnectEventIdentity,
2994
+ },
2983
2995
  }));
2984
2996
  return;
2985
2997
  }
@@ -3015,6 +3027,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3015
3027
  isRunning: false,
3016
3028
  tabId: tabId || threadId,
3017
3029
  reason: "failed",
3030
+ ...reconnectEventIdentity,
3018
3031
  },
3019
3032
  }));
3020
3033
  return;
@@ -3055,7 +3068,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3055
3068
  reconnectAutoRecoveryCountRef.current = 0;
3056
3069
  }
3057
3070
  window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
3058
- detail: { isRunning: false, tabId: tabId || threadId },
3071
+ detail: {
3072
+ isRunning: false,
3073
+ tabId: tabId || threadId,
3074
+ ...reconnectEventIdentity,
3075
+ },
3059
3076
  }));
3060
3077
  }
3061
3078
  // Skip the final refresh when the adapter runtime is live — importing
@@ -0,0 +1,3 @@
1
+ export declare function BuilderReferralInviteRow({ className, }: {
2
+ className?: string;
3
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ShareCopyRow } from "@agent-native/toolkit/sharing";
3
+ import { writeClipboardText } from "./clipboard.js";
4
+ import { useT } from "./i18n.js";
5
+ import { useActionQuery } from "./use-action.js";
6
+ import { cn } from "./utils.js";
7
+ export function BuilderReferralInviteRow({ className, }) {
8
+ const t = useT();
9
+ const referralQuery = useActionQuery("get-builder-referral-info", {}, { staleTime: 5 * 60_000 });
10
+ const referralInfo = referralQuery.data;
11
+ if (!referralInfo?.eligible || !referralInfo.inviteUrl)
12
+ return null;
13
+ return (_jsx(ShareCopyRow, { className: cn("min-w-0", className), value: referralInfo.inviteUrl, label: t("agentChat.usage.inviteFriends"), description: t("agentChat.usage.inviteCredits", {
14
+ amount: referralInfo.creditsPerReferral.toLocaleString(),
15
+ }), copyLabel: t("agentChat.usage.copyInviteLink"), copiedLabel: t("agentChat.usage.inviteLinkCopied"), onCopy: writeClipboardText }));
16
+ }
@@ -1884,7 +1884,7 @@ export function createAgentChatAdapter(options) {
1884
1884
  // Signal that generation is starting
1885
1885
  if (typeof window !== "undefined") {
1886
1886
  window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
1887
- detail: { isRunning: true, tabId },
1887
+ detail: { isRunning: true, tabId, turnId },
1888
1888
  }));
1889
1889
  }
1890
1890
  const content = [];
@@ -1907,9 +1907,10 @@ export function createAgentChatAdapter(options) {
1907
1907
  pendingTurnBelongsToSurface &&
1908
1908
  (pendingTurn.threadId !== threadId || pendingTurn.turnId !== turnId));
1909
1909
  };
1910
- const activeRunMatchesTab = (activeRun) => !activeRun?.tabId ||
1911
- !activeRunTabId ||
1912
- activeRun.tabId === activeRunTabId;
1910
+ const activeRunMatchesTab = (activeRun) => !activeRun ||
1911
+ (activeRun.tabId
1912
+ ? !activeRunTabId || activeRun.tabId === activeRunTabId
1913
+ : !threadId || activeRun.threadId === threadId);
1913
1914
  const ownsActiveRunState = () => {
1914
1915
  if (hasPendingSuccessorRequest())
1915
1916
  return false;
@@ -1949,7 +1950,7 @@ export function createAgentChatAdapter(options) {
1949
1950
  return;
1950
1951
  dispatchTerminalChatUiCleanup(tabId);
1951
1952
  window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
1952
- detail: { isRunning: false, tabId },
1953
+ detail: { isRunning: false, tabId, turnId },
1953
1954
  }));
1954
1955
  };
1955
1956
  const settleTerminalChatRun = () => {
@@ -11,6 +11,7 @@ import { isCreditsLimitErrorCode } from "../../agent/engine/error-detail.js";
11
11
  import { buildSettingsRoute } from "../../navigation/index.js";
12
12
  import { withBuilderUtmTrackingParams } from "../../shared/builder-link-tracking.js";
13
13
  import { agentNativePath } from "../api-path.js";
14
+ import { BuilderReferralInviteRow } from "../BuilderReferralInviteRow.js";
14
15
  import { writeClipboardText } from "../clipboard.js";
15
16
  import { isProviderAuthenticationError, localizeKnownChatErrorText, } from "../error-format.js";
16
17
  import { useFormatters, useT } from "../i18n.js";
@@ -322,11 +323,11 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
322
323
  : handleMissingProviderConnected }), _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")] }) })] }));
323
324
  }
324
325
  if (isBuilderCreditsLimit) {
325
- return (_jsx("div", { className: "min-w-0 rounded-lg border border-border bg-card p-3 text-sm", children: _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-3", children: [_jsx("p", { className: "min-w-0 flex-1 font-medium text-foreground", children: t("agentChat.errorMessages.creditsLimitReached", {
326
- defaultValue: "You've reached your AI credits limit.",
327
- }) }), _jsx(Button, { asChild: true, size: "sm", children: _jsxs("a", { href: builderSubscriptionUrl, target: "_blank", rel: "noreferrer", children: [t("agentChat.errorMessages.addCreditsInBuilder", {
328
- defaultValue: "Add credits in Builder",
329
- }), _jsx(IconArrowUpRight, {})] }) }), _jsx("button", { type: "button", onClick: onDismiss, "aria-label": t("agentChat.common.dismiss"), className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { size: 14 }) })] }) }));
326
+ return (_jsxs("div", { className: "min-w-0 rounded-lg border border-border bg-card p-3 text-sm", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-3", children: [_jsx("p", { className: "min-w-0 flex-1 font-medium text-foreground", children: t("agentChat.errorMessages.creditsLimitReached", {
327
+ defaultValue: "You've reached your AI credits limit.",
328
+ }) }), _jsx(Button, { asChild: true, size: "sm", children: _jsxs("a", { href: builderSubscriptionUrl, target: "_blank", rel: "noreferrer", children: [t("agentChat.errorMessages.addCreditsInBuilder", {
329
+ defaultValue: "Add credits in Builder",
330
+ }), _jsx(IconArrowUpRight, {})] }) }), _jsx("button", { type: "button", onClick: onDismiss, "aria-label": t("agentChat.common.dismiss"), className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { size: 14 }) })] }), _jsx(BuilderReferralInviteRow, { className: "mt-3 border-t border-border/70 pt-3" })] }));
330
331
  }
331
332
  return (_jsxs("div", { className: "min-w-0 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, {
332
333
  recoverable: t("agentChat.error.stopped"),
@@ -9,6 +9,7 @@ import { IconArrowUpRight, IconBell, IconChartLine, IconChevronDown, IconLoader2
9
9
  import { useState } from "react";
10
10
  import { Link } from "react-router";
11
11
  import { withBuilderUtmTrackingParams } from "../../shared/builder-link-tracking.js";
12
+ import { BuilderReferralInviteRow } from "../BuilderReferralInviteRow.js";
12
13
  import { useT } from "../i18n.js";
13
14
  import { useActionMutation, useActionQuery } from "../use-action.js";
14
15
  const builderAddCreditsUrl = withBuilderUtmTrackingParams("https://builder.io/account/subscription?signupSource=agent-native", { content: "usage_credit_balance" });
@@ -192,7 +193,7 @@ function BuilderCreditUsagePanel({ usage }) {
192
193
  } }) }), _jsx("div", { className: "mt-1 text-right text-xs tabular-nums text-muted-foreground", children: t("agentChat.usage.creditRemaining", {
193
194
  defaultValue: "{{amount}} remaining",
194
195
  amount: remaining,
195
- }) })] })] }));
196
+ }) }), _jsx(BuilderReferralInviteRow, { className: "mt-4 border-t border-border/70 pt-4" })] })] }));
196
197
  }
197
198
  function AlertEditor({ draft, onChange, onSave, onCancel, isPending, }) {
198
199
  const t = useT();
@@ -2,7 +2,7 @@ export { AgentSettingsContent, areExtensionSettingsEnabled, SettingsPanel, useAg
2
2
  export { getAgentSettingsSearchTabs, type AgentSettingsSearchTab, } from "./agent-settings-search.js";
3
3
  export { SettingsTabsPage, type SettingsSearchEntry, type SettingsTabItem, type SettingsTabsPageProps, } from "./SettingsTabsPage.js";
4
4
  export { AccountSettingsCard, AccountSettingsForm, type AccountSettingsCardProps, type AccountSettingsFormProps, } from "./AccountSettingsCard.js";
5
- export { openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, type BuilderConnectFlow, type BuilderConnectFlowOptions, type BuilderConnectStartOptions, type BuilderStatus, type OpenBuilderConnectPopupOptions, } from "./useBuilderStatus.js";
5
+ export { hasBuilderOAuthCredential, openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, type BuilderConnectFlow, type BuilderConnectFlowOptions, type BuilderConnectStartOptions, type BuilderStatus, type OpenBuilderConnectPopupOptions, } from "./useBuilderStatus.js";
6
6
  export { DeferredBuilderConnectPopover as BuilderConnectPopover } from "./deferred-builder-connect-popover.js";
7
7
  export type { BuilderConnectPopoverProps } from "./BuilderConnectPopover.js";
8
8
  export { NewKeyMenu, normalizeKeyName, type NewKeyMenuProps, type NewKeyOption, } from "./NewKeyMenu.js";
@@ -2,7 +2,7 @@ export { AgentSettingsContent, areExtensionSettingsEnabled, SettingsPanel, useAg
2
2
  export { getAgentSettingsSearchTabs, } from "./agent-settings-search.js";
3
3
  export { SettingsTabsPage, } from "./SettingsTabsPage.js";
4
4
  export { AccountSettingsCard, AccountSettingsForm, } from "./AccountSettingsCard.js";
5
- export { openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, } from "./useBuilderStatus.js";
5
+ export { hasBuilderOAuthCredential, openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, } from "./useBuilderStatus.js";
6
6
  export { DeferredBuilderConnectPopover as BuilderConnectPopover } from "./deferred-builder-connect-popover.js";
7
7
  export { NewKeyMenu, normalizeKeyName, } from "./NewKeyMenu.js";
8
8
  export { SecretsSection } from "./SecretsSection.js";
@@ -59,6 +59,9 @@ export interface BuilderStatus {
59
59
  at: number;
60
60
  };
61
61
  }
62
+ export declare function hasBuilderOAuthCredential(status: Pick<BuilderStatus, "configured" | "envManaged"> & {
63
+ credentialSource?: BuilderStatus["credentialSource"] | null;
64
+ }): boolean;
62
65
  /**
63
66
  * Fetches Builder connection status from the neutral connection-status route.
64
67
  * The legacy /_agent-native/builder/status route remains available for older
@@ -85,7 +88,7 @@ export interface BuilderConnectFlowOptions {
85
88
  trackingSource?: string;
86
89
  /** Product flow that needed Builder connect, e.g. connect_llm. */
87
90
  trackingFlow?: string;
88
- /** Invoked after the status poll first sees `configured: true`. */
91
+ /** Invoked when the current user/org/workspace Builder credential is ready. */
89
92
  onConnected?: (state: {
90
93
  orgName: string | null;
91
94
  }) => void | Promise<void>;
@@ -7,6 +7,11 @@ import { openMcpAppHostLink } from "../mcp-app-host.js";
7
7
  import { oauthPopupWaitingUrl } from "../oauth-popup.js";
8
8
  import { scheduleAfterPaint } from "../use-after-paint.js";
9
9
  import { usePollLoop } from "../use-poll-loop.js";
10
+ export function hasBuilderOAuthCredential(status) {
11
+ return (status.configured &&
12
+ status.credentialSource !== "env" &&
13
+ (!status.envManaged || status.credentialSource != null));
14
+ }
10
15
  /**
11
16
  * Fetches Builder connection status from the neutral connection-status route.
12
17
  * The legacy /_agent-native/builder/status route remains available for older
@@ -660,10 +665,12 @@ export function useBuilderConnectFlow(opts = {}) {
660
665
  statusConnectUrlAtRef.current = nextConnectUrl ? Date.now() : null;
661
666
  const org = s.orgName ?? null;
662
667
  setOrgName(org);
663
- if (s.configured) {
668
+ const hasOAuthCredential = hasBuilderOAuthCredential(s);
669
+ if (hasOAuthCredential) {
664
670
  connectStartedAtRef.current = null;
671
+ setConnecting(false);
665
672
  }
666
- if (s.configured && !notifiedConnectedRef.current) {
673
+ if (hasOAuthCredential && !notifiedConnectedRef.current) {
667
674
  notifiedConnectedRef.current = true;
668
675
  notifyAgentEngineConfiguredChanged("builder-status");
669
676
  try {
@@ -673,7 +680,7 @@ export function useBuilderConnectFlow(opts = {}) {
673
680
  // The caller's callback is a UI convenience; status is already set.
674
681
  }
675
682
  }
676
- else if (!s.configured) {
683
+ else if (!hasOAuthCredential) {
677
684
  notifiedConnectedRef.current = false;
678
685
  }
679
686
  // Surface persisted auth-failure messages on idle refreshes, but don't
@@ -993,31 +1000,39 @@ export function useBuilderConnectFlow(opts = {}) {
993
1000
  if (started == null)
994
1001
  return;
995
1002
  const s = await fetchStatus(signal, connectAttemptIdRef.current ?? undefined);
996
- if (!mountedRef.current)
1003
+ if (!mountedRef.current || connectStartedAtRef.current !== started) {
997
1004
  return;
998
- if (s)
1005
+ }
1006
+ const orgName = s?.orgName ?? null;
1007
+ if (s) {
1008
+ if (statusUnavailableRef.current) {
1009
+ statusUnavailableRef.current = false;
1010
+ setError(null);
1011
+ }
1012
+ setHasFetchedStatus(true);
999
1013
  setStatusResolved(true);
1000
- if (s?.configured) {
1001
- setConfigured(true);
1014
+ setConfigured(!!s.configured);
1002
1015
  setCodeChangeConfigured(isCodeChangeConfigured(s));
1003
1016
  setEnvManaged(!!s.envManaged);
1004
1017
  setCredentialSource(s.credentialSource ?? null);
1005
1018
  setCanDisconnect(!!s.canDisconnect);
1006
1019
  setAgentNativeProvisioningEnabled(!!s.agentNativeProvisioningEnabled);
1007
1020
  setAgentNativeProvisioningToken(s.agentNativeProvisioningToken ?? null);
1008
- setAccountExists(false);
1021
+ setAccountExists(s.connectError?.code === "account_exists");
1009
1022
  setBuilderEnabled(!!s.builderEnabled);
1010
1023
  const nextConnectUrl = s.connectUrl ?? null;
1011
1024
  setStatusConnectUrl(nextConnectUrl);
1012
1025
  statusConnectUrlAtRef.current = nextConnectUrl ? Date.now() : null;
1013
- const org = s.orgName ?? null;
1014
- setOrgName(org);
1026
+ setOrgName(orgName);
1027
+ }
1028
+ if (s && hasBuilderOAuthCredential(s)) {
1029
+ setAccountExists(false);
1015
1030
  setConnecting(false);
1016
1031
  connectStartedAtRef.current = null;
1017
1032
  notifiedConnectedRef.current = true;
1018
1033
  notifyAgentEngineConfiguredChanged("builder-connect");
1019
1034
  try {
1020
- await onConnectedRef.current?.({ orgName: org });
1035
+ await onConnectedRef.current?.({ orgName });
1021
1036
  }
1022
1037
  catch {
1023
1038
  // coercion-ok: the connection itself succeeded and the UI state is
@@ -1162,7 +1177,7 @@ export function useBuilderConnectFlow(opts = {}) {
1162
1177
  connectStartedAtRef.current !== started) {
1163
1178
  return;
1164
1179
  }
1165
- if (s?.configured ||
1180
+ if ((s && hasBuilderOAuthCredential(s)) ||
1166
1181
  isCurrentConnectError(s?.connectError, started)) {
1167
1182
  break;
1168
1183
  }
@@ -1186,7 +1201,8 @@ export function useBuilderConnectFlow(opts = {}) {
1186
1201
  }
1187
1202
  if (callbackSuccessInFlightAtRef.current === started) {
1188
1203
  callbackSuccessInFlightAtRef.current = null;
1189
- if (popupClosedAtRef.current !== null && !s?.configured) {
1204
+ if (popupClosedAtRef.current !== null &&
1205
+ (!s || !hasBuilderOAuthCredential(s))) {
1190
1206
  popupClosedAtRef.current = Date.now();
1191
1207
  }
1192
1208
  }
@@ -1196,15 +1212,15 @@ export function useBuilderConnectFlow(opts = {}) {
1196
1212
  }
1197
1213
  if (!s)
1198
1214
  return;
1199
- if (!s.configured) {
1215
+ if (!hasBuilderOAuthCredential(s)) {
1200
1216
  const connectError = isCurrentConnectError(s?.connectError, started)
1201
1217
  ? s?.connectError
1202
1218
  : null;
1203
1219
  setHasFetchedStatus(true);
1204
1220
  if (s) {
1205
1221
  setStatusResolved(true);
1206
- setConfigured(false);
1207
- setCodeChangeConfigured(false);
1222
+ setConfigured(!!s.configured);
1223
+ setCodeChangeConfigured(isCodeChangeConfigured(s));
1208
1224
  setEnvManaged(!!s.envManaged);
1209
1225
  setCredentialSource(s.credentialSource ?? null);
1210
1226
  setCanDisconnect(!!s.canDisconnect);
@@ -8,6 +8,8 @@ export interface AgentChatRunningEventDetail {
8
8
  threadId?: string;
9
9
  /** Legacy alias used by AssistantChat and embedded chat frames. */
10
10
  tabId?: string;
11
+ /** Logical chat turn that may continue across multiple server runs. */
12
+ turnId?: string;
11
13
  runId?: string;
12
14
  reason?: string;
13
15
  }
@@ -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;
@@ -14,7 +14,7 @@ import { type DatabaseToolsOption } from "./scripts/db/tool-mode.js";
14
14
  * when it names them in `initialToolNames`. They stay in the searchable registry
15
15
  * either way.
16
16
  */
17
- export declare const FRAMEWORK_TOOL_GROUPS: readonly ["sharing", "review", "history", "featureFlags", "launchDarkly", "labs", "localization", "audit", "contextXray", "userProfile", "automation", "docs", "resources", "web", "workspaceApps", "chat", "email", "emailCatalog", "workspaceUserGroups", "orgServiceTokens", "orgAdministration"];
17
+ export declare const FRAMEWORK_TOOL_GROUPS: readonly ["sharing", "review", "history", "featureFlags", "launchDarkly", "labs", "localization", "audit", "contextXray", "userProfile", "automation", "docs", "resources", "browserSessions", "web", "workspaceApps", "chat", "email", "emailCatalog", "workspaceUserGroups", "orgServiceTokens", "orgAdministration"];
18
18
  export type FrameworkToolGroup = (typeof FRAMEWORK_TOOL_GROUPS)[number];
19
19
  /**
20
20
  * Per-group switches for the framework's own agent tools. Every group defaults
@@ -71,6 +71,8 @@ export interface FrameworkToolsOption {
71
71
  docs?: boolean;
72
72
  /** The `resources` tool — workspace notes, memory, and context files. */
73
73
  resources?: boolean;
74
+ /** Raw browser-session controls such as `run-browser-session-action`. */
75
+ browserSessions?: boolean;
74
76
  /** `web-request` and `web-search`. */
75
77
  web?: boolean;
76
78
  /** `describe-workspace-apps` and `call-agent` for cross-app delegation. */
@@ -28,6 +28,7 @@ export const FRAMEWORK_TOOL_GROUPS = [
28
28
  "automation",
29
29
  "docs",
30
30
  "resources",
31
+ "browserSessions",
31
32
  "web",
32
33
  "workspaceApps",
33
34
  "chat",
@@ -161,6 +162,7 @@ export const CORE_ACTION_GROUPS = {
161
162
  "manage-usage-alert": "automation",
162
163
  "get-usage-metrics": "automation",
163
164
  "get-builder-credit-usage": "automation",
165
+ "get-builder-referral-info": "automation",
164
166
  "context-manifest-get": "contextXray",
165
167
  "context-preview-get": "contextXray",
166
168
  "context-pin": "contextXray",