@nextclaw/ui 0.12.8 → 0.12.10

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 (227) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
  3. package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
  4. package/dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-DMfr0Oow.js} +1 -1
  5. package/dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-BXydqby-.js} +1 -1
  6. package/dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-hO7tY7hE.js} +1 -1
  7. package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
  8. package/dist/assets/{ProviderScopedModelInput-CmTIzgI7.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
  9. package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
  10. package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
  11. package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
  12. package/dist/assets/{SecretsConfig-Ba1RPJaG.js → SecretsConfig-MEt6MjuD.js} +2 -2
  13. package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
  14. package/dist/assets/{app-query-client-DniXoIN5.js → app-query-client-9jNewezV.js} +1 -1
  15. package/dist/assets/{book-open-DocgeQtR.js → book-open-DzdUViDm.js} +1 -1
  16. package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
  17. package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
  18. package/dist/assets/{chunk-JZWAC4HX-BvKvh1R8.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
  19. package/dist/assets/{client-CVqPF5ie.js → client-C-8fH7-c.js} +1 -1
  20. package/dist/assets/{config-Bop2oB18.js → config-CBScxsdV.js} +1 -1
  21. package/dist/assets/config-split-page-BUout_Ak.js +1 -0
  22. package/dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-dy5ie7Ox.js} +1 -1
  23. package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
  24. package/dist/assets/{dist-DmAlInRu.js → dist-BruyLa92.js} +1 -1
  25. package/dist/assets/{dist-Da5Gm_pO.js → dist-Cy7_j6hA.js} +1 -1
  26. package/dist/assets/download-BD0ETkB-.js +1 -0
  27. package/dist/assets/{external-link-DFjw3x1B.js → external-link-kZSAO8nT.js} +1 -1
  28. package/dist/assets/{hash-DJtaCejM.js → hash-BHJC2Ovu.js} +1 -1
  29. package/dist/assets/i18n-CpTZLchQ.js +1 -0
  30. package/dist/assets/index-mW8W2FUu.css +1 -0
  31. package/dist/assets/index-zDZfXoI4.js +6 -0
  32. package/dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
  33. package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
  34. package/dist/assets/{logos-DEFUIR12.js → logos-B7gRObP8.js} +1 -1
  35. package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
  36. package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
  37. package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
  38. package/dist/assets/{page-layout-Da3i3r6G.js → page-layout-0UcO9H9Z.js} +1 -1
  39. package/dist/assets/play-CKDjSQFL.js +1 -0
  40. package/dist/assets/plus-CG0QrVY_.js +1 -0
  41. package/dist/assets/{refresh-ccw-D6HkNtfz.js → refresh-ccw-COVhNHtN.js} +1 -1
  42. package/dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-Bcv40SXy.js} +1 -1
  43. package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
  44. package/dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-oHMKJMC8.js} +1 -1
  45. package/dist/assets/{save-DHGmi2e9.js → save-EqJPOF0G.js} +1 -1
  46. package/dist/assets/search-BCAlB8nz.js +1 -0
  47. package/dist/assets/security-config-Slh0Mayz.js +1 -0
  48. package/dist/assets/select-CVz0t7MF.js +41 -0
  49. package/dist/assets/setting-row-CbVHAuQt.js +1 -0
  50. package/dist/assets/skeleton-D5rdKvzy.js +1 -0
  51. package/dist/assets/{status-dot-DurKKSwA.js → status-dot-DpPtVzQT.js} +1 -1
  52. package/dist/assets/{switch-0rmPBRKI.js → switch-CM29eCAR.js} +1 -1
  53. package/dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-YcZUWn3o.js} +1 -1
  54. package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
  55. package/dist/assets/{trash-2-C6caKPoz.js → trash-2-mJT6oWa2.js} +1 -1
  56. package/dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
  57. package/dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BsVuqu1x.js} +1 -1
  58. package/dist/assets/{useMutation-BmxxvCNf.js → useMutation-CNcz2fgt.js} +1 -1
  59. package/dist/assets/x-Czwxm82I.js +1 -0
  60. package/dist/index.html +95 -21
  61. package/dist/manifest.webmanifest +30 -0
  62. package/dist/offline.html +102 -0
  63. package/dist/pwa-192.png +0 -0
  64. package/dist/pwa-512.png +0 -0
  65. package/dist/runtime-icons/claude.ico +0 -0
  66. package/dist/runtime-icons/codex-openai.svg +6 -0
  67. package/dist/runtime-icons/hermes-agent.png +0 -0
  68. package/dist/sw.js +80 -0
  69. package/index.html +73 -1
  70. package/package.json +5 -5
  71. package/public/manifest.webmanifest +30 -0
  72. package/public/offline.html +102 -0
  73. package/public/pwa-192.png +0 -0
  74. package/public/pwa-512.png +0 -0
  75. package/public/runtime-icons/claude.ico +0 -0
  76. package/public/runtime-icons/codex-openai.svg +6 -0
  77. package/public/runtime-icons/hermes-agent.png +0 -0
  78. package/public/sw.js +80 -0
  79. package/src/account/components/account-panel.tsx +217 -97
  80. package/src/account/managers/account.manager.ts +3 -2
  81. package/src/api/chat-session-type.types.ts +7 -0
  82. package/src/api/runtime-control.types.ts +8 -0
  83. package/src/api/server-path.ts +27 -4
  84. package/src/api/types.ts +25 -10
  85. package/src/app.tsx +227 -54
  86. package/src/components/agents/agent-dialogs.tsx +499 -0
  87. package/src/components/agents/agents-page.test.tsx +238 -0
  88. package/src/components/agents/agents-page.tsx +435 -0
  89. package/src/components/chat/ChatSidebar.test.tsx +43 -1
  90. package/src/components/chat/ChatSidebar.tsx +35 -35
  91. package/src/components/chat/adapters/chat-message.summary-truncation.test.ts +66 -0
  92. package/src/components/chat/adapters/file-operation/card.ts +9 -0
  93. package/src/components/chat/adapters/file-operation/diff.ts +14 -0
  94. package/src/components/chat/{ChatConversationPanel.test.tsx → chat-conversation-panel.test.tsx} +127 -206
  95. package/src/components/chat/chat-conversation-panel.tsx +482 -0
  96. package/src/components/chat/chat-page-shell.tsx +19 -13
  97. package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
  98. package/src/components/chat/chat-session-type-option-item.tsx +68 -0
  99. package/src/components/chat/chat-session-workspace-file-preview.test.tsx +178 -0
  100. package/src/components/chat/chat-session-workspace-file-preview.tsx +278 -0
  101. package/src/components/chat/chat-session-workspace-panel-nav.tsx +203 -0
  102. package/src/components/chat/chat-session-workspace-panel.tsx +318 -0
  103. package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
  104. package/src/components/chat/chat-sidebar-session-item.tsx +32 -2
  105. package/src/components/chat/containers/chat-message-list.container.test.tsx +49 -0
  106. package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
  107. package/src/components/chat/managers/chat-session-list.manager.test.ts +12 -0
  108. package/src/components/chat/managers/chat-session-list.manager.ts +7 -0
  109. package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
  110. package/src/components/chat/ncp/ncp-chat-page.tsx +9 -7
  111. package/src/components/chat/ncp/ncp-chat-thread.manager.ts +179 -41
  112. package/src/components/chat/ncp/ncp-session-adapter.test.ts +36 -1
  113. package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
  114. package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +62 -13
  115. package/src/components/chat/ncp/tests/ncp-chat-thread.manager.test.ts +189 -0
  116. package/src/components/chat/presenter/chat-presenter-context.tsx +13 -2
  117. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +26 -0
  118. package/src/components/chat/session-header/chat-session-header-actions.tsx +19 -1
  119. package/src/components/chat/stores/chat-input.store.ts +2 -1
  120. package/src/components/chat/stores/chat-thread.store.ts +27 -1
  121. package/src/components/chat/useChatSessionTypeState.ts +10 -1
  122. package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
  123. package/src/components/common/BrandHeader.tsx +3 -1
  124. package/src/components/common/session-context-icon.tsx +15 -2
  125. package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
  126. package/src/components/config/ChannelForm.test.tsx +89 -3
  127. package/src/components/config/ChannelForm.tsx +157 -188
  128. package/src/components/config/ChannelsList.test.tsx +163 -119
  129. package/src/components/config/ChannelsList.tsx +90 -101
  130. package/src/components/config/ProviderForm.tsx +108 -146
  131. package/src/components/config/ProvidersList.tsx +100 -123
  132. package/src/components/config/RuntimeConfig.tsx +141 -2
  133. package/src/components/config/SearchConfig.tsx +423 -393
  134. package/src/components/config/channel-form-fields-section.tsx +70 -37
  135. package/src/components/config/config-split-page.tsx +109 -0
  136. package/src/components/config/provider-enabled-field.tsx +17 -10
  137. package/src/components/config/runtime-control-card.test.tsx +56 -0
  138. package/src/components/config/runtime-control-card.tsx +25 -0
  139. package/src/components/config/runtime-presence-card.tsx +93 -79
  140. package/src/components/layout/AppLayout.tsx +25 -37
  141. package/src/components/layout/app-layout.test.tsx +46 -14
  142. package/src/components/layout/runtime-status-entry.test.tsx +157 -0
  143. package/src/components/layout/runtime-status-entry.tsx +143 -0
  144. package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
  145. package/src/components/marketplace/marketplace-list-card.tsx +288 -0
  146. package/src/components/marketplace/marketplace-page-data.ts +129 -0
  147. package/src/components/marketplace/marketplace-page.test.tsx +339 -0
  148. package/src/components/marketplace/marketplace-page.tsx +596 -0
  149. package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
  150. package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
  151. package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
  152. package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
  153. package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
  154. package/src/components/providers/ThemeProvider.tsx +5 -0
  155. package/src/components/remote/remote-access-page.test.tsx +105 -0
  156. package/src/components/remote/remote-access-page.tsx +248 -0
  157. package/src/components/ui/notice-card.tsx +129 -0
  158. package/src/components/ui/setting-row.tsx +51 -0
  159. package/src/components/ui/tag-chip.tsx +39 -0
  160. package/src/components/ui/textarea.tsx +19 -0
  161. package/src/hooks/server-path/use-server-path-read.ts +20 -0
  162. package/src/hooks/useConfig.ts +2 -1
  163. package/src/index.css +24 -0
  164. package/src/lib/app-resource-uri.test.ts +20 -0
  165. package/src/lib/app-resource-uri.ts +29 -0
  166. package/src/lib/chat-message.ts +14 -3
  167. package/src/lib/i18n.chat.ts +12 -1
  168. package/src/lib/i18n.pwa.ts +62 -0
  169. package/src/lib/i18n.remote.ts +1 -1
  170. package/src/lib/i18n.runtime-control.ts +31 -0
  171. package/src/lib/i18n.ts +7 -10
  172. package/src/lib/session-context.utils.test.ts +71 -0
  173. package/src/lib/session-context.utils.ts +28 -3
  174. package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
  175. package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
  176. package/src/pwa/components/pwa-install-entry.test.tsx +110 -0
  177. package/src/pwa/components/pwa-install-entry.tsx +205 -0
  178. package/src/pwa/managers/pwa-install.manager.test.ts +160 -0
  179. package/src/pwa/managers/pwa-install.manager.ts +232 -0
  180. package/src/pwa/managers/pwa-runtime.manager.ts +196 -0
  181. package/src/pwa/managers/pwa-shell-theme.manager.test.ts +30 -0
  182. package/src/pwa/managers/pwa-shell-theme.manager.ts +46 -0
  183. package/src/pwa/pwa-install-banner.storage.ts +55 -0
  184. package/src/pwa/pwa.types.ts +22 -0
  185. package/src/pwa/register-pwa.ts +14 -0
  186. package/src/pwa/stores/pwa.store.ts +17 -0
  187. package/src/vite-env.d.ts +9 -0
  188. package/dist/assets/ChannelsList-KIQIxluX.js +0 -8
  189. package/dist/assets/DocBrowser-CyDgAtO9.js +0 -1
  190. package/dist/assets/MarketplacePage-BySqkYDh.js +0 -49
  191. package/dist/assets/MarketplacePage-C0olZaek.js +0 -1
  192. package/dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
  193. package/dist/assets/ModelConfig-IrmzoslW.js +0 -1
  194. package/dist/assets/ProvidersList-8_Kalfwl.js +0 -1
  195. package/dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
  196. package/dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
  197. package/dist/assets/SearchConfig-DNBR-UbE.js +0 -1
  198. package/dist/assets/SessionsConfig-Doqp5ghH.js +0 -2
  199. package/dist/assets/chat-page-Bph8M5zo.js +0 -58
  200. package/dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
  201. package/dist/assets/config-layout-DmlGaay2.js +0 -1
  202. package/dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
  203. package/dist/assets/i18n-CwHZ-9vt.js +0 -1
  204. package/dist/assets/index-DafCdM4F.css +0 -1
  205. package/dist/assets/index-DdksE6U3.js +0 -6
  206. package/dist/assets/loader-circle-PsSP0H9n.js +0 -1
  207. package/dist/assets/play-DBQbBxTA.js +0 -1
  208. package/dist/assets/plus-DUOVbsyQ.js +0 -1
  209. package/dist/assets/popover-C_mWOFzI.js +0 -1
  210. package/dist/assets/search-MChQRYR1.js +0 -1
  211. package/dist/assets/security-config-CbXfPZzr.js +0 -1
  212. package/dist/assets/select-Caud8QvU.js +0 -41
  213. package/dist/assets/skeleton-B-4vRq_Z.js +0 -1
  214. package/dist/assets/x-DuMhMATD.js +0 -1
  215. package/src/components/agents/AgentDialogs.tsx +0 -400
  216. package/src/components/agents/AgentsPage.test.tsx +0 -217
  217. package/src/components/agents/AgentsPage.tsx +0 -352
  218. package/src/components/chat/ChatConversationPanel.tsx +0 -256
  219. package/src/components/chat/chat-child-session-panel.tsx +0 -270
  220. package/src/components/config/config-layout.ts +0 -10
  221. package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
  222. package/src/components/marketplace/MarketplacePage.tsx +0 -827
  223. package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
  224. package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
  225. package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
  226. package/src/components/remote/RemoteAccessPage.tsx +0 -144
  227. /package/dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
