@nextclaw/ui 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/assets/ChannelsList-DDfZIiJa.js +1 -0
  3. package/dist/assets/ChatPage-FpRraTxm.js +38 -0
  4. package/dist/assets/{DocBrowser-CVwUDJMO.js → DocBrowser-Kndx8OJj.js} +1 -1
  5. package/dist/assets/LogoBadge-hKHoLH9n.js +1 -0
  6. package/dist/assets/MarketplacePage-CZIJyfjK.js +49 -0
  7. package/dist/assets/McpMarketplacePage-BGrAMA37.js +40 -0
  8. package/dist/assets/{ModelConfig-CsX-_fyy.js → ModelConfig-BpKQeGfb.js} +1 -1
  9. package/dist/assets/ProvidersList-qfUL6mrW.js +1 -0
  10. package/dist/assets/RemoteAccessPage-BQuMsngI.js +1 -0
  11. package/dist/assets/{RuntimeConfig-CX2TGEG1.js → RuntimeConfig-CVlqNWKO.js} +1 -1
  12. package/dist/assets/{SearchConfig-C-WBTcWi.js → SearchConfig-DXFV6Mvx.js} +1 -1
  13. package/dist/assets/{SecretsConfig-9kbR0ZCB.js → SecretsConfig-BGW9aUqv.js} +2 -2
  14. package/dist/assets/{SessionsConfig-Bohn3P1q.js → SessionsConfig-BByfa1ke.js} +2 -2
  15. package/dist/assets/{chat-message-AWIcksDK.js → chat-message-ZwnDwDuQ.js} +1 -1
  16. package/dist/assets/index-BWvap_iq.js +8 -0
  17. package/dist/assets/index-COrhpAdh.css +1 -0
  18. package/dist/assets/{index-CPDASUXh.js → index-Ct7FQpxN.js} +1 -1
  19. package/dist/assets/{label-DD61y-4v.js → label-Bklr3fXc.js} +1 -1
  20. package/dist/assets/marketplace-localization-Dk31LJJJ.js +1 -0
  21. package/dist/assets/{page-layout-CfnoVycc.js → page-layout-sNhcbwtm.js} +1 -1
  22. package/dist/assets/{popover-DsugZ6rp.js → popover-C3rJrJJG.js} +1 -1
  23. package/dist/assets/{security-config-DIrf2Z0O.js → security-config-BueosYw1.js} +1 -1
  24. package/dist/assets/skeleton-CiG6msbm.js +1 -0
  25. package/dist/assets/status-dot-CsIV5YrS.js +1 -0
  26. package/dist/assets/{switch-NX5OmUXQ.js → switch-DSdHSIsC.js} +1 -1
  27. package/dist/assets/{tabs-custom-9ihB5Jem.js → tabs-custom-BB-VjdL2.js} +1 -1
  28. package/dist/assets/{useConfirmDialog-BuQnVTeR.js → useConfirmDialog-BL5s8KDC.js} +2 -2
  29. package/dist/assets/{vendor-DKBNiC31.js → vendor-CwsIoNvJ.js} +128 -93
  30. package/dist/index.html +3 -3
  31. package/package.json +3 -3
  32. package/src/App.tsx +4 -0
  33. package/src/api/auth.types.ts +24 -0
  34. package/src/api/chat-session-type.types.ts +21 -0
  35. package/src/api/marketplace.ts +8 -2
  36. package/src/api/mcp-marketplace.ts +138 -0
  37. package/src/api/remote.ts +77 -0
  38. package/src/api/remote.types.ts +104 -0
  39. package/src/api/types.ts +28 -34
  40. package/src/components/chat/ChatSidebar.test.tsx +31 -2
  41. package/src/components/chat/ChatSidebar.tsx +26 -2
  42. package/src/components/chat/chat-page-data.ts +36 -38
  43. package/src/components/chat/chat-page-runtime.test.ts +96 -2
  44. package/src/components/chat/chat-page-runtime.ts +1 -135
  45. package/src/components/chat/chat-session-preference-governance.ts +303 -0
  46. package/src/components/chat/legacy/LegacyChatPage.tsx +4 -19
  47. package/src/components/chat/ncp/NcpChatPage.tsx +4 -19
  48. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +36 -0
  49. package/src/components/chat/ncp/ncp-chat-page-data.ts +62 -21
  50. package/src/components/chat/ncp/ncp-session-adapter.test.ts +2 -0
  51. package/src/components/chat/ncp/ncp-session-adapter.ts +2 -0
  52. package/src/components/chat/stores/chat-input.store.ts +14 -1
  53. package/src/components/chat/useChatSessionTypeState.test.tsx +29 -0
  54. package/src/components/chat/useChatSessionTypeState.ts +55 -12
  55. package/src/components/layout/Sidebar.tsx +11 -1
  56. package/src/components/marketplace/MarketplacePage.test.tsx +152 -0
  57. package/src/components/marketplace/MarketplacePage.tsx +52 -199
  58. package/src/components/marketplace/marketplace-installed-cache.test.ts +110 -0
  59. package/src/components/marketplace/marketplace-installed-cache.ts +149 -0
  60. package/src/components/marketplace/marketplace-localization.ts +77 -0
  61. package/src/components/marketplace/marketplace-page-parts.tsx +102 -0
  62. package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +208 -0
  63. package/src/components/marketplace/mcp/McpMarketplacePage.tsx +578 -0
  64. package/src/components/remote/RemoteAccessPage.tsx +396 -0
  65. package/src/components/ui/input.tsx +1 -1
  66. package/src/components/ui/label.tsx +1 -1
  67. package/src/hooks/useMarketplace.ts +36 -7
  68. package/src/hooks/useMcpMarketplace.ts +99 -0
  69. package/src/hooks/useRemoteAccess.ts +120 -0
  70. package/src/hooks/useWebSocket.ts +25 -16
  71. package/src/lib/i18n.marketplace.ts +91 -0
  72. package/src/lib/i18n.remote.ts +142 -0
  73. package/src/lib/i18n.ts +10 -68
  74. package/dist/assets/ChannelsList-DKD6Llid.js +0 -1
  75. package/dist/assets/ChatPage-BK9X4Tin.js +0 -38
  76. package/dist/assets/LogoBadge-CYQ_b7jk.js +0 -1
  77. package/dist/assets/MarketplacePage-B_2z3ii_.js +0 -49
  78. package/dist/assets/ProvidersList-CZstsyv7.js +0 -1
  79. package/dist/assets/index-BEgClaDH.js +0 -8
  80. package/dist/assets/index-C8GsgIUn.css +0 -1
  81. package/dist/assets/skeleton-DJ-Wen2o.js +0 -1
