@agent-native/dispatch 0.38.15 → 0.38.16-nightly-20260927213438

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 (94) hide show
  1. package/dist/actions/create-workspace-resource.d.ts +13 -13
  2. package/dist/actions/provider-api-register.d.ts +12 -12
  3. package/dist/components/automation-create-dialog.js +4 -4
  4. package/dist/components/automation-create-dialog.js.map +1 -1
  5. package/dist/components/layout/Header.js +1 -1
  6. package/dist/components/layout/Header.js.map +1 -1
  7. package/dist/components/layout/Layout.d.ts +7 -0
  8. package/dist/components/layout/Layout.d.ts.map +1 -1
  9. package/dist/components/layout/Layout.js +19 -4
  10. package/dist/components/layout/Layout.js.map +1 -1
  11. package/dist/components/messaging-setup-panel.d.ts.map +1 -1
  12. package/dist/components/messaging-setup-panel.js +7 -28
  13. package/dist/components/messaging-setup-panel.js.map +1 -1
  14. package/dist/components/transactional-email-send-log.js +1 -1
  15. package/dist/components/transactional-email-send-log.js.map +1 -1
  16. package/dist/components/ui/alert-dialog.d.ts +1 -20
  17. package/dist/components/ui/alert-dialog.d.ts.map +1 -1
  18. package/dist/components/ui/alert-dialog.js +1 -26
  19. package/dist/components/ui/alert-dialog.js.map +1 -1
  20. package/dist/components/ui/button.d.ts +1 -11
  21. package/dist/components/ui/button.d.ts.map +1 -1
  22. package/dist/components/ui/button.js +1 -33
  23. package/dist/components/ui/button.js.map +1 -1
  24. package/dist/components/ui/carousel.d.ts +2 -2
  25. package/dist/components/ui/carousel.d.ts.map +1 -1
  26. package/dist/components/ui/input.d.ts +1 -3
  27. package/dist/components/ui/input.d.ts.map +1 -1
  28. package/dist/components/ui/input.js +1 -8
  29. package/dist/components/ui/input.js.map +1 -1
  30. package/dist/components/ui/sidebar.d.ts +4 -2
  31. package/dist/components/ui/sidebar.d.ts.map +1 -1
  32. package/dist/components/ui/switch.d.ts +1 -4
  33. package/dist/components/ui/switch.d.ts.map +1 -1
  34. package/dist/components/ui/switch.js +1 -7
  35. package/dist/components/ui/switch.js.map +1 -1
  36. package/dist/components/ui/tabs.d.ts +1 -7
  37. package/dist/components/ui/tabs.d.ts.map +1 -1
  38. package/dist/components/ui/tabs.js +1 -12
  39. package/dist/components/ui/tabs.js.map +1 -1
  40. package/dist/components/ui/toggle.d.ts +1 -12
  41. package/dist/components/ui/toggle.d.ts.map +1 -1
  42. package/dist/components/ui/toggle.js +1 -25
  43. package/dist/components/ui/toggle.js.map +1 -1
  44. package/dist/components/usage-alerts-panel.d.ts.map +1 -1
  45. package/dist/components/usage-alerts-panel.js +1 -1
  46. package/dist/components/usage-alerts-panel.js.map +1 -1
  47. package/dist/components/workspace-app-search.d.ts.map +1 -1
  48. package/dist/components/workspace-app-search.js +1 -1
  49. package/dist/components/workspace-app-search.js.map +1 -1
  50. package/dist/routes/pages/automations.d.ts.map +1 -1
  51. package/dist/routes/pages/automations.js +2 -2
  52. package/dist/routes/pages/automations.js.map +1 -1
  53. package/dist/routes/pages/metrics.d.ts.map +1 -1
  54. package/dist/routes/pages/metrics.js +1 -1
  55. package/dist/routes/pages/metrics.js.map +1 -1
  56. package/dist/routes/pages/settings.d.ts +9 -0
  57. package/dist/routes/pages/settings.d.ts.map +1 -1
  58. package/dist/routes/pages/settings.js +65 -12
  59. package/dist/routes/pages/settings.js.map +1 -1
  60. package/dist/routes/pages/thread-debug.js +1 -1
  61. package/dist/routes/pages/thread-debug.js.map +1 -1
  62. package/dist/routes/pages/vault.d.ts.map +1 -1
  63. package/dist/routes/pages/vault.js +2 -2
  64. package/dist/routes/pages/vault.js.map +1 -1
  65. package/dist/server/lib/usage-metrics-store.js +2 -3
  66. package/dist/server/lib/usage-metrics-store.js.map +1 -1
  67. package/dist/server/plugins/db.d.ts.map +1 -1
  68. package/dist/server/plugins/db.js +44 -0
  69. package/dist/server/plugins/db.js.map +1 -1
  70. package/package.json +4 -4
  71. package/src/components/automation-create-dialog.tsx +6 -6
  72. package/src/components/layout/Header.tsx +2 -2
  73. package/src/components/layout/Layout.spec.tsx +28 -0
  74. package/src/components/layout/Layout.tsx +73 -43
  75. package/src/components/messaging-setup-panel.spec.tsx +5 -1
  76. package/src/components/messaging-setup-panel.tsx +9 -45
  77. package/src/components/transactional-email-send-log.tsx +1 -1
  78. package/src/components/ui/alert-dialog.tsx +1 -139
  79. package/src/components/ui/button.tsx +1 -56
  80. package/src/components/ui/input.tsx +1 -22
  81. package/src/components/ui/switch.tsx +1 -27
  82. package/src/components/ui/tabs.tsx +1 -53
  83. package/src/components/ui/toggle.tsx +1 -43
  84. package/src/components/usage-alerts-panel.tsx +6 -12
  85. package/src/components/workspace-app-search.tsx +2 -1
  86. package/src/routes/pages/automations.tsx +4 -2
  87. package/src/routes/pages/metrics.tsx +2 -1
  88. package/src/routes/pages/settings.spec.tsx +179 -0
  89. package/src/routes/pages/settings.tsx +179 -119
  90. package/src/routes/pages/thread-debug.tsx +1 -1
  91. package/src/routes/pages/vault.tsx +3 -2
  92. package/src/server/lib/usage-metrics-store.spec.ts +1 -0
  93. package/src/server/lib/usage-metrics-store.ts +2 -2
  94. package/src/server/plugins/db.ts +49 -0
