@agent-native/core 0.77.6 → 0.77.7

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 (159) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/agent/thread-data-builder.ts +21 -2
  5. package/corpus/core/src/cli/create.ts +35 -1
  6. package/corpus/core/src/cli/skills.ts +8 -0
  7. package/corpus/core/src/client/AgentPanel.tsx +57 -25
  8. package/corpus/core/src/client/MultiTabAssistantChat.tsx +31 -1
  9. package/corpus/core/src/client/analytics.ts +24 -2
  10. package/corpus/core/src/client/use-agent-engine-configured.ts +29 -15
  11. package/corpus/core/src/client/use-chat-threads.ts +104 -55
  12. package/corpus/core/src/server/analytics.ts +27 -3
  13. package/corpus/core/src/sharing/access.ts +35 -13
  14. package/corpus/core/src/sharing/actions/share-resource.ts +32 -9
  15. package/corpus/core/src/sharing/actions/unshare-resource.ts +25 -2
  16. package/corpus/core/src/sharing/registry.ts +9 -0
  17. package/corpus/core/src/vite/client.ts +3 -0
  18. package/corpus/templates/analytics/actions/data-source-status.ts +25 -14
  19. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +2 -2
  20. package/corpus/templates/analytics/app/lib/data-source-status.ts +47 -6
  21. package/corpus/templates/analytics/app/lib/data-sources.ts +14 -3
  22. package/corpus/templates/analytics/app/pages/DataSources.tsx +24 -5
  23. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +15 -43
  24. package/corpus/templates/analytics/changelog/2026-06-25-fixed-hubspot-data-source-status-so-private-app-tokens-legac.md +6 -0
  25. package/corpus/templates/analytics/netlify.toml +1 -0
  26. package/corpus/templates/analytics/server/lib/credential-keys.ts +3 -1
  27. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +12 -3
  28. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +5 -2
  29. package/corpus/templates/assets/changelog/2026-06-25-create-now-returns-from-agent-settings-to-the-asset-chat-eve.md +6 -0
  30. package/corpus/templates/assets/netlify.toml +1 -0
  31. package/corpus/templates/brain/netlify.toml +1 -0
  32. package/corpus/templates/calendar/app/i18n-data.ts +12 -0
  33. package/corpus/templates/calendar/app/lib/clipboard.ts +58 -0
  34. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +15 -9
  35. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +6 -2
  36. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-copy-buttons-no-longer-fail-when-clipboard-perm.md +6 -0
  37. package/corpus/templates/calendar/netlify.toml +1 -0
  38. package/corpus/templates/chat/netlify.toml +1 -0
  39. package/corpus/templates/clips/AGENTS.md +9 -7
  40. package/corpus/templates/clips/actions/add-vocabulary-term.ts +2 -2
  41. package/corpus/templates/clips/actions/archive-recording.ts +5 -2
  42. package/corpus/templates/clips/actions/clear-edits.ts +5 -2
  43. package/corpus/templates/clips/actions/create-folder.ts +7 -2
  44. package/corpus/templates/clips/actions/delete-folder.ts +7 -4
  45. package/corpus/templates/clips/actions/delete-recording-permanent.ts +5 -2
  46. package/corpus/templates/clips/actions/finalize-recording.ts +5 -2
  47. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -1
  48. package/corpus/templates/clips/actions/list-notifications.ts +9 -5
  49. package/corpus/templates/clips/actions/list-organization-state.ts +2 -1
  50. package/corpus/templates/clips/actions/list-recordings.ts +4 -1
  51. package/corpus/templates/clips/actions/move-recording.ts +5 -1
  52. package/corpus/templates/clips/actions/rename-folder.ts +6 -3
  53. package/corpus/templates/clips/actions/request-transcript.ts +5 -2
  54. package/corpus/templates/clips/actions/restore-recording.ts +5 -2
  55. package/corpus/templates/clips/actions/save-browser-diagnostics.ts +21 -8
  56. package/corpus/templates/clips/actions/set-chapters.ts +5 -2
  57. package/corpus/templates/clips/actions/set-thumbnail.ts +5 -2
  58. package/corpus/templates/clips/actions/split-recording.ts +5 -2
  59. package/corpus/templates/clips/actions/stitch-recordings.ts +6 -2
  60. package/corpus/templates/clips/actions/trash-recording.ts +5 -2
  61. package/corpus/templates/clips/actions/undo-edit.ts +5 -2
  62. package/corpus/templates/clips/actions/view-screen.ts +2 -1
  63. package/corpus/templates/clips/app/components/library/recording-card.tsx +24 -7
  64. package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +5 -1
  65. package/corpus/templates/clips/changelog/2026-06-25-chrome-extension-recordings-now-upload-large-capture-bursts.md +6 -0
  66. package/corpus/templates/clips/changelog/2026-06-25-console-diagnostics-keep-more-useful-non-secret-details-when.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-06-25-fixed-owner-recognition-so-clips-created-from-desktop-and-we.md +6 -0
  68. package/corpus/templates/clips/chrome-extension/package.json +3 -0
  69. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  70. package/corpus/templates/clips/chrome-extension/src/background.ts +85 -9
  71. package/corpus/templates/clips/chrome-extension/src/content-script.ts +55 -0
  72. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +79 -3
  73. package/corpus/templates/clips/chrome-extension/src/options.ts +10 -0
  74. package/corpus/templates/clips/chrome-extension/src/overlay.ts +6 -0
  75. package/corpus/templates/clips/chrome-extension/src/permission.ts +8 -1
  76. package/corpus/templates/clips/chrome-extension/src/popup.ts +65 -39
  77. package/corpus/templates/clips/chrome-extension/src/sentry.ts +206 -0
  78. package/corpus/templates/clips/chrome-extension/vite.config.ts +47 -0
  79. package/corpus/templates/clips/netlify.toml +1 -0
  80. package/corpus/templates/clips/server/db/index.ts +1 -0
  81. package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +35 -23
  82. package/corpus/templates/clips/server/lib/public-agent-context.ts +8 -1
  83. package/corpus/templates/clips/server/lib/recordings.ts +18 -2
  84. package/corpus/templates/clips/server/lib/slack-oauth.ts +9 -6
  85. package/corpus/templates/clips/server/plugins/onboarding-calendar.ts +6 -2
  86. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +2 -2
  87. package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +5 -2
  88. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -2
  89. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +5 -2
  90. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +5 -2
  91. package/corpus/templates/clips/shared/browser-diagnostics.ts +14 -4
  92. package/corpus/templates/content/netlify.toml +1 -0
  93. package/corpus/templates/design/netlify.toml +1 -0
  94. package/corpus/templates/dispatch/netlify.toml +1 -0
  95. package/corpus/templates/forms/netlify.toml +1 -0
  96. package/corpus/templates/macros/netlify.toml +1 -0
  97. package/corpus/templates/mail/app/components/email/EmailList.tsx +7 -3
  98. package/corpus/templates/mail/app/components/email/EmailThread.tsx +4 -1
  99. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +10 -2
  100. package/corpus/templates/mail/app/hooks/use-compose-state.ts +15 -5
  101. package/corpus/templates/mail/app/hooks/use-google-auth.ts +47 -9
  102. package/corpus/templates/mail/changelog/2026-06-25-account-avatars-stay-stable-while-mail-refreshes-google-acco.md +6 -0
  103. package/corpus/templates/mail/changelog/2026-06-25-mail-background-draft-saves-and-thread-prefetches-no-longer.md +6 -0
  104. package/corpus/templates/mail/netlify.toml +1 -0
  105. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +8 -0
  106. package/corpus/templates/plan/netlify.toml +1 -0
  107. package/corpus/templates/slides/netlify.toml +1 -0
  108. package/corpus/templates/videos/netlify.toml +1 -0
  109. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  110. package/dist/agent/thread-data-builder.js +23 -2
  111. package/dist/agent/thread-data-builder.js.map +1 -1
  112. package/dist/cli/create.d.ts.map +1 -1
  113. package/dist/cli/create.js +28 -1
  114. package/dist/cli/create.js.map +1 -1
  115. package/dist/cli/skills.d.ts +1 -1
  116. package/dist/cli/skills.d.ts.map +1 -1
  117. package/dist/cli/skills.js +8 -0
  118. package/dist/cli/skills.js.map +1 -1
  119. package/dist/client/AgentPanel.d.ts +8 -2
  120. package/dist/client/AgentPanel.d.ts.map +1 -1
  121. package/dist/client/AgentPanel.js +29 -15
  122. package/dist/client/AgentPanel.js.map +1 -1
  123. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  124. package/dist/client/MultiTabAssistantChat.js +4 -4
  125. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  126. package/dist/client/analytics.d.ts.map +1 -1
  127. package/dist/client/analytics.js +22 -2
  128. package/dist/client/analytics.js.map +1 -1
  129. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  130. package/dist/client/use-agent-engine-configured.js +23 -13
  131. package/dist/client/use-agent-engine-configured.js.map +1 -1
  132. package/dist/client/use-chat-threads.d.ts +4 -0
  133. package/dist/client/use-chat-threads.d.ts.map +1 -1
  134. package/dist/client/use-chat-threads.js +49 -4
  135. package/dist/client/use-chat-threads.js.map +1 -1
  136. package/dist/collab/routes.d.ts +2 -2
  137. package/dist/notifications/routes.d.ts +1 -1
  138. package/dist/observability/routes.d.ts +5 -5
  139. package/dist/resources/handlers.d.ts +2 -2
  140. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  141. package/dist/server/analytics.d.ts +4 -0
  142. package/dist/server/analytics.d.ts.map +1 -1
  143. package/dist/server/analytics.js +19 -3
  144. package/dist/server/analytics.js.map +1 -1
  145. package/dist/server/transcribe-voice.d.ts +1 -1
  146. package/dist/sharing/access.d.ts.map +1 -1
  147. package/dist/sharing/access.js +26 -12
  148. package/dist/sharing/access.js.map +1 -1
  149. package/dist/sharing/actions/share-resource.js +20 -9
  150. package/dist/sharing/actions/share-resource.js.map +1 -1
  151. package/dist/sharing/actions/unshare-resource.js +13 -2
  152. package/dist/sharing/actions/unshare-resource.js.map +1 -1
  153. package/dist/sharing/registry.d.ts +9 -0
  154. package/dist/sharing/registry.d.ts.map +1 -1
  155. package/dist/sharing/registry.js.map +1 -1
  156. package/dist/vite/client.d.ts.map +1 -1
  157. package/dist/vite/client.js +1 -0
  158. package/dist/vite/client.js.map +1 -1
  159. package/package.json +3 -3
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 1932
31
- - template files: 4279
31
+ - template files: 4289
@@ -1,5 +1,17 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.77.7
4
+
5
+ ### Patch Changes
6
+
7
+ - e5bdcb3: Paginate the All Chats history client so older conversations can be loaded without truncating the history list.
8
+ - e5bdcb3: Drop benign reasonless browser AbortError events from Sentry reporting.
9
+ - e5bdcb3: Repair duplicate persisted chat message ids during thread normalization.
10
+ - e5bdcb3: Ensure hosted template apps can inject Google Analytics from Netlify build-time configuration.
11
+ - e5bdcb3: Normalize email casing when resolving shareable-resource ownership and user shares, and let resources opt into owner access across active-org drift.
12
+ - e5bdcb3: Re-check current LLM connection status before a stale missing-credentials event can keep chat locked after reconnecting Builder, and prevent page chat surfaces from rendering sidebar settings in the main content area.
13
+ - e5bdcb3: Allow browser Sentry initialization to build a DSN from Vite-exposed Sentry client key, project id, and ingest host env vars when runtime config injection is unavailable.
14
+
3
15
  ## 0.77.6