@@ -0,0 +1,248 @@
1
+ import { PageHeader, PageLayout } from "@/components/layout/page-layout";
2
+ import { Button } from "@/components/ui/button";
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from "@/components/ui/card";
10
+ import { NoticeCard } from "@/components/ui/notice-card";
11
+ import { StatusDot } from "@/components/ui/status-dot";
12
+ import { useRemoteStatus } from "@/hooks/useRemoteAccess";
13
+ import { formatDateTime, t } from "@/lib/i18n";
14
+ import { useAppPresenter } from "@/presenter/app-presenter-context";
15
+ import { resolveRemoteWebBase } from "@/remote/remote-access.query";
16
+ import { buildRemoteAccessFeedbackView } from "@/remote/remote-access-feedback.service";
17
+ import { useRemoteAccessStore } from "@/remote/stores/remote-access.store";
18
+ import { Laptop, RefreshCcw, SquareArrowOutUpRight } from "lucide-react";
19
+ import { useEffect, useMemo } from "react";
20
+
21
+ function KeyValueRow(props: {
22
+ label: string;
23
+ value?: string | number | null;
24
+ muted?: boolean;
25
+ }) {
26
+ const { label, muted, value: rawValue } = props;
27
+ const value =
28
+ rawValue === undefined || rawValue === null || rawValue === ""
29
+ ? "-"
30
+ : String(rawValue);
31
+ return (
32
+ <div className="flex items-start justify-between gap-4 py-2 text-sm">
33
+ <span className="text-gray-500">{label}</span>
34
+ <span
35
+ className={
36
+ muted ? "text-right text-gray-500" : "text-right text-gray-900"
37
+ }
38
+ >
39
+ {value}
40
+ </span>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ export function RemoteAccessPage() {
46
+ const presenter = useAppPresenter();
47
+ const remoteStatus = useRemoteStatus();
48
+ const status = remoteStatus.data;
49
+ const actionLabel = useRemoteAccessStore((state) => state.actionLabel);
50
+ const feedbackView = useMemo(
51
+ () => buildRemoteAccessFeedbackView(status),
52
+ [status],
53
+ );
54
+ const busy = Boolean(actionLabel);
55
+ const deviceName =
56
+ status?.runtime?.deviceName?.trim() ||
57
+ status?.settings.deviceName?.trim() ||
58
+ t("remoteDeviceNameAuto");
59
+ const canOpenDeviceList = Boolean(
60
+ status?.account.loggedIn && resolveRemoteWebBase(status),
61
+ );
62
+ const { hero: heroView, issueHint } = feedbackView;
63
+
64
+ useEffect(() => {
65
+ presenter.remoteAccessManager.syncStatus(status);
66
+ }, [presenter, status]);
67
+
68
+ if (remoteStatus.isLoading && !status) {
69
+ return <div className="p-8 text-gray-400">{t("remoteLoading")}</div>;
70
+ }
71
+
72
+ return (
73
+ <PageLayout className="space-y-6">
74
+ <PageHeader
75
+ title={t("remotePageTitle")}
76
+ description={t("remotePageDescription")}
77
+ />
78
+
79
+ <div className="grid gap-6 xl:grid-cols-[1.2fr_0.8fr]">
80
+ <Card>
81
+ <CardHeader className="space-y-4">
82
+ <div className="flex flex-wrap items-center gap-3">
83
+ <CardTitle>{heroView.title}</CardTitle>
84
+ <StatusDot
85
+ status={heroView.badgeStatus}
86
+ label={heroView.badgeLabel}
87
+ />
88
+ </div>
89
+ <CardDescription>{heroView.description}</CardDescription>
90
+ </CardHeader>
91
+ <CardContent className="space-y-5">
92
+ <NoticeCard tone="neutral">
93
+ <KeyValueRow
94
+ label={t("remoteSignedInAccount")}
95
+ value={status?.account.email}
96
+ />
97
+ <KeyValueRow label={t("remoteDeviceName")} value={deviceName} />
98
+ <KeyValueRow
99
+ label={t("remoteConnectionStatus")}
100
+ value={heroView.badgeLabel}
101
+ />
102
+ <KeyValueRow
103
+ label={t("remoteLastConnectedAt")}
104
+ value={
105
+ status?.runtime?.lastConnectedAt
106
+ ? formatDateTime(status.runtime.lastConnectedAt)
107
+ : "-"
108
+ }
109
+ muted
110
+ />
111
+ </NoticeCard>
112
+
113
+ <div className="flex flex-wrap gap-3">
114
+ {feedbackView.primaryAction ? (
115
+ <Button
116
+ onClick={() => {
117
+ if (feedbackView.primaryAction?.kind === "reauthorize") {
118
+ void presenter.remoteAccessManager.reauthorizeRemoteAccess(
119
+ status,
120
+ );
121
+ return;
122
+ }
123
+ if (feedbackView.primaryAction?.kind === "repair") {
124
+ void presenter.remoteAccessManager.repairRemoteAccess(
125
+ status,
126
+ );
127
+ return;
128
+ }
129
+ void presenter.remoteAccessManager.enableRemoteAccess(
130
+ status,
131
+ );
132
+ }}
133
+ disabled={busy}
134
+ >
135
+ {feedbackView.primaryAction.showRefreshIcon ? (
136
+ <RefreshCcw className="mr-2 h-4 w-4" />
137
+ ) : null}
138
+ {actionLabel || feedbackView.primaryAction.label}
139
+ </Button>
140
+ ) : null}
141
+
142
+ <Button
143
+ variant="outline"
144
+ onClick={() => void presenter.accountManager.openNextClawWeb()}
145
+ disabled={busy || !canOpenDeviceList}
146
+ >
147
+ <SquareArrowOutUpRight className="mr-2 h-4 w-4" />
148
+ {t("remoteOpenDeviceList")}
149
+ </Button>
150
+
151
+ {status?.settings.enabled ? (
152
+ <Button
153
+ variant="outline"
154
+ onClick={() =>
155
+ void presenter.remoteAccessManager.disableRemoteAccess(
156
+ status,
157
+ )
158
+ }
159
+ disabled={busy}
160
+ >
161
+ {t("remoteDisable")}
162
+ </Button>
163
+ ) : null}
164
+ </div>
165
+
166
+ {feedbackView.shouldShowIssueHint && issueHint ? (
167
+ <NoticeCard
168
+ tone="warning"
169
+ title={issueHint.title}
170
+ description={issueHint.body}
171
+ />
172
+ ) : null}
173
+
174
+ <p className="text-xs text-gray-500">{t("remoteOpenWebHint")}</p>
175
+ </CardContent>
176
+ </Card>
177
+
178
+ <Card>
179
+ <CardHeader>
180
+ <CardTitle className="flex items-center gap-2">
181
+ <Laptop className="h-4 w-4 text-primary" />
182
+ {t("remoteDeviceSectionTitle")}
183
+ </CardTitle>
184
+ <CardDescription>
185
+ {t("remoteDeviceSectionDescription")}
186
+ </CardDescription>
187
+ </CardHeader>
188
+ <CardContent className="space-y-5">
189
+ <div className="flex flex-wrap gap-2">
190
+ <StatusDot
191
+ status={status?.account.loggedIn ? "ready" : "inactive"}
192
+ label={
193
+ status?.account.loggedIn
194
+ ? t("remoteAccountConnected")
195
+ : t("remoteAccountNotConnected")
196
+ }
197
+ />
198
+ <StatusDot
199
+ status={status?.settings.enabled ? "active" : "inactive"}
200
+ label={
201
+ status?.settings.enabled
202
+ ? t("remoteEnabled")
203
+ : t("remoteStateDisabled")
204
+ }
205
+ />
206
+ <StatusDot
207
+ status={status?.service.running ? "active" : "inactive"}
208
+ label={
209
+ status?.service.running
210
+ ? t("remoteServiceRunning")
211
+ : t("remoteServiceStopped")
212
+ }
213
+ />
214
+ </div>
215
+
216
+ <NoticeCard tone="neutral">
217
+ <KeyValueRow label={t("remoteDeviceName")} value={deviceName} />
218
+ <KeyValueRow
219
+ label={t("remoteConnectionStatus")}
220
+ value={heroView.badgeLabel}
221
+ />
222
+ <KeyValueRow
223
+ label={t("remoteLastConnectedAt")}
224
+ value={
225
+ status?.runtime?.lastConnectedAt
226
+ ? formatDateTime(status.runtime.lastConnectedAt)
227
+ : "-"
228
+ }
229
+ muted
230
+ />
231
+ </NoticeCard>
232
+
233
+ <NoticeCard
234
+ tone="neutral"
235
+ borderStyle="dashed"
236
+ description={
237
+ status?.account.loggedIn
238
+ ? t("remoteOpenWebHint")
239
+ : t("remoteStatusNeedsSignInDescription")
240
+ }
241
+ className="text-sm"
242
+ />
243
+ </CardContent>
244
+ </Card>
245
+ </div>
246
+ </PageLayout>
247
+ );
248
+ }
@@ -0,0 +1,129 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const noticeCardVariants = cva("rounded-2xl border px-4 py-3", {
6
+ variants: {
7
+ tone: {
8
+ neutral: "border-gray-200 bg-gray-50 text-gray-900",
9
+ success: "border-emerald-200 bg-emerald-50 text-emerald-900",
10
+ warning: "border-amber-200 bg-amber-50 text-amber-900",
11
+ danger: "border-rose-200 bg-rose-50 text-rose-700",
12
+ info: "border-primary/20 bg-primary/10 text-primary",
13
+ },
14
+ borderStyle: {
15
+ solid: "",
16
+ dashed: "border-dashed",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ tone: "neutral",
21
+ borderStyle: "solid",
22
+ },
23
+ });
24
+
25
+ const titleClassMap: Record<
26
+ NonNullable<VariantProps<typeof noticeCardVariants>["tone"]>,
27
+ string
28
+ > = {
29
+ neutral: "text-gray-900",
30
+ success: "text-emerald-800",
31
+ warning: "text-amber-900",
32
+ danger: "text-rose-700",
33
+ info: "text-primary",
34
+ };
35
+
36
+ const descriptionClassMap: Record<
37
+ NonNullable<VariantProps<typeof noticeCardVariants>["tone"]>,
38
+ string
39
+ > = {
40
+ neutral: "text-gray-600",
41
+ success: "text-emerald-700",
42
+ warning: "text-amber-800",
43
+ danger: "text-rose-700",
44
+ info: "text-primary/90",
45
+ };
46
+
47
+ export interface NoticeCardProps
48
+ extends
49
+ Omit<React.HTMLAttributes<HTMLDivElement>, "title">,
50
+ VariantProps<typeof noticeCardVariants> {
51
+ title?: React.ReactNode;
52
+ description?: React.ReactNode;
53
+ icon?: React.ReactNode;
54
+ actions?: React.ReactNode;
55
+ }
56
+
57
+ export const NoticeCard = React.forwardRef<HTMLDivElement, NoticeCardProps>(
58
+ (
59
+ {
60
+ className,
61
+ tone = "neutral",
62
+ borderStyle = "solid",
63
+ title,
64
+ description,
65
+ icon,
66
+ actions,
67
+ children,
68
+ ...props
69
+ },
70
+ ref,
71
+ ) => {
72
+ const resolvedTone = tone ?? "neutral";
73
+ const hasHeader =
74
+ Boolean(title) ||
75
+ Boolean(description) ||
76
+ Boolean(icon) ||
77
+ Boolean(actions);
78
+
79
+ return (
80
+ <div
81
+ ref={ref}
82
+ className={cn(
83
+ noticeCardVariants({ tone: resolvedTone, borderStyle }),
84
+ className,
85
+ )}
86
+ {...props}
87
+ >
88
+ {hasHeader ? (
89
+ <div className="flex items-start justify-between gap-3">
90
+ <div className="min-w-0 flex-1">
91
+ <div className="flex items-start gap-2">
92
+ {icon ? <div className="mt-0.5 shrink-0">{icon}</div> : null}
93
+ <div className="min-w-0 flex-1">
94
+ {title ? (
95
+ <p
96
+ className={cn(
97
+ "text-sm font-medium",
98
+ titleClassMap[resolvedTone],
99
+ )}
100
+ >
101
+ {title}
102
+ </p>
103
+ ) : null}
104
+ {description ? (
105
+ <p
106
+ className={cn(
107
+ title ? "mt-1" : "",
108
+ "text-sm leading-6",
109
+ descriptionClassMap[resolvedTone],
110
+ )}
111
+ >
112
+ {description}
113
+ </p>
114
+ ) : null}
115
+ </div>
116
+ </div>
117
+ </div>
118
+ {actions ? <div className="shrink-0">{actions}</div> : null}
119
+ </div>
120
+ ) : null}
121
+ {children ? (
122
+ <div className={cn(hasHeader ? "mt-3" : "")}>{children}</div>
123
+ ) : null}
124
+ </div>
125
+ );
126
+ },
127
+ );
128
+
129
+ NoticeCard.displayName = "NoticeCard";
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const settingRowVariants = cva(
6
+ "flex items-start justify-between gap-4 rounded-xl border p-4",
7
+ {
8
+ variants: {
9
+ tone: {
10
+ default: "border-gray-200 bg-white",
11
+ muted: "border-gray-200 bg-gray-50",
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ tone: "default",
16
+ },
17
+ },
18
+ );
19
+
20
+ export interface SettingRowProps
21
+ extends
22
+ Omit<React.HTMLAttributes<HTMLDivElement>, "title">,
23
+ VariantProps<typeof settingRowVariants> {
24
+ title: React.ReactNode;
25
+ description?: React.ReactNode;
26
+ control?: React.ReactNode;
27
+ }
28
+
29
+ export const SettingRow = React.forwardRef<HTMLDivElement, SettingRowProps>(
30
+ (
31
+ { className, tone, title, description, control, children, ...props },
32
+ ref,
33
+ ) => (
34
+ <div
35
+ ref={ref}
36
+ className={cn(settingRowVariants({ tone }), className)}
37
+ {...props}
38
+ >
39
+ <div className="min-w-0 flex-1 space-y-2">
40
+ <div className="text-sm font-medium text-gray-900">{title}</div>
41
+ {description ? (
42
+ <p className="text-sm leading-6 text-gray-500">{description}</p>
43
+ ) : null}
44
+ {children}
45
+ </div>
46
+ {control ? <div className="shrink-0">{control}</div> : null}
47
+ </div>
48
+ ),
49
+ );
50
+
51
+ SettingRow.displayName = "SettingRow";
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const tagChipVariants = cva(
6
+ "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",
7
+ {
8
+ variants: {
9
+ tone: {
10
+ subtle: "border-gray-200 bg-gray-50 text-gray-600",
11
+ neutral: "border-gray-200 bg-white text-gray-600",
12
+ success: "border-emerald-200 bg-emerald-50 text-emerald-700",
13
+ warning: "border-amber-200 bg-amber-50 text-amber-800",
14
+ danger: "border-rose-200 bg-rose-50 text-rose-600",
15
+ info: "border-primary/20 bg-primary/10 text-primary",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ tone: "subtle",
20
+ },
21
+ },
22
+ );
23
+
24
+ export interface TagChipProps
25
+ extends
26
+ React.HTMLAttributes<HTMLSpanElement>,
27
+ VariantProps<typeof tagChipVariants> {}
28
+
29
+ export const TagChip = React.forwardRef<HTMLSpanElement, TagChipProps>(
30
+ ({ className, tone, ...props }, ref) => (
31
+ <span
32
+ ref={ref}
33
+ className={cn(tagChipVariants({ tone }), className)}
34
+ {...props}
35
+ />
36
+ ),
37
+ );
38
+
39
+ TagChip.displayName = "TagChip";
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
5
+
6
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
7
+ ({ className, ...props }, ref) => (
8
+ <textarea
9
+ className={cn(
10
+ "flex min-h-28 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2.5 text-sm text-gray-900 placeholder:text-gray-300 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50",
11
+ className,
12
+ )}
13
+ ref={ref}
14
+ {...props}
15
+ />
16
+ ),
17
+ );
18
+
19
+ Textarea.displayName = "Textarea";
@@ -0,0 +1,20 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { fetchServerPathRead } from '@/api/server-path';
3
+
4
+ export function useServerPathRead(params: {
5
+ path?: string | null;
6
+ basePath?: string | null;
7
+ enabled?: boolean;
8
+ }) {
9
+ const normalizedPath = params.path?.trim() ?? '';
10
+ return useQuery({
11
+ queryKey: ['server-path-read', normalizedPath, params.basePath ?? null],
12
+ queryFn: () =>
13
+ fetchServerPathRead({
14
+ path: normalizedPath,
15
+ basePath: params.basePath,
16
+ }),
17
+ enabled: (params.enabled ?? true) && normalizedPath.length > 0,
18
+ staleTime: 0,
19
+ });
20
+ }
@@ -185,7 +185,8 @@ export function useUpdateChannel() {
185
185
  updateChannel(channel, data as Parameters<typeof updateChannel>[1]),
186
186
  onSuccess: () => {
187
187
  queryClient.invalidateQueries({ queryKey: ['config'] });
188
- toast.success(t('configSavedApplied'));
188
+ queryClient.invalidateQueries({ queryKey: ['config-meta'] });
189
+ toast.success(t('configSavedApplying'));
189
190
  },
190
191
  onError: (error: Error) => {
191
192
  toast.error(t('configSaveFailed') + ': ' + error.message);
package/src/index.css CHANGED
@@ -59,6 +59,30 @@
59
59
  background: hsl(var(--gray-400));
60
60
  }
61
61
 
62
+ .workspace-horizontal-scrollbar {
63
+ scrollbar-width: thin;
64
+ scrollbar-color: hsl(var(--gray-300) / 0.38) transparent;
65
+ scrollbar-gutter: stable;
66
+ }
67
+
68
+ .workspace-horizontal-scrollbar::-webkit-scrollbar {
69
+ width: 3px;
70
+ height: 2px;
71
+ }
72
+
73
+ .workspace-horizontal-scrollbar::-webkit-scrollbar-track {
74
+ background: transparent;
75
+ }
76
+
77
+ .workspace-horizontal-scrollbar::-webkit-scrollbar-thumb {
78
+ background: hsl(var(--gray-300) / 0.38);
79
+ border-radius: 999px;
80
+ }
81
+
82
+ .workspace-horizontal-scrollbar::-webkit-scrollbar-thumb:hover {
83
+ background: hsl(var(--gray-400) / 0.48);
84
+ }
85
+
62
86
  /* ========================================
63
87
  GLASSMORPHISM
64
88
  ======================================== */
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveAppResourceUri } from "@/lib/app-resource-uri";
3
+
4
+ describe("resolveAppResourceUri", () => {
5
+ it("maps app resource uris to public app paths", () => {
6
+ expect(resolveAppResourceUri("app://runtime-icons/codex-openai.svg")).toBe(
7
+ "/runtime-icons/codex-openai.svg",
8
+ );
9
+ });
10
+
11
+ it("passes through ordinary image src values for compatibility", () => {
12
+ expect(resolveAppResourceUri("https://example.com/icon.png")).toBe(
13
+ "https://example.com/icon.png",
14
+ );
15
+ });
16
+
17
+ it("rejects app resource uris that escape the app resource directory", () => {
18
+ expect(resolveAppResourceUri("app://../icon.png")).toBeNull();
19
+ });
20
+ });
@@ -0,0 +1,29 @@
1
+ const APP_RESOURCE_URI_PREFIX = "app://";
2
+
3
+ function normalizeAppResourcePath(value: string): string | null {
4
+ const normalized = value.trim().replace(/^\/+/, "");
5
+ if (!normalized) {
6
+ return null;
7
+ }
8
+ const segments = normalized.split("/");
9
+ if (
10
+ segments.some((segment) => segment.trim().length === 0 || segment === "." || segment === "..")
11
+ ) {
12
+ return null;
13
+ }
14
+ return segments.join("/");
15
+ }
16
+
17
+ export function resolveAppResourceUri(uri: string): string | null {
18
+ const normalized = uri.trim();
19
+ if (!normalized) {
20
+ return null;
21
+ }
22
+ if (!normalized.startsWith(APP_RESOURCE_URI_PREFIX)) {
23
+ return normalized;
24
+ }
25
+ const appResourcePath = normalizeAppResourcePath(
26
+ normalized.slice(APP_RESOURCE_URI_PREFIX.length),
27
+ );
28
+ return appResourcePath ? `/${appResourcePath}` : null;
29
+ }
@@ -24,6 +24,17 @@ function truncateText(value: string, maxChars = 2400): string {
24
24
  return `${value.slice(0, maxChars)}\n…`;
25
25
  }
26
26
 
27
+ function truncateInlineText(value: string, maxChars = 120): string {
28
+ const normalized = value.replace(/\s+/g, ' ').trim();
29
+ if (normalized.length <= maxChars) {
30
+ return normalized;
31
+ }
32
+ if (maxChars <= 1) {
33
+ return '…';
34
+ }
35
+ return `${normalized.slice(0, maxChars - 1)}…`;
36
+ }
37
+
27
38
  export function stringifyUnknown(value: unknown): string {
28
39
  if (typeof value === 'string') {
29
40
  return value;
@@ -64,7 +75,7 @@ export function summarizeToolArgs(args: unknown): string | undefined {
64
75
  const parsed = parseArgsObject(args);
65
76
  if (!parsed) {
66
77
  const text = stringifyUnknown(args).trim();
67
- return text ? truncateText(text, 120) : undefined;
78
+ return text ? truncateInlineText(text, 120) : undefined;
68
79
  }
69
80
 
70
81
  const items: string[] = [];
@@ -80,9 +91,9 @@ export function summarizeToolArgs(args: unknown): string | undefined {
80
91
  }
81
92
  }
82
93
  if (items.length > 0) {
83
- return items.join(' · ');
94
+ return truncateInlineText(items.join(' · '), 120);
84
95
  }
85
- return truncateText(stringifyUnknown(parsed), 140);
96
+ return truncateInlineText(stringifyUnknown(parsed), 140);
86
97
  }
87
98
 
88
99
  function toToolName(value: unknown): string {
@@ -43,9 +43,20 @@ export const CHAT_LABELS: Record<string, { zh: string; en: string }> = {
43
43
  chatHistoryLoading: { zh: '加载会话历史中...', en: 'Loading session history...' },
44
44
  chatNoMessages: { zh: '暂无消息,发送一条开始对话。', en: 'No messages yet. Send one to start.' },
45
45
  chatBackToParent: { zh: '返回父会话', en: 'Back to parent' },
46
+ chatSessionOpenChildSessions: { zh: '查看子会话', en: 'View child sessions' },
46
47
  chatChildSessionLoading: { zh: '正在加载子会话…', en: 'Loading child session…' },
47
48
  chatChildSessionEmpty: { zh: '子会话还没有消息。', en: 'No child session messages yet.' },
48
- chatChildSessionClosePanel: { zh: '关闭子会话侧栏', en: 'Close child session panel' },
49
+ chatWorkspaceClosePanel: { zh: '关闭工作区侧栏', en: 'Close workspace panel' },
50
+ chatWorkspaceChildSessions: { zh: '子会话', en: 'Child sessions' },
51
+ chatWorkspaceOpenFiles: { zh: '打开的文件', en: 'Open files' },
52
+ chatWorkspacePreview: { zh: '预览', en: 'Preview' },
53
+ chatWorkspaceDiff: { zh: 'Diff', en: 'Diff' },
54
+ chatWorkspaceCloseFile: { zh: '关闭文件', en: 'Close file' },
55
+ chatWorkspaceLoadingFile: { zh: '正在加载文件…', en: 'Loading file…' },
56
+ chatWorkspacePreviewUnsupported: { zh: '该文件暂不支持在侧栏预览。', en: 'This file is not supported in the sidebar preview yet.' },
57
+ chatWorkspacePreviewEmpty: { zh: '当前没有可显示的文件内容。', en: 'No file content is available for preview.' },
58
+ chatWorkspaceDiffEmpty: { zh: '当前没有可显示的 diff 内容。', en: 'No diff content is available.' },
59
+ chatWorkspacePreviewTruncated: { zh: '内容已截断', en: 'Preview truncated' },
49
60
  chatSessionUnread: { zh: '会话有未读更新', en: 'Session has unread updates' },
50
61
  chatTyping: { zh: 'Agent 正在思考...', en: 'Agent is thinking...' },
51
62
  chatInputPlaceholder: { zh: '输入消息,输入 / 选择技能,Enter 发送,Shift + Enter 换行', en: 'Type a message, type / to select skills, Enter to send, Shift + Enter for newline' },