@@ -124,7 +124,7 @@ function AlertEditor({
124
124
  }
125
125
  disabled={isExisting}
126
126
  >
127
- <SelectTrigger className="h-9 text-sm">
127
+ <SelectTrigger className="text-sm">
128
128
  <SelectValue />
129
129
  </SelectTrigger>
130
130
  <SelectContent>
@@ -143,7 +143,7 @@ function AlertEditor({
143
143
  value={draft.unit}
144
144
  onValueChange={(value) => onChange({ unit: value as AlertUnit })}
145
145
  >
146
- <SelectTrigger className="h-9 text-sm">
146
+ <SelectTrigger className="text-sm">
147
147
  <SelectValue />
148
148
  </SelectTrigger>
149
149
  <SelectContent>
@@ -161,7 +161,7 @@ function AlertEditor({
161
161
  step={draft.unit === "tokens" ? "1" : "0.01"}
162
162
  value={draft.limit}
163
163
  onChange={(event) => onChange({ limit: event.target.value })}
164
- className="h-9 text-sm"
164
+ className="text-sm"
165
165
  />
166
166
  </label>
167
167
  <label className="space-y-1.5 text-xs text-muted-foreground">
@@ -172,7 +172,7 @@ function AlertEditor({
172
172
  onChange({ period: value as "day" | "month" })
173
173
  }
174
174
  >
175
- <SelectTrigger className="h-9 text-sm">
175
+ <SelectTrigger className="text-sm">
176
176
  <SelectValue />
177
177
  </SelectTrigger>
178
178
  <SelectContent>
@@ -199,19 +199,13 @@ function AlertEditor({
199
199
  <Button
200
200
  type="button"
201
201
  size="sm"
202
- className="ms-auto h-8"
202
+ className="ms-auto"
203
203
  onClick={onSave}
204
204
  disabled={isPending}
205
205
  >
206
206
  Save
207
207
  </Button>
208
- <Button
209
- type="button"
210
- variant="ghost"
211
- size="sm"
212
- className="h-8"
213
- onClick={onCancel}
214
- >
208
+ <Button type="button" variant="ghost" size="sm" onClick={onCancel}>
215
209
  Cancel
216
210
  </Button>
217
211
  </div>
@@ -37,10 +37,11 @@ export function WorkspaceAppSearch({
37
37
  />
38
38
  <Input
39
39
  id={inputId}
40
+ size="sm"
40
41
  value={query}
41
42
  onChange={(event) => onQueryChange(event.target.value)}
42
43
  placeholder={placeholder}
43
- className="h-9 bg-background ps-9"
44
+ className="bg-background ps-9"
44
45
  autoComplete="off"
45
46
  />
46
47
  </div>
@@ -228,11 +228,12 @@ export default function AutomationsRoute() {
228
228
  className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
229
229
  />
230
230
  <Input
231
+ size="sm"
231
232
  value={query}
232
233
  onChange={(event) => setQuery(event.target.value)}
233
234
  placeholder="Search automations"
234
235
  aria-label="Search automations"
235
- className="h-8 pl-8 text-xs"
236
+ className="pl-8 text-xs"
236
237
  />
237
238
  </div>
238
239
  <div className="flex items-center gap-2 text-sm text-muted-foreground">
@@ -251,7 +252,8 @@ export default function AutomationsRoute() {
251
252
  }}
252
253
  >
253
254
  <SelectTrigger
254
- className="h-8 w-[10.5rem] text-xs"
255
+ size="sm"
256
+ className="w-[10.5rem] text-xs"
255
257
  aria-label="Automation view"
256
258
  >
257
259
  <SelectValue />
@@ -570,7 +570,8 @@ function UserSelector({
570
570
  }
571
571
  >
572
572
  <SelectTrigger
573
- className="h-8 w-[210px] text-xs"
573
+ size="sm"
574
+ className="w-[210px] text-xs"
574
575
  aria-label="Filter usage by user"
575
576
  >
576
577
  <SelectValue placeholder="All users" />
@@ -0,0 +1,179 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { act, type ComponentType, type ReactNode } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { MemoryRouter } from "react-router";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+
8
+ import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
9
+
10
+ const state = vi.hoisted(() => ({
11
+ redesign: false,
12
+ registered: [] as Array<{
13
+ id: string;
14
+ component: ComponentType;
15
+ searchEntries?: unknown;
16
+ }>,
17
+ membersAppRoles: undefined as unknown,
18
+ pageProps: null as null | {
19
+ general?: ReactNode;
20
+ generalGroups?: ReactNode;
21
+ generalSearchEntries?: Array<{ id: string; hash?: string }>;
22
+ extraTabs?: Array<{ id: string; href?: string }>;
23
+ whatsNew?: ReactNode;
24
+ },
25
+ }));
26
+
27
+ vi.mock("@agent-native/core/client/agent-chat", () => ({
28
+ CHAT_FIRST_MODE_CHANGED_EVENT: "chat-first-mode-changed",
29
+ readChatFirstModeState: () => ({ enabled: false, availability: "ok" }),
30
+ writeChatFirstMode: () => ({ ok: true }),
31
+ }));
32
+
33
+ vi.mock("@agent-native/core/client/changelog", () => ({
34
+ ChangelogSettingsCard: ({ markdown }: { markdown: string }) => (
35
+ <div data-changelog>{markdown}</div>
36
+ ),
37
+ }));
38
+
39
+ vi.mock("@agent-native/core/client/feature-flags", () => ({
40
+ useFeatureFlag: () => false,
41
+ useFeatureFlagState: () => ({ status: "ready", enabled: state.redesign }),
42
+ }));
43
+
44
+ vi.mock("@agent-native/core/feature-flags/registry", () => ({
45
+ CONNECT_APPS_FLAG: { key: "connect-apps" },
46
+ SETTINGS_REDESIGN_FLAG: { key: "settings-redesign" },
47
+ defineFeatureFlag: (flag: { key: string }) => flag,
48
+ }));
49
+
50
+ vi.mock("@agent-native/core/client/i18n", () => ({
51
+ useT: () => (key: string) => key,
52
+ LanguagePicker: () => null,
53
+ }));
54
+
55
+ vi.mock("@agent-native/core/client/org", () => ({
56
+ TeamPage: () => null,
57
+ OrgMembersPage: ({ appRoles }: { appRoles?: unknown }) => {
58
+ state.membersAppRoles = appRoles;
59
+ return <div data-members-page />;
60
+ },
61
+ }));
62
+
63
+ vi.mock("@agent-native/core/client/settings", () => ({
64
+ AccountSettingsCard: () => null,
65
+ CORE_SETTINGS_PAGES: [
66
+ { id: "profile", component: () => null },
67
+ {
68
+ id: "members",
69
+ component: () => null,
70
+ searchEntries: [{ id: "invite-members" }],
71
+ },
72
+ ],
73
+ registerSettingsPages: (pages: typeof state.registered) => {
74
+ state.registered.push(...pages);
75
+ },
76
+ SettingsGroup: ({ children }: { children: ReactNode }) => (
77
+ <section>{children}</section>
78
+ ),
79
+ SettingsRow: ({
80
+ id,
81
+ label,
82
+ children,
83
+ }: {
84
+ id?: string;
85
+ label: ReactNode;
86
+ children?: ReactNode;
87
+ }) => (
88
+ <div data-row={id}>
89
+ {label}
90
+ {children}
91
+ </div>
92
+ ),
93
+ SettingsTabsPage: (props: NonNullable<typeof state.pageProps>) => {
94
+ state.pageProps = props;
95
+ return <main>{state.redesign ? props.generalGroups : props.general}</main>;
96
+ },
97
+ useAgentSettingsTabs: () => [{ id: "organization", label: "Organization" }],
98
+ }));
99
+
100
+ const { DispatchSettingsPage } = await import("./settings.js");
101
+
102
+ describe("Dispatch settings route", () => {
103
+ let container: HTMLDivElement;
104
+ let root: Root;
105
+
106
+ function render() {
107
+ act(() => {
108
+ root.render(
109
+ <MemoryRouter>
110
+ <DispatchSettingsPage changelog="## 2026-09-25" />
111
+ </MemoryRouter>,
112
+ );
113
+ });
114
+ }
115
+
116
+ beforeEach(() => {
117
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
118
+ state.redesign = false;
119
+ state.pageProps = null;
120
+ state.membersAppRoles = undefined;
121
+ container = document.createElement("div");
122
+ document.body.appendChild(container);
123
+ root = createRoot(container);
124
+ });
125
+
126
+ afterEach(() => {
127
+ act(() => root.unmount());
128
+ container.remove();
129
+ vi.unstubAllGlobals();
130
+ });
131
+
132
+ it("replaces core Members with one that keeps Dispatch app roles", () => {
133
+ const members = state.registered.find((page) => page.id === "members");
134
+ expect(members?.searchEntries).toEqual([{ id: "invite-members" }]);
135
+
136
+ const Members = members!.component;
137
+ act(() => {
138
+ root.render(<Members />);
139
+ });
140
+
141
+ expect(container.querySelector("[data-members-page]")).not.toBeNull();
142
+ expect(state.membersAppRoles).toBe(dispatchAccessDescriptor);
143
+ });
144
+
145
+ it("keeps the language row and Admin link on today's General tab", () => {
146
+ render();
147
+
148
+ expect(container.querySelector('[data-row="language"]')).not.toBeNull();
149
+ expect(container.textContent).toContain(
150
+ "settings.chatFirstSessionWatchDescription",
151
+ );
152
+ expect(
153
+ state.pageProps?.generalSearchEntries?.map((entry) => entry.id),
154
+ ).toContain("dispatch-language");
155
+ expect(
156
+ state.pageProps?.extraTabs?.find((tab) => tab.id === "admin")?.href,
157
+ ).toBe("/admin");
158
+ });
159
+
160
+ it("drops the language row in the redesigned Settings", () => {
161
+ state.redesign = true;
162
+ render();
163
+
164
+ expect(container.querySelector('[data-row="language"]')).toBeNull();
165
+ expect(container.querySelector('[data-row="chat-first"]')).not.toBeNull();
166
+ expect(
167
+ container.querySelector('[data-row="workspace-resources"]')?.textContent,
168
+ ).toContain("settings.resourcesTitle");
169
+ expect(container.textContent).not.toContain(
170
+ "settings.chatFirstSessionWatchDescription",
171
+ );
172
+ expect(
173
+ state.pageProps?.generalSearchEntries?.map((entry) => entry.hash),
174
+ ).toEqual(["workspace-resources", "chat-first"]);
175
+ expect(
176
+ state.pageProps?.extraTabs?.find((tab) => tab.id === "admin")?.href,
177
+ ).toBe("/admin");
178
+ });
179
+ });
@@ -4,36 +4,69 @@ import {
4
4
  writeChatFirstMode,
5
5
  } from "@agent-native/core/client/agent-chat";
6
6
  import { ChangelogSettingsCard } from "@agent-native/core/client/changelog";
7
+ import {
8
+ useFeatureFlag,
9
+ useFeatureFlagState,
10
+ } from "@agent-native/core/client/feature-flags";
7
11
  import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
8
- import { TeamPage } from "@agent-native/core/client/org";
12
+ import { OrgMembersPage, TeamPage } from "@agent-native/core/client/org";
9
13
  import {
14
+ AccountSettingsCard,
15
+ CORE_SETTINGS_PAGES,
16
+ registerSettingsPages,
17
+ SettingsGroup,
18
+ SettingsRow,
10
19
  SettingsTabsPage,
11
20
  useAgentSettingsTabs,
21
+ type SettingsSearchEntry,
12
22
  } from "@agent-native/core/client/settings";
23
+ import { SETTINGS_REDESIGN_FLAG } from "@agent-native/core/feature-flags/registry";
13
24
  import { IconShield } from "@tabler/icons-react";
14
- import { useState } from "react";
25
+ import { useMemo, useState } from "react";
15
26
  import { Link } from "react-router";
16
27
 
17
- import changelog from "../../../CHANGELOG.md?raw";
18
- import { DispatchShell } from "../../components/dispatch-shell";
28
+ import packageChangelog from "../../../CHANGELOG.md?raw";
19
29
  import { Button } from "../../components/ui/button";
20
- import {
21
- Card,
22
- CardContent,
23
- CardDescription,
24
- CardHeader,
25
- CardTitle,
26
- } from "../../components/ui/card";
27
- import { Label } from "../../components/ui/label";
28
30
  import { Switch } from "../../components/ui/switch";
29
31
  import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
32
+ import { DISPATCH_CONNECT_APPS_FLAG } from "../../shared/feature-flags.js";
33
+
34
+ const coreMembersPage = CORE_SETTINGS_PAGES.find(
35
+ (page) => page.id === "members",
36
+ );
37
+ if (!coreMembersPage) {
38
+ throw new Error("Core Settings has no members page for Dispatch app roles");
39
+ }
40
+
41
+ function DispatchMembersSettingsPage() {
42
+ return <OrgMembersPage appRoles={dispatchAccessDescriptor} />;
43
+ }
44
+
45
+ // The redesigned Settings shows Members from core; Dispatch's replaces it so
46
+ // the app-role column stays.
47
+ registerSettingsPages([
48
+ { ...coreMembersPage, component: DispatchMembersSettingsPage },
49
+ ]);
30
50
 
31
51
  export function meta() {
32
52
  return [{ title: "Settings - Dispatch" }];
33
53
  }
34
54
 
35
- export default function SettingsRoute() {
55
+ export interface DispatchSettingsPageProps {
56
+ /** Raw CHANGELOG.md behind What's new. */
57
+ changelog: string;
58
+ }
59
+
60
+ /**
61
+ * Dispatch's Settings. The template route renders it with the app's own
62
+ * changelog; `dispatchRoutes` consumers get the default export.
63
+ */
64
+ export function DispatchSettingsPage({ changelog }: DispatchSettingsPageProps) {
36
65
  const t = useT();
66
+ const connectAppsEnabled = useFeatureFlag(DISPATCH_CONNECT_APPS_FLAG.key);
67
+ // Core Preferences owns the interface language in the redesigned Settings,
68
+ // whose Dispatch General page shows `generalGroups` instead of `general`.
69
+ const redesign = useFeatureFlagState(SETTINGS_REDESIGN_FLAG.key).enabled;
37
70
  const agentSettingsTabs = useAgentSettingsTabs({
38
71
  usageAppId: "dispatch",
39
72
  usageViewAllHref: "/admin/metrics",
@@ -48,7 +81,7 @@ export default function SettingsRoute() {
48
81
  ),
49
82
  });
50
83
  const settingsTabs = [
51
- ...agentSettingsTabs.filter((tab) => tab.id !== "integrations"),
84
+ ...agentSettingsTabs,
52
85
  {
53
86
  id: "admin",
54
87
  label: t("dispatch.nav.admin", { defaultValue: "Admin" }),
@@ -66,16 +99,14 @@ export default function SettingsRoute() {
66
99
  string | null
67
100
  >(
68
101
  chatFirstModeState.availability === "unavailable"
69
- ? "This browser is not allowing local preferences, so Chat-first mode cannot be persisted."
102
+ ? t("settings.chatFirstStorageUnavailable")
70
103
  : null,
71
104
  );
72
105
 
73
106
  function updateChatFirstMode(enabled: boolean) {
74
107
  const result = writeChatFirstMode(enabled);
75
108
  if (!result.ok) {
76
- setChatFirstStorageNotice(
77
- "This browser blocked local preferences, so Chat-first mode was not changed.",
78
- );
109
+ setChatFirstStorageNotice(t("settings.chatFirstStorageBlocked"));
79
110
  return;
80
111
  }
81
112
  setChatFirstStorageNotice(null);
@@ -87,110 +118,139 @@ export default function SettingsRoute() {
87
118
  );
88
119
  }
89
120
 
121
+ const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
122
+ () => [
123
+ ...(redesign
124
+ ? []
125
+ : [
126
+ {
127
+ id: "dispatch-language",
128
+ label: t("settings.languageTitle"),
129
+ keywords: "language locale translation i18n",
130
+ hash: "language",
131
+ },
132
+ ]),
133
+ {
134
+ id: "dispatch-workspace",
135
+ label: redesign
136
+ ? t("settings.resourcesTitle")
137
+ : t("settings.workspaceTitle"),
138
+ keywords: "workspace resources integrations vault destinations",
139
+ hash: "workspace-resources",
140
+ },
141
+ {
142
+ id: "dispatch-chat-first",
143
+ label: t("settings.chatFirstTitle"),
144
+ keywords: "chat first codex t3 apps pane navigation",
145
+ hash: "chat-first",
146
+ },
147
+ ],
148
+ [redesign, t],
149
+ );
150
+
151
+ const chatFirstSwitch = (
152
+ <Switch
153
+ aria-label={t("settings.chatFirstAriaLabel")}
154
+ checked={chatFirstMode}
155
+ onCheckedChange={updateChatFirstMode}
156
+ />
157
+ );
158
+ const chatFirstStorageAlert = chatFirstStorageNotice ? (
159
+ <p className="text-sm text-destructive" role="alert">
160
+ {chatFirstStorageNotice}
161
+ </p>
162
+ ) : null;
163
+ const connectAppsRow = connectAppsEnabled ? (
164
+ <SettingsRow
165
+ id="connect-apps"
166
+ label={t("settings.connectApps")}
167
+ description={t("settings.connectAppsDescription")}
168
+ control={
169
+ <Button variant="outline" asChild>
170
+ <Link to="/connect">{t("settings.openConnectApps")}</Link>
171
+ </Button>
172
+ }
173
+ />
174
+ ) : null;
175
+ const resourceSettingsButton = (
176
+ <Button variant="outline" asChild>
177
+ <Link to="/workspace">{t("settings.openResourceSettings")}</Link>
178
+ </Button>
179
+ );
180
+
90
181
  return (
91
- <DispatchShell
92
- title={t("settings.title")}
93
- description={t("settings.description")}
94
- >
95
- <SettingsTabsPage
96
- extraTabs={settingsTabs}
97
- general={
98
- <div className="mx-auto w-full max-w-3xl space-y-6">
99
- <Card>
100
- <CardHeader>
101
- <CardTitle className="text-base">
102
- {t("settings.languageTitle")}
103
- </CardTitle>
104
- <CardDescription>
105
- {t("settings.languageDescription")}
106
- </CardDescription>
107
- </CardHeader>
108
- <CardContent className="max-w-xs space-y-1.5">
109
- <Label>{t("settings.languageLabel")}</Label>
110
- <LanguagePicker label={t("settings.languageLabel")} />
111
- </CardContent>
112
- </Card>
182
+ <SettingsTabsPage
183
+ account={<AccountSettingsCard />}
184
+ extraTabs={settingsTabs}
185
+ generalSearchEntries={generalSearchEntries}
186
+ general={
187
+ <div className="mx-auto w-full max-w-2xl space-y-6">
188
+ <p className="text-sm leading-6 text-muted-foreground">
189
+ {t("settings.description")}
190
+ </p>
113
191
 
114
- <Card>
115
- <CardHeader>
116
- <CardTitle className="text-base">
117
- Chat-first workspace
118
- </CardTitle>
119
- <CardDescription>
120
- Keep chats at the center and open workspace apps in a
121
- contextual pane beside them.
122
- </CardDescription>
123
- </CardHeader>
124
- <CardContent className="flex items-center justify-between gap-4">
125
- <div className="space-y-1">
126
- <Label htmlFor="dispatch-chat-first-mode">
127
- Use chat-first navigation
128
- </Label>
129
- <p className="text-sm text-muted-foreground">
130
- This preference only changes your local Dispatch shell.
131
- </p>
132
- <p className="text-sm text-muted-foreground">
133
- Session watch and follow-up messaging work in this browser
134
- pane too. Local CLI subscription detection stays in the
135
- Electron app; Dispatch uses workspace/provider credentials.
136
- </p>
192
+ <SettingsGroup>
193
+ <SettingsRow
194
+ id="language"
195
+ label={t("settings.languageTitle")}
196
+ description={t("settings.languageDescription")}
197
+ control={
198
+ <div className="w-56">
199
+ <LanguagePicker label={t("settings.languageLabel")} />
137
200
  </div>
138
- <Switch
139
- id="dispatch-chat-first-mode"
140
- checked={chatFirstMode}
141
- onCheckedChange={updateChatFirstMode}
142
- />
143
- </CardContent>
144
- {chatFirstStorageNotice ? (
145
- <p className="px-6 pb-4 text-sm text-destructive" role="alert">
146
- {chatFirstStorageNotice}
147
- </p>
148
- ) : null}
149
- </Card>
201
+ }
202
+ />
203
+ <SettingsRow
204
+ id="workspace-resources"
205
+ label={t("settings.workspaceTitle")}
206
+ description={t("settings.workspaceDescription")}
207
+ control={resourceSettingsButton}
208
+ />
209
+ {connectAppsRow}
210
+ </SettingsGroup>
150
211
 
151
- <Card>
152
- <CardHeader>
153
- <CardTitle className="text-base">
154
- {t("settings.workspaceTitle")}
155
- </CardTitle>
156
- <CardDescription>
157
- {t("settings.workspaceDescription")}
158
- </CardDescription>
159
- </CardHeader>
160
- <CardContent>
161
- <Button variant="outline" asChild>
162
- <Link to="/admin/workspace">
163
- {t("settings.openResourceSettings")}
164
- </Link>
165
- </Button>
166
- </CardContent>
167
- </Card>
168
-
169
- <Card>
170
- <CardHeader>
171
- <CardTitle className="text-base">
172
- {t("settings.deliveryTitle")}
173
- </CardTitle>
174
- <CardDescription>
175
- {t("settings.deliveryDescription")}
176
- </CardDescription>
177
- </CardHeader>
178
- <CardContent>
179
- <Button variant="outline" asChild>
180
- <Link to="/admin/destinations">
181
- {t("settings.openDelivery")}
182
- </Link>
183
- </Button>
184
- </CardContent>
185
- </Card>
186
- </div>
187
- }
188
- whatsNew={
189
- <div className="mx-auto w-full max-w-3xl">
190
- <ChangelogSettingsCard markdown={changelog} />
191
- </div>
192
- }
193
- />
194
- </DispatchShell>
212
+ <SettingsGroup id="chat-first">
213
+ <SettingsRow
214
+ label={t("settings.chatFirstTitle")}
215
+ description={t("settings.chatFirstDescription")}
216
+ control={chatFirstSwitch}
217
+ >
218
+ <p className="text-sm leading-6 text-muted-foreground">
219
+ {t("settings.chatFirstSessionWatchDescription")}
220
+ </p>
221
+ {chatFirstStorageAlert}
222
+ </SettingsRow>
223
+ </SettingsGroup>
224
+ </div>
225
+ }
226
+ generalGroups={
227
+ <SettingsGroup id="workspace" title={t("settings.workspaceTitle")}>
228
+ <SettingsRow
229
+ id="chat-first"
230
+ label={t("settings.chatFirstTitle")}
231
+ description={t("settings.chatFirstDescription")}
232
+ control={chatFirstSwitch}
233
+ >
234
+ {chatFirstStorageAlert}
235
+ </SettingsRow>
236
+ <SettingsRow
237
+ id="workspace-resources"
238
+ label={t("settings.resourcesTitle")}
239
+ description={t("settings.workspaceDescription")}
240
+ control={resourceSettingsButton}
241
+ />
242
+ {connectAppsRow}
243
+ </SettingsGroup>
244
+ }
245
+ whatsNew={
246
+ <div className="mx-auto w-full max-w-2xl">
247
+ <ChangelogSettingsCard markdown={changelog} />
248
+ </div>
249
+ }
250
+ />
195
251
  );
196
252
  }
253
+
254
+ export default function SettingsRoute() {
255
+ return <DispatchSettingsPage changelog={packageChangelog} />;
256
+ }
@@ -972,7 +972,7 @@ function ThreadDetail({ detail }: { detail: ThreadDebugResponse }) {
972
972
  ) : null}
973
973
 
974
974
  <Tabs defaultValue="overview" className="p-5">
975
- <TabsList className="h-9">
975
+ <TabsList>
976
976
  <TabsTrigger value="overview">Overview</TabsTrigger>
977
977
  <TabsTrigger value="timeline">Timeline</TabsTrigger>
978
978
  <TabsTrigger value="transcript">Transcript</TabsTrigger>
@@ -755,11 +755,12 @@ function RequestRow({
755
755
  <div className="flex-1 space-y-1">
756
756
  <Label className="text-xs">Key value to provision</Label>
757
757
  <Input
758
+ size="sm"
758
759
  type="password"
759
760
  placeholder="Enter the key value"
760
761
  value={secretValue}
761
762
  onChange={(e) => setSecretValue(e.target.value)}
762
- className="h-8 text-sm"
763
+ className="text-sm"
763
764
  />
764
765
  </div>
765
766
  <Button
@@ -1028,7 +1029,7 @@ export default function VaultRoute() {
1028
1029
  initial: { credentialKey: name ?? "" },
1029
1030
  })
1030
1031
  }
1031
- triggerClassName="h-9 px-3 text-sm"
1032
+ size="default"
1032
1033
  />
1033
1034
  <AddSecretDialog
1034
1035
  open={addDialogState.open}
@@ -19,6 +19,7 @@ vi.mock("@agent-native/core/agent/engine", () => ({
19
19
  getAgentEngineEntry: vi.fn(() => null),
20
20
  isAgentEngineSettingConfigured: vi.fn(() => false),
21
21
  isStoredEngineUsable: vi.fn(() => false),
22
+ readDefaultAgentEngineSetting: (...args: any[]) => mocks.getSetting(...args),
22
23
  registerBuiltinEngines: () => mocks.registerBuiltinEngines(),
23
24
  }));
24
25