4
16
 
5
17
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.77.6",
3
+ "version": "0.77.7",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -211,8 +211,8 @@
211
211
  "@react-router/dev": "^8.0.1",
212
212
  "@react-router/fs-routes": "^8.0.1",
213
213
  "@resvg/resvg-js": "^2.6.2",
214
- "@sentry/browser": "^10.50.0",
215
- "@sentry/node": "^10.50.0",
214
+ "@sentry/browser": "^10.61.0",
215
+ "@sentry/node": "^10.61.0",
216
216
  "@standard-schema/spec": "^1.1.0",
217
217
  "@tanstack/react-table": "^8.21.3",
218
218
  "@tiptap/core": "3.27.1",
@@ -482,7 +482,26 @@ function normalizeAssistantToolCallIds(message: any): any {
482
482
  */
483
483
  export function normalizeThreadRepository(repo: any): any {
484
484
  const normalized = repo && typeof repo === "object" ? { ...repo } : {};
485
- const sourceMessages = Array.isArray(repo?.messages) ? repo.messages : [];
485
+ const sourceMessages: any[] = Array.isArray(repo?.messages)
486
+ ? repo.messages
487
+ : [];
488
+ const firstIndexById = new Map<string, number>();
489
+ const lastEntryById = new Map<string, any>();
490
+ sourceMessages.forEach((entry, index) => {
491
+ const id = messageId(getStoredMessage(entry));
492
+ if (!id) return;
493
+ if (!firstIndexById.has(id)) firstIndexById.set(id, index);
494
+ lastEntryById.set(id, entry);
495
+ });
496
+ const uniqueSourceMessages = sourceMessages
497
+ .filter((entry, index) => {
498
+ const id = messageId(getStoredMessage(entry));
499
+ return id && firstIndexById.get(id) === index;
500
+ })
501
+ .map((entry) => {
502
+ const id = messageId(getStoredMessage(entry));
503
+ return (id && lastEntryById.get(id)) || entry;
504
+ });
486
505
  const messages: Array<{
487
506
  message: any;
488
507
  parentId: string | null;
@@ -491,7 +510,7 @@ export function normalizeThreadRepository(repo: any): any {
491
510
  const seenIds = new Set<string>();
492
511
  let previousId: string | null = null;
493
512
 
494
- for (const entry of sourceMessages) {
513
+ for (const entry of uniqueSourceMessages) {
495
514
  const message = getStoredMessage(entry);
496
515
  const id = messageId(message);
497
516
  if (!id) continue;
@@ -27,6 +27,10 @@ const STANDALONE_EXACT_DEPENDENCY_OVERRIDES: Record<string, string> = {
27
27
  "@react-router/fs-routes": "8.0.1",
28
28
  "react-router": "8.0.1",
29
29
  };
30
+ const SENTRY_MINIMUM_RELEASE_AGE_EXCLUDES = [
31
+ '"@sentry/browser"',
32
+ '"@sentry/node"',
33
+ ];
30
34
  const FIRST_PARTY_TARBALL_SYMLINK_EXCLUDES = [
31
35
  "*/CLAUDE.md",
32
36
  "*/.claude/skills",
@@ -1054,7 +1058,12 @@ function postProcessStandalone(
1054
1058
  '"@assistant-ui/tap"': '"^0.5.14"',
1055
1059
  };
1056
1060
  }
1057
- const updated = mergeWorkspaceYamlSections(existing, sections);
1061
+ let updated = mergeWorkspaceYamlSections(existing, sections);
1062
+ updated = mergeWorkspaceYamlListItems(
1063
+ updated,
1064
+ "minimumReleaseAgeExclude",
1065
+ SENTRY_MINIMUM_RELEASE_AGE_EXCLUDES,
1066
+ );
1058
1067
  if (updated !== existing) {
1059
1068
  fs.writeFileSync(wsPath, updated);
1060
1069
  }
@@ -1366,6 +1375,31 @@ function mergeWorkspaceYamlSections(
1366
1375
  return result;
1367
1376
  }
1368
1377
 
1378
+ function mergeWorkspaceYamlListItems(
1379
+ yaml: string,
1380
+ section: string,
1381
+ items: string[],
1382
+ ): string {
1383
+ let result = yaml;
1384
+ for (const item of items) {
1385
+ const rendered = ` - ${item}`;
1386
+ if (result.includes(rendered)) continue;
1387
+ const sectionHeader = new RegExp(`^${section}:\\s*$`, "m");
1388
+ const match = sectionHeader.exec(result);
1389
+ if (match) {
1390
+ const insertAt = match.index + match[0].length;
1391
+ result =
1392
+ result.slice(0, insertAt) + `\n${rendered}` + result.slice(insertAt);
1393
+ } else {
1394
+ result =
1395
+ result.trimEnd() +
1396
+ (result ? "\n" : "") +
1397
+ `\n${section}:\n${rendered}\n`;
1398
+ }
1399
+ }
1400
+ return result;
1401
+ }
1402
+
1369
1403
  /**
1370
1404
  * Load the pnpm workspace catalog.
1371
1405
  * First tries the build-time snapshot at dist/catalog.json (works when
@@ -795,6 +795,14 @@ or cross either artboard, remove the label and explain the transition with a
795
795
  nearby annotation instead. Before handoff, inspect the top canvas at default zoom
796
796
  and move any frame whose label, connector, or annotation crosses another frame.
797
797
 
798
+ **Board-unit spacing defaults.** The canvas coordinate system uses approximately 2 board units per screen pixel. \`browser\` frames occupy roughly 700 × 600 board units; \`desktop\` frames roughly 900 × 700 board units. Apply these minimum x/y gaps when placing frames explicitly — any less and frames will touch or overlap:
799
+
800
+ - x-gap between \`browser\` frames: **≥ 1100** (700-unit frame + 400-unit gutter)
801
+ - x-gap between \`desktop\` frames: **≥ 1300** (900-unit frame + 400-unit gutter)
802
+ - y-gap between rows of any surface: **≥ 1400** (includes frame height + section header + buffer)
803
+
804
+ When in doubt, use larger values — the canvas auto-zooms to fit everything.
805
+
798
806
  **Canvas annotations are designer notes on the artboard.** When a top canvas is
799
807
  present, sprinkle Figma-style notes near the frames they explain: a short
800
808
  heading, supporting text, and bullets — plain text layers, never bordered or
@@ -164,6 +164,12 @@ const CLI_DEFAULT = "claude";
164
164
  const EXEC_MODE_KEY = "agent-native-exec-mode";
165
165
  type ExecMode = "build" | "plan";
166
166
  type PanelMode = "chat" | "cli" | "resources" | "settings";
167
+ export function normalizeAgentPanelModeForSurface(
168
+ mode: PanelMode,
169
+ allowSettingsMode: boolean,
170
+ ): PanelMode {
171
+ return mode === "settings" && !allowSettingsMode ? "chat" : mode;
172
+ }
167
173
  const AGENT_PANEL_FONT_FAMILY =
168
174
  'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
169
175
  const AGENT_PANEL_ROOT_STYLE = {
@@ -377,15 +383,11 @@ export function shouldShowAgentPanelChatTabBar(
377
383
  }
378
384
 
379
385
  export function shouldShowAgentPanelPageNewChatButton(
380
- tabs: MultiTabAssistantChatHeaderProps["tabs"],
386
+ _tabs: MultiTabAssistantChatHeaderProps["tabs"],
381
387
  activeTabId: string,
382
- activeTabMessageCount: number,
388
+ _activeTabMessageCount: number,
383
389
  ) {
384
- const activeTab = tabs.find((tab) => tab.id === activeTabId);
385
- return (
386
- Boolean(activeTabId) &&
387
- (activeTabMessageCount > 0 || activeTab?.status === "running")
388
- );
390
+ return Boolean(activeTabId);
389
391
  }
390
392
 
391
393
  export function shouldShowAgentPanelCliTabBar(cliTabs: string[]) {
@@ -548,6 +550,8 @@ export interface AgentPanelProps extends Omit<
548
550
  showTabBar?: boolean;
549
551
  /** Show a compact New chat action in page chat when the main header is hidden. */
550
552
  showPageNewChatButton?: boolean;
553
+ /** Allow the sidebar settings view to render inside this panel. Default: true. */
554
+ allowSettingsMode?: boolean;
551
555
  /** Capability gate for source edits and CLI access. */
552
556
  codeAccess?: AgentPanelCodeAccess;
553
557
  }
@@ -666,6 +670,7 @@ function AgentPanelInner({
666
670
  chatNotice,
667
671
  showTabBar = true,
668
672
  showPageNewChatButton = false,
673
+ allowSettingsMode = true,
669
674
  codeAccess,
670
675
  ...assistantChatProps
671
676
  }: AgentPanelProps) {
@@ -715,9 +720,9 @@ function AgentPanelInner({
715
720
  saved === "resources" ||
716
721
  saved === "settings"
717
722
  )
718
- return saved;
723
+ return normalizeAgentPanelModeForSurface(saved, allowSettingsMode);
719
724
  } catch {}
720
- return defaultMode;
725
+ return normalizeAgentPanelModeForSurface(defaultMode, allowSettingsMode);
721
726
  });
722
727
  useEffect(() => {
723
728
  try {
@@ -728,9 +733,18 @@ function AgentPanelInner({
728
733
  section: string | null;
729
734
  requestKey: number;
730
735
  }>({ section: null, requestKey: 0 });
731
- const switchMode = useCallback((m: PanelMode) => {
732
- startTransition(() => setMode(m));
733
- }, []);
736
+ const switchMode = useCallback(
737
+ (m: PanelMode) => {
738
+ startTransition(() =>
739
+ setMode(normalizeAgentPanelModeForSurface(m, allowSettingsMode)),
740
+ );
741
+ },
742
+ [allowSettingsMode],
743
+ );
744
+ useEffect(() => {
745
+ const nextMode = normalizeAgentPanelModeForSurface(mode, allowSettingsMode);
746
+ if (nextMode !== mode) switchMode(nextMode);
747
+ }, [mode, allowSettingsMode, switchMode]);
734
748
  const openRunThread = useCallback(
735
749
  (threadId: string, run?: AgentRun) => {
736
750
  switchMode("chat");
@@ -794,6 +808,10 @@ function AgentPanelInner({
794
808
  section: section ?? null,
795
809
  requestKey: prev.requestKey + 1,
796
810
  }));
811
+ if (!allowSettingsMode) {
812
+ switchMode("chat");
813
+ return;
814
+ }
797
815
  switchMode("settings");
798
816
  }
799
817
  window.addEventListener(
@@ -805,7 +823,7 @@ function AgentPanelInner({
805
823
  AGENT_PANEL_OPEN_SETTINGS_EVENT,
806
824
  handleOpenSettings,
807
825
  );
808
- }, [switchMode]);
826
+ }, [allowSettingsMode, switchMode]);
809
827
 
810
828
  // CLI terminal tabs (ephemeral — not persisted to SQL)
811
829
  const [cliTabs, setCliTabs] = useState<string[]>(["cli-1"]);
@@ -1151,15 +1169,17 @@ function AgentPanelInner({
1151
1169
  <DropdownMenuSeparator />
1152
1170
  </>
1153
1171
  )}
1154
- <DropdownMenuItem
1155
- onSelect={() => switchMode("settings")}
1156
- className={cn(
1157
- mode === "settings" ? "font-medium" : "text-muted-foreground",
1158
- )}
1159
- >
1160
- <IconSettings size={14} className="shrink-0" />
1161
- {t("agentPanel.settings")}
1162
- </DropdownMenuItem>
1172
+ {allowSettingsMode && (
1173
+ <DropdownMenuItem
1174
+ onSelect={() => switchMode("settings")}
1175
+ className={cn(
1176
+ mode === "settings" ? "font-medium" : "text-muted-foreground",
1177
+ )}
1178
+ >
1179
+ <IconSettings size={14} className="shrink-0" />
1180
+ {t("agentPanel.settings")}
1181
+ </DropdownMenuItem>
1182
+ )}
1163
1183
  <DropdownMenuItem onSelect={() => setFeedbackOpen(true)}>
1164
1184
  <IconMessageDots size={14} className="shrink-0" />
1165
1185
  {t("agentPanel.feedback")}
@@ -1241,6 +1261,7 @@ function AgentPanelInner({
1241
1261
  [
1242
1262
  activeCliTab,
1243
1263
  addCliTab,
1264
+ allowSettingsMode,
1244
1265
  availableClis,
1245
1266
  canUseCodeTools,
1246
1267
  closeAllCliTabs,
@@ -1287,7 +1308,7 @@ function AgentPanelInner({
1287
1308
  data-agent-page-new-chat=""
1288
1309
  aria-label={t("agentPanel.newChat")}
1289
1310
  onClick={() => {
1290
- addTab();
1311
+ void addTab();
1291
1312
  }}
1292
1313
  className="pointer-events-auto inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background/95 px-2.5 text-xs font-medium text-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1293
1314
  >
@@ -2241,9 +2262,16 @@ export interface AgentChatSurfaceProps extends AgentPanelProps {
2241
2262
 
2242
2263
  export function shouldDefaultAgentChatSurfacePageNewChatButton(
2243
2264
  mode: AgentChatSurfaceMode | undefined,
2244
- showTabBar: boolean | undefined,
2265
+ _showTabBar: boolean | undefined,
2245
2266
  ): boolean {
2246
- return mode === "page" && showTabBar !== false;
2267
+ return mode === "page";
2268
+ }
2269
+
2270
+ export function shouldAllowAgentChatSurfaceSettingsMode(
2271
+ mode: AgentChatSurfaceMode | undefined,
2272
+ allowSettingsMode: boolean | undefined,
2273
+ ): boolean {
2274
+ return allowSettingsMode ?? mode !== "page";
2247
2275
  }
2248
2276
 
2249
2277
  /**
@@ -2272,6 +2300,10 @@ export function AgentChatSurface({
2272
2300
  {...props}
2273
2301
  defaultMode={defaultMode}
2274
2302
  isFullscreen={isFullscreen ?? pageMode}
2303
+ allowSettingsMode={shouldAllowAgentChatSurfaceSettingsMode(
2304
+ mode,
2305
+ props.allowSettingsMode,
2306
+ )}
2275
2307
  showPageNewChatButton={
2276
2308
  showPageNewChatButton ?? defaultShowPageNewChatButton
2277
2309
  }
@@ -428,16 +428,24 @@ function HistoryPopover({
428
428
  openTabIds,
429
429
  activeThreadId,
430
430
  currentScope,
431
+ hasMoreThreads = false,
432
+ isLoadingMoreThreads = false,
433
+ loadError,
431
434
  onSelect,
432
435
  onClose,
436
+ onLoadMore,
433
437
  onSearch,
434
438
  }: {
435
439
  threads: ChatThreadSummary[];
436
440
  openTabIds: Set<string>;
437
441
  activeThreadId: string | null;
438
442
  currentScope?: ChatThreadScope | null;
443
+ hasMoreThreads?: boolean;
444
+ isLoadingMoreThreads?: boolean;
445
+ loadError?: string | null;
439
446
  onSelect: (id: string) => void;
440
447
  onClose: () => void;
448
+ onLoadMore?: () => void;
441
449
  onSearch?: (query: string) => Promise<ChatThreadSummary[]>;
442
450
  }) {
443
451
  const [search, setSearch] = useState("");
@@ -542,7 +550,11 @@ function HistoryPopover({
542
550
  />
543
551
  </div>
544
552
  <div className="max-h-64 overflow-y-auto py-1">
545
- {isSearching ? (
553
+ {loadError && !search.trim() ? (
554
+ <div className="px-3 py-4 text-xs text-amber-500 text-center">
555
+ {loadError}
556
+ </div>
557
+ ) : isSearching ? (
546
558
  <div className="px-3 py-4 text-xs text-muted-foreground text-center">
547
559
  Searching...
548
560
  </div>
@@ -599,6 +611,16 @@ function HistoryPopover({
599
611
  ),
600
612
  )
601
613
  )}
614
+ {!search.trim() && hasMoreThreads && (
615
+ <button
616
+ type="button"
617
+ onClick={() => onLoadMore?.()}
618
+ disabled={isLoadingMoreThreads}
619
+ className="mx-1 mt-1 flex w-[calc(100%-0.5rem)] items-center justify-center rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground disabled:cursor-default disabled:opacity-60"
620
+ >
621
+ {isLoadingMoreThreads ? "Loading..." : "Load older chats"}
622
+ </button>
623
+ )}
602
624
  </div>
603
625
  </PopoverContent>
604
626
  </Popover>
@@ -1034,7 +1056,11 @@ export function MultiTabAssistantChat({
1034
1056
  saveThreadData,
1035
1057
  generateTitle,
1036
1058
  searchThreads,
1059
+ loadMoreThreads,
1037
1060
  refreshThreads,
1061
+ hasMoreThreads,
1062
+ isLoadingMoreThreads,
1063
+ threadsLoadError,
1038
1064
  isNewThread,
1039
1065
  } = useChatThreads(apiUrl, storageKey, scope, {
1040
1066
  restoreActiveThread,
@@ -2649,8 +2675,12 @@ export function MultiTabAssistantChat({
2649
2675
  openTabIds={new Set(openTabIds)}
2650
2676
  activeThreadId={activeThreadId}
2651
2677
  currentScope={scope}
2678
+ hasMoreThreads={hasMoreThreads}
2679
+ isLoadingMoreThreads={isLoadingMoreThreads}
2680
+ loadError={threadsLoadError}
2652
2681
  onSelect={openFromHistory}
2653
2682
  onClose={() => setShowHistory(false)}
2683
+ onLoadMore={loadMoreThreads}
2654
2684
  onSearch={searchThreads}
2655
2685
  />
2656
2686
  )}
@@ -501,9 +501,12 @@ function shouldDropBrowserSentryNoise(event: Sentry.Event): boolean {
501
501
  .toLowerCase();
502
502
  return (
503
503
  exceptionValue === "the user aborted a request." ||
504
+ exceptionValue === "signal is aborted without reason" ||
504
505
  exceptionValue === "aborterror: the user aborted a request." ||
506
+ exceptionValue === "aborterror: signal is aborted without reason" ||
505
507
  (exceptionType === "aborterror" &&
506
- exceptionValue.includes("the user aborted a request"))
508
+ (exceptionValue.includes("the user aborted a request") ||
509
+ exceptionValue.includes("signal is aborted without reason")))
507
510
  );
508
511
  })
509
512
  ) {
@@ -534,12 +537,31 @@ function shouldDropBrowserSentryNoise(event: Sentry.Event): boolean {
534
537
  );
535
538
  }
536
539
 
540
+ function firstNonEmpty(...values: Array<string | undefined>): string {
541
+ for (const value of values) {
542
+ const trimmed = value?.trim();
543
+ if (trimmed) return trimmed;
544
+ }
545
+ return "";
546
+ }
547
+
548
+ function resolveClientSentryDsnFromKeyProject(
549
+ env: Record<string, string | undefined>,
550
+ ): string | undefined {
551
+ const key = firstNonEmpty(env.VITE_SENTRY_CLIENT_KEY);
552
+ const projectId = firstNonEmpty(env.VITE_SENTRY_PROJECT_ID);
553
+ const host = firstNonEmpty(env.VITE_SENTRY_INGEST_HOST);
554
+ if (!key || !projectId || !host) return undefined;
555
+ return `https://${key}@${host}/${projectId}`;
556
+ }
557
+
537
558
  function getClientSentryDsn(): string | undefined {
538
559
  const env = (import.meta.env as Record<string, string | undefined>) ?? {};
539
560
  return (
540
561
  env.VITE_SENTRY_CLIENT_DSN ||
541
562
  env.VITE_SENTRY_DSN ||
542
- window.__AGENT_NATIVE_CONFIG__?.sentryDsn
563
+ window.__AGENT_NATIVE_CONFIG__?.sentryDsn ||
564
+ resolveClientSentryDsnFromKeyProject(env)
543
565
  );
544
566
  }
545
567
 
@@ -26,21 +26,13 @@ export function useAgentEngineConfigured(
26
26
  ): UseAgentEngineConfiguredResult {
27
27
  const [state, setState] = useState<AgentEngineConfiguredState>("unknown");
28
28
 
29
- // Mid-run adapter signal that no key is usable — honored even when disabled.
30
29
  useEffect(() => {
31
- const onMissing = () => setState("missing");
32
- window.addEventListener("agent-chat:missing-api-key", onMissing);
33
- return () =>
34
- window.removeEventListener("agent-chat:missing-api-key", onMissing);
35
- }, []);
36
-
37
- useEffect(() => {
38
- if (!enabled) {
39
- setState("configured");
40
- return;
41
- }
42
30
  let cancelled = false;
43
- const check = async () => {
31
+ const check = async (options?: { missingFallback?: boolean }) => {
32
+ if (!enabled) {
33
+ setState("configured");
34
+ return;
35
+ }
44
36
  const [envKeys, builderStatus, engineStatus] = await Promise.all([
45
37
  fetch(agentNativePath("/_agent-native/env-status"))
46
38
  .then((r) => (r.ok ? r.json() : null))
@@ -55,6 +47,7 @@ export function useAgentEngineConfigured(
55
47
  if (cancelled) return;
56
48
  // All three failed — likely a flaky network; keep the current state.
57
49
  if (envKeys == null && builderStatus == null && engineStatus == null) {
50
+ if (options?.missingFallback) setState("missing");
58
51
  return;
59
52
  }
60
53
  const keys = (envKeys ?? []) as Array<{
@@ -68,11 +61,32 @@ export function useAgentEngineConfigured(
68
61
  engineStatus?.configured === true;
69
62
  setState(anyConfigured ? "configured" : "missing");
70
63
  };
64
+ const onConfiguredChanged = () => {
65
+ void check();
66
+ };
67
+ const onMissing = () => {
68
+ if (!enabled) {
69
+ setState("configured");
70
+ return;
71
+ }
72
+ void check({ missingFallback: true });
73
+ };
74
+
71
75
  void check();
72
- window.addEventListener("agent-engine:configured-changed", check);
76
+ window.addEventListener(
77
+ "agent-engine:configured-changed",
78
+ onConfiguredChanged,
79
+ );
80
+ // A stale failed stream can arrive after a reconnect succeeds. Re-check the
81
+ // current status before pinning the composer in setup.
82
+ window.addEventListener("agent-chat:missing-api-key", onMissing);
73
83
  return () => {
74
84
  cancelled = true;
75
- window.removeEventListener("agent-engine:configured-changed", check);
85
+ window.removeEventListener(
86
+ "agent-engine:configured-changed",
87
+ onConfiguredChanged,
88
+ );
89
+ window.removeEventListener("agent-chat:missing-api-key", onMissing);
76
90
  };
77
91
  }, [enabled]);
78
92