package/dist/index.html CHANGED
@@ -6,9 +6,9 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>NextClaw - 系统配置</title>
9
- <script type="module" crossorigin src="/assets/index-BEgClaDH.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/vendor-DKBNiC31.js">
11
- <link rel="stylesheet" crossorigin href="/assets/index-C8GsgIUn.css">
9
+ <script type="module" crossorigin src="/assets/index-BWvap_iq.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-CwsIoNvJ.js">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-COrhpAdh.css">
12
12
  </head>
13
13
 
14
14
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,10 +28,10 @@
28
28
  "zod": "^3.23.8",
29
29
  "zustand": "^5.0.2",
30
30
  "@nextclaw/agent-chat-ui": "0.2.1",
31
+ "@nextclaw/ncp-http-agent-client": "0.3.1",
31
32
  "@nextclaw/ncp": "0.3.1",
32
33
  "@nextclaw/ncp-react": "0.3.2",
33
- "@nextclaw/agent-chat": "0.1.1",
34
- "@nextclaw/ncp-http-agent-client": "0.3.1"
34
+ "@nextclaw/agent-chat": "0.1.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@testing-library/react": "^16.3.0",
package/src/App.tsx CHANGED
@@ -25,7 +25,9 @@ const RuntimeConfigPage = lazy(async () => ({ default: (await import('@/componen
25
25
  const SecurityConfigPage = lazy(async () => ({ default: (await import('@/components/config/security-config')).SecurityConfig }));
26
26
  const SessionsConfigPage = lazy(async () => ({ default: (await import('@/components/config/SessionsConfig')).SessionsConfig }));
27
27
  const SecretsConfigPage = lazy(async () => ({ default: (await import('@/components/config/SecretsConfig')).SecretsConfig }));
28
+ const RemoteAccessPage = lazy(async () => ({ default: (await import('@/components/remote/RemoteAccessPage')).RemoteAccessPage }));
28
29
  const MarketplacePage = lazy(async () => ({ default: (await import('@/components/marketplace/MarketplacePage')).MarketplacePage }));
30
+ const McpMarketplacePage = lazy(async () => ({ default: (await import('@/components/marketplace/mcp/McpMarketplacePage')).McpMarketplacePage }));
29
31
 
30
32
  function RouteFallback() {
31
33
  return <div className="h-full w-full animate-pulse rounded-2xl border border-border/40 bg-card/40" />;
@@ -52,12 +54,14 @@ function ProtectedApp() {
52
54
  <Route path="/providers" element={<LazyRoute><ProvidersListPage /></LazyRoute>} />
53
55
  <Route path="/channels" element={<LazyRoute><ChannelsListPage /></LazyRoute>} />
54
56
  <Route path="/runtime" element={<LazyRoute><RuntimeConfigPage /></LazyRoute>} />
57
+ <Route path="/remote" element={<LazyRoute><RemoteAccessPage /></LazyRoute>} />
55
58
  <Route path="/security" element={<LazyRoute><SecurityConfigPage /></LazyRoute>} />
56
59
  <Route path="/sessions" element={<LazyRoute><SessionsConfigPage /></LazyRoute>} />
57
60
  <Route path="/secrets" element={<LazyRoute><SecretsConfigPage /></LazyRoute>} />
58
61
  <Route path="/settings" element={<Navigate to="/model" replace />} />
59
62
  <Route path="/marketplace/skills" element={<Navigate to="/skills" replace />} />
60
63
  <Route path="/marketplace" element={<Navigate to="/marketplace/plugins" replace />} />
64
+ <Route path="/marketplace/mcp" element={<LazyRoute><McpMarketplacePage /></LazyRoute>} />
61
65
  <Route path="/marketplace/:type" element={<LazyRoute><MarketplacePage /></LazyRoute>} />
62
66
  <Route path="/" element={<Navigate to="/chat" replace />} />
63
67
  <Route path="*" element={<Navigate to="/chat" replace />} />
@@ -0,0 +1,24 @@
1
+ export type AuthStatusView = {
2
+ enabled: boolean;
3
+ configured: boolean;
4
+ authenticated: boolean;
5
+ username?: string;
6
+ };
7
+
8
+ export type AuthSetupRequest = {
9
+ username: string;
10
+ password: string;
11
+ };
12
+
13
+ export type AuthLoginRequest = {
14
+ username: string;
15
+ password: string;
16
+ };
17
+
18
+ export type AuthPasswordUpdateRequest = {
19
+ password: string;
20
+ };
21
+
22
+ export type AuthEnabledUpdateRequest = {
23
+ enabled: boolean;
24
+ };
@@ -0,0 +1,21 @@
1
+ export type ChatSessionTypeCtaView = {
2
+ kind: string;
3
+ label?: string;
4
+ href?: string;
5
+ };
6
+
7
+ export type ChatSessionTypeOptionView = {
8
+ value: string;
9
+ label: string;
10
+ ready?: boolean;
11
+ reason?: string | null;
12
+ reasonMessage?: string | null;
13
+ supportedModels?: string[];
14
+ recommendedModel?: string | null;
15
+ cta?: ChatSessionTypeCtaView | null;
16
+ };
17
+
18
+ export type ChatSessionTypesView = {
19
+ defaultType: string;
20
+ options: ChatSessionTypeOptionView[];
21
+ };
@@ -23,8 +23,14 @@ export type MarketplaceListParams = {
23
23
  pageSize?: number;
24
24
  };
25
25
 
26
- function toMarketplaceTypeSegment(type: MarketplaceItemType): 'plugins' | 'skills' {
27
- return type === 'plugin' ? 'plugins' : 'skills';
26
+ function toMarketplaceTypeSegment(type: MarketplaceItemType): 'plugins' | 'skills' | 'mcp' {
27
+ if (type === 'plugin') {
28
+ return 'plugins';
29
+ }
30
+ if (type === 'skill') {
31
+ return 'skills';
32
+ }
33
+ return 'mcp';
28
34
  }
29
35
 
30
36
  export async function fetchMarketplaceItems(params: MarketplaceListParams): Promise<MarketplaceListView> {
@@ -0,0 +1,138 @@
1
+ import { api } from './client';
2
+ import type {
3
+ MarketplaceInstalledView,
4
+ MarketplaceItemView,
5
+ MarketplaceListView,
6
+ MarketplaceMcpContentView,
7
+ MarketplaceMcpDoctorResult,
8
+ MarketplaceRecommendationView,
9
+ MarketplaceSort
10
+ } from './types';
11
+
12
+ export type McpMarketplaceListParams = {
13
+ q?: string;
14
+ tag?: string;
15
+ sort?: MarketplaceSort;
16
+ page?: number;
17
+ pageSize?: number;
18
+ };
19
+
20
+ export async function fetchMcpMarketplaceItems(params: McpMarketplaceListParams): Promise<MarketplaceListView> {
21
+ const query = new URLSearchParams();
22
+ if (params.q?.trim()) {
23
+ query.set('q', params.q.trim());
24
+ }
25
+ if (params.tag?.trim()) {
26
+ query.set('tag', params.tag.trim());
27
+ }
28
+ if (params.sort) {
29
+ query.set('sort', params.sort);
30
+ }
31
+ if (typeof params.page === 'number' && Number.isFinite(params.page)) {
32
+ query.set('page', String(Math.max(1, Math.trunc(params.page))));
33
+ }
34
+ if (typeof params.pageSize === 'number' && Number.isFinite(params.pageSize)) {
35
+ query.set('pageSize', String(Math.max(1, Math.trunc(params.pageSize))));
36
+ }
37
+
38
+ const suffix = query.toString();
39
+ const response = await api.get<MarketplaceListView>(
40
+ suffix ? `/api/marketplace/mcp/items?${suffix}` : '/api/marketplace/mcp/items'
41
+ );
42
+ if (!response.ok) {
43
+ throw new Error(response.error.message);
44
+ }
45
+ return response.data;
46
+ }
47
+
48
+ export async function fetchMcpMarketplaceInstalled(): Promise<MarketplaceInstalledView> {
49
+ const response = await api.get<MarketplaceInstalledView>('/api/marketplace/mcp/installed');
50
+ if (!response.ok) {
51
+ throw new Error(response.error.message);
52
+ }
53
+ return response.data;
54
+ }
55
+
56
+ export async function fetchMcpMarketplaceItem(slug: string): Promise<MarketplaceItemView> {
57
+ const response = await api.get<MarketplaceItemView>(`/api/marketplace/mcp/items/${encodeURIComponent(slug)}`);
58
+ if (!response.ok) {
59
+ throw new Error(response.error.message);
60
+ }
61
+ return response.data;
62
+ }
63
+
64
+ export async function fetchMcpMarketplaceContent(slug: string): Promise<MarketplaceMcpContentView> {
65
+ const response = await api.get<MarketplaceMcpContentView>(`/api/marketplace/mcp/items/${encodeURIComponent(slug)}/content`);
66
+ if (!response.ok) {
67
+ throw new Error(response.error.message);
68
+ }
69
+ return response.data;
70
+ }
71
+
72
+ export async function fetchMcpMarketplaceRecommendations(params: {
73
+ scene?: string;
74
+ limit?: number;
75
+ } = {}): Promise<MarketplaceRecommendationView> {
76
+ const query = new URLSearchParams();
77
+ if (params.scene?.trim()) {
78
+ query.set('scene', params.scene.trim());
79
+ }
80
+ if (typeof params.limit === 'number' && Number.isFinite(params.limit)) {
81
+ query.set('limit', String(Math.max(1, Math.trunc(params.limit))));
82
+ }
83
+ const suffix = query.toString();
84
+ const response = await api.get<MarketplaceRecommendationView>(
85
+ suffix ? `/api/marketplace/mcp/recommendations?${suffix}` : '/api/marketplace/mcp/recommendations'
86
+ );
87
+ if (!response.ok) {
88
+ throw new Error(response.error.message);
89
+ }
90
+ return response.data;
91
+ }
92
+
93
+ export async function installMcpMarketplaceItem(request: {
94
+ spec: string;
95
+ name?: string;
96
+ enabled?: boolean;
97
+ allAgents?: boolean;
98
+ agents?: string[];
99
+ inputs?: Record<string, string>;
100
+ }): Promise<{ type: 'mcp'; spec: string; name?: string; message: string; output?: string }> {
101
+ const response = await api.post<{ type: 'mcp'; spec: string; name?: string; message: string; output?: string }>(
102
+ '/api/marketplace/mcp/install',
103
+ {
104
+ type: 'mcp',
105
+ ...request
106
+ }
107
+ );
108
+ if (!response.ok) {
109
+ throw new Error(response.error.message);
110
+ }
111
+ return response.data;
112
+ }
113
+
114
+ export async function manageMcpMarketplaceItem(request: {
115
+ action: 'enable' | 'disable' | 'remove';
116
+ id?: string;
117
+ spec?: string;
118
+ }): Promise<{ type: 'mcp'; action: 'enable' | 'disable' | 'remove'; id: string; message: string; output?: string }> {
119
+ const response = await api.post<{ type: 'mcp'; action: 'enable' | 'disable' | 'remove'; id: string; message: string; output?: string }>(
120
+ '/api/marketplace/mcp/manage',
121
+ {
122
+ type: 'mcp',
123
+ ...request
124
+ }
125
+ );
126
+ if (!response.ok) {
127
+ throw new Error(response.error.message);
128
+ }
129
+ return response.data;
130
+ }
131
+
132
+ export async function doctorMcpMarketplaceItem(name: string): Promise<MarketplaceMcpDoctorResult> {
133
+ const response = await api.post<MarketplaceMcpDoctorResult>('/api/marketplace/mcp/doctor', { name });
134
+ if (!response.ok) {
135
+ throw new Error(response.error.message);
136
+ }
137
+ return response.data;
138
+ }
@@ -0,0 +1,77 @@
1
+ import { api } from './client';
2
+ import type {
3
+ RemoteAccessView,
4
+ RemoteBrowserAuthPollRequest,
5
+ RemoteBrowserAuthPollResult,
6
+ RemoteBrowserAuthStartRequest,
7
+ RemoteBrowserAuthStartResult,
8
+ RemoteDoctorView,
9
+ RemoteLoginRequest,
10
+ RemoteServiceAction,
11
+ RemoteServiceActionResult,
12
+ RemoteSettingsUpdateRequest
13
+ } from './remote.types';
14
+
15
+ export async function fetchRemoteStatus(): Promise<RemoteAccessView> {
16
+ const response = await api.get<RemoteAccessView>('/api/remote/status');
17
+ if (!response.ok) {
18
+ throw new Error(response.error.message);
19
+ }
20
+ return response.data;
21
+ }
22
+
23
+ export async function fetchRemoteDoctor(): Promise<RemoteDoctorView> {
24
+ const response = await api.get<RemoteDoctorView>('/api/remote/doctor');
25
+ if (!response.ok) {
26
+ throw new Error(response.error.message);
27
+ }
28
+ return response.data;
29
+ }
30
+
31
+ export async function loginRemote(data: RemoteLoginRequest): Promise<RemoteAccessView> {
32
+ const response = await api.post<RemoteAccessView>('/api/remote/login', data);
33
+ if (!response.ok) {
34
+ throw new Error(response.error.message);
35
+ }
36
+ return response.data;
37
+ }
38
+
39
+ export async function startRemoteBrowserAuth(data: RemoteBrowserAuthStartRequest): Promise<RemoteBrowserAuthStartResult> {
40
+ const response = await api.post<RemoteBrowserAuthStartResult>('/api/remote/auth/start', data);
41
+ if (!response.ok) {
42
+ throw new Error(response.error.message);
43
+ }
44
+ return response.data;
45
+ }
46
+
47
+ export async function pollRemoteBrowserAuth(data: RemoteBrowserAuthPollRequest): Promise<RemoteBrowserAuthPollResult> {
48
+ const response = await api.post<RemoteBrowserAuthPollResult>('/api/remote/auth/poll', data);
49
+ if (!response.ok) {
50
+ throw new Error(response.error.message);
51
+ }
52
+ return response.data;
53
+ }
54
+
55
+ export async function logoutRemote(): Promise<RemoteAccessView> {
56
+ const response = await api.post<RemoteAccessView>('/api/remote/logout', {});
57
+ if (!response.ok) {
58
+ throw new Error(response.error.message);
59
+ }
60
+ return response.data;
61
+ }
62
+
63
+ export async function updateRemoteSettings(data: RemoteSettingsUpdateRequest): Promise<RemoteAccessView> {
64
+ const response = await api.put<RemoteAccessView>('/api/remote/settings', data);
65
+ if (!response.ok) {
66
+ throw new Error(response.error.message);
67
+ }
68
+ return response.data;
69
+ }
70
+
71
+ export async function controlRemoteService(action: RemoteServiceAction): Promise<RemoteServiceActionResult> {
72
+ const response = await api.post<RemoteServiceActionResult>(`/api/remote/service/${action}`, {});
73
+ if (!response.ok) {
74
+ throw new Error(response.error.message);
75
+ }
76
+ return response.data;
77
+ }
@@ -0,0 +1,104 @@
1
+ export type RemoteAccountView = {
2
+ loggedIn: boolean;
3
+ email?: string;
4
+ role?: string;
5
+ platformBase?: string | null;
6
+ apiBase?: string | null;
7
+ };
8
+
9
+ export type RemoteRuntimeView = {
10
+ enabled: boolean;
11
+ mode: "service" | "foreground";
12
+ state: "disabled" | "connecting" | "connected" | "disconnected" | "error";
13
+ deviceId?: string;
14
+ deviceName?: string;
15
+ platformBase?: string;
16
+ localOrigin?: string;
17
+ lastConnectedAt?: string | null;
18
+ lastError?: string | null;
19
+ updatedAt: string;
20
+ };
21
+
22
+ export type RemoteServiceView = {
23
+ running: boolean;
24
+ pid?: number;
25
+ uiUrl?: string;
26
+ uiPort?: number;
27
+ currentProcess: boolean;
28
+ };
29
+
30
+ export type RemoteSettingsView = {
31
+ enabled: boolean;
32
+ deviceName: string;
33
+ platformApiBase: string;
34
+ };
35
+
36
+ export type RemoteAccessView = {
37
+ account: RemoteAccountView;
38
+ settings: RemoteSettingsView;
39
+ service: RemoteServiceView;
40
+ localOrigin: string;
41
+ configuredEnabled: boolean;
42
+ platformBase?: string | null;
43
+ runtime: RemoteRuntimeView | null;
44
+ };
45
+
46
+ export type RemoteDoctorCheckView = {
47
+ name: string;
48
+ ok: boolean;
49
+ detail: string;
50
+ };
51
+
52
+ export type RemoteDoctorView = {
53
+ generatedAt: string;
54
+ checks: RemoteDoctorCheckView[];
55
+ snapshot: {
56
+ configuredEnabled: boolean;
57
+ runtime: RemoteRuntimeView | null;
58
+ };
59
+ };
60
+
61
+ export type RemoteLoginRequest = {
62
+ email: string;
63
+ password: string;
64
+ apiBase?: string;
65
+ register?: boolean;
66
+ };
67
+
68
+ export type RemoteBrowserAuthStartRequest = {
69
+ apiBase?: string;
70
+ };
71
+
72
+ export type RemoteBrowserAuthStartResult = {
73
+ sessionId: string;
74
+ verificationUri: string;
75
+ expiresAt: string;
76
+ intervalMs: number;
77
+ };
78
+
79
+ export type RemoteBrowserAuthPollRequest = {
80
+ sessionId: string;
81
+ apiBase?: string;
82
+ };
83
+
84
+ export type RemoteBrowserAuthPollResult = {
85
+ status: "pending" | "authorized" | "expired";
86
+ message?: string;
87
+ nextPollMs?: number;
88
+ email?: string;
89
+ role?: string;
90
+ };
91
+
92
+ export type RemoteSettingsUpdateRequest = {
93
+ enabled?: boolean;
94
+ deviceName?: string;
95
+ platformApiBase?: string;
96
+ };
97
+
98
+ export type RemoteServiceAction = "start" | "restart" | "stop";
99
+
100
+ export type RemoteServiceActionResult = {
101
+ accepted: boolean;
102
+ action: RemoteServiceAction;
103
+ message: string;
104
+ };
package/src/api/types.ts CHANGED
@@ -160,30 +160,27 @@ export type ProviderAuthImportResult = {
160
160
  expiresAt?: string;
161
161
  };
162
162
 
163
- export type AuthStatusView = {
164
- enabled: boolean;
165
- configured: boolean;
166
- authenticated: boolean;
167
- username?: string;
168
- };
169
-
170
- export type AuthSetupRequest = {
171
- username: string;
172
- password: string;
173
- };
174
-
175
- export type AuthLoginRequest = {
176
- username: string;
177
- password: string;
178
- };
179
-
180
- export type AuthPasswordUpdateRequest = {
181
- password: string;
182
- };
183
-
184
- export type AuthEnabledUpdateRequest = {
185
- enabled: boolean;
186
- };
163
+ export type {
164
+ AuthEnabledUpdateRequest,
165
+ AuthLoginRequest,
166
+ AuthPasswordUpdateRequest,
167
+ AuthSetupRequest,
168
+ AuthStatusView
169
+ } from './auth.types';
170
+
171
+ export type {
172
+ RemoteAccessView,
173
+ RemoteAccountView,
174
+ RemoteDoctorCheckView,
175
+ RemoteDoctorView,
176
+ RemoteLoginRequest,
177
+ RemoteRuntimeView,
178
+ RemoteServiceAction,
179
+ RemoteServiceActionResult,
180
+ RemoteServiceView,
181
+ RemoteSettingsUpdateRequest,
182
+ RemoteSettingsView
183
+ } from './remote.types';
187
184
 
188
185
  export type AgentProfileView = {
189
186
  id: string;
@@ -223,6 +220,7 @@ export type SessionEntryView = {
223
220
  updatedAt: string;
224
221
  label?: string;
225
222
  preferredModel?: string;
223
+ preferredThinking?: ThinkingLevel | null;
226
224
  sessionType: string;
227
225
  sessionTypeMutable: boolean;
228
226
  messageCount: number;
@@ -316,6 +314,12 @@ export type ChatTurnStreamReadyEvent = {
316
314
  stopReason?: string;
317
315
  };
318
316
 
317
+ export type {
318
+ ChatSessionTypeCtaView,
319
+ ChatSessionTypeOptionView,
320
+ ChatSessionTypesView,
321
+ } from './chat-session-type.types';
322
+
319
323
  export type ChatTurnStreamDeltaEvent = {
320
324
  delta: string;
321
325
  };
@@ -334,16 +338,6 @@ export type ChatCapabilitiesView = {
334
338
  stopReason?: string;
335
339
  };
336
340
 
337
- export type ChatSessionTypeOptionView = {
338
- value: string;
339
- label: string;
340
- };
341
-
342
- export type ChatSessionTypesView = {
343
- defaultType: string;
344
- options: ChatSessionTypeOptionView[];
345
- };
346
-
347
341
  export type ChatCommandOptionView = {
348
342
  name: string;
349
343
  description: string;
@@ -68,8 +68,8 @@ describe('ChatSidebar', () => {
68
68
  ...useChatInputStore.getState().snapshot,
69
69
  defaultSessionType: 'native',
70
70
  sessionTypeOptions: [
71
- { value: 'native', label: 'Native' },
72
- { value: 'codex', label: 'Codex' }
71
+ { value: 'native', label: 'Native', ready: true },
72
+ { value: 'codex', label: 'Codex', ready: true }
73
73
  ]
74
74
  }
75
75
  });
@@ -105,6 +105,35 @@ describe('ChatSidebar', () => {
105
105
  });
106
106
  });
107
107
 
108
+ it('shows setup required status for runtime session types that are not ready yet', () => {
109
+ useChatInputStore.setState({
110
+ snapshot: {
111
+ ...useChatInputStore.getState().snapshot,
112
+ sessionTypeOptions: [
113
+ { value: 'native', label: 'Native', ready: true },
114
+ {
115
+ value: 'claude',
116
+ label: 'Claude',
117
+ ready: false,
118
+ reasonMessage: 'Configure a provider API key first.'
119
+ }
120
+ ]
121
+ }
122
+ });
123
+
124
+ render(
125
+ <MemoryRouter>
126
+ <ChatSidebar />
127
+ </MemoryRouter>
128
+ );
129
+
130
+ fireEvent.click(screen.getByLabelText('Session Type'));
131
+
132
+ expect(screen.getByText('Claude')).not.toBeNull();
133
+ expect(screen.getByText('Setup')).not.toBeNull();
134
+ expect(screen.getByText('Configure a provider API key first.')).not.toBeNull();
135
+ });
136
+
108
137
  it('shows a session type badge for non-native sessions in the list', () => {
109
138
  useChatSessionListStore.setState({
110
139
  snapshot: {
@@ -85,6 +85,16 @@ function resolveSessionTypeLabel(
85
85
  .join(' ');
86
86
  }
87
87
 
88
+ function resolveSessionTypeStatusText(option: {
89
+ ready?: boolean;
90
+ reasonMessage?: string | null;
91
+ }): string {
92
+ if (option.ready === false) {
93
+ return option.reasonMessage?.trim() || t('statusSetup');
94
+ }
95
+ return t('statusReady');
96
+ }
97
+
88
98
  const navItems = [
89
99
  { target: '/cron', label: () => t('chatSidebarScheduledTasks'), icon: AlarmClock },
90
100
  { target: '/skills', label: () => t('chatSidebarSkills'), icon: BrainCircuit },
@@ -168,8 +178,22 @@ export function ChatSidebar() {
168
178
  }}
169
179
  className="w-full rounded-xl px-3 py-2 text-left transition-colors hover:bg-gray-100"
170
180
  >
171
- <div className="text-[13px] font-medium text-gray-900">{option.label}</div>
172
- <div className="mt-0.5 text-[11px] text-gray-500">{t('chatSidebarNewTask')}</div>
181
+ <div className="flex items-center justify-between gap-3">
182
+ <div className="text-[13px] font-medium text-gray-900">{option.label}</div>
183
+ <span
184
+ className={cn(
185
+ 'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold',
186
+ option.ready === false
187
+ ? 'bg-amber-100 text-amber-800'
188
+ : 'bg-emerald-100 text-emerald-700'
189
+ )}
190
+ >
191
+ {option.ready === false ? t('statusSetup') : t('statusReady')}
192
+ </span>
193
+ </div>
194
+ <div className="mt-0.5 text-[11px] text-gray-500">
195
+ {resolveSessionTypeStatusText(option)}
196
+ </div>
173
197
  </button>
174
198
  ))}
175
199
  </div>