@nextclaw/ui 0.13.11 → 0.13.13
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.
- package/CHANGELOG.md +34 -0
- package/dist/assets/{api-DmxQnm30.js → api-D0hARyyl.js} +1 -1
- package/dist/assets/app-presenter-provider-C7EsUuEJ.js +3 -0
- package/dist/assets/arrow-left-CzSkR5L6.js +1 -0
- package/dist/assets/book-open-DmeQPqkd.js +1 -0
- package/dist/assets/{channels-list-page-bkKDqaO5.js → channels-list-page-B0yiiH01.js} +2 -2
- package/dist/assets/chat-page-ChPjx_X9.js +105 -0
- package/dist/assets/{config-split-page-CdJEkWxv.js → config-split-page-BzfZSyAr.js} +1 -1
- package/dist/assets/{confirm-dialog-BWRNsRgb.js → confirm-dialog-CTcaBp-8.js} +1 -1
- package/dist/assets/desktop-update-config-CExpdkqD.js +1 -0
- package/dist/assets/{dist-CyWdaJvr.js → dist-C59BKqbX.js} +1 -1
- package/dist/assets/{dist-DzM2ZEVN.js → dist-Di6EHp2x.js} +1 -1
- package/dist/assets/{doc-browser-DewXa6oX.js → doc-browser-EZbnEcdk.js} +1 -1
- package/dist/assets/doc-browser-XIwe1Tg-.js +1 -0
- package/dist/assets/{doc-browser-context-CpL2m4Sp.js → doc-browser-context-BzEA1lm1.js} +1 -1
- package/dist/assets/{ellipsis-BDgTYNDX.js → ellipsis-hXUNwi63.js} +1 -1
- package/dist/assets/{es2015-WqekV0OK.js → es2015-CJlVSTh-.js} +1 -1
- package/dist/assets/{external-link-BLjo8z2a.js → external-link-J0fCK-vu.js} +1 -1
- package/dist/assets/host-capabilities-DjiRQ0Q9.js +1 -0
- package/dist/assets/index-DfZTO6Pi.js +101 -0
- package/dist/assets/index-mIb_YMrc.css +1 -0
- package/dist/assets/{key-round-Du8x3sWa.js → key-round-BzYZ0Nno.js} +1 -1
- package/dist/assets/loader-circle-BbdIlBbQ.js +1 -0
- package/dist/assets/mcp-marketplace-page-L22aCzHX.js +40 -0
- package/dist/assets/mcp-marketplace-page-f80Jmv37.js +1 -0
- package/dist/assets/model-config-DkD7vSQT.js +1 -0
- package/dist/assets/{notice-card-3a7FmAz-.js → notice-card-CD9Ok62G.js} +1 -1
- package/dist/assets/play-DHuSeKs8.js +1 -0
- package/dist/assets/plus-DrV685KC.js +1 -0
- package/dist/assets/{popover-BlJLguiZ.js → popover-CoBu3mbV.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-BgPTQa8H.js → provider-scoped-model-input-DZlmTCJU.js} +1 -1
- package/dist/assets/providers-list-CVkWiqIy.js +1 -0
- package/dist/assets/{react-BVjl1ZaK.js → react-C7HsxuKH.js} +1 -1
- package/dist/assets/{refresh-cw-DFR87zei.js → refresh-cw-CVo4nWpq.js} +1 -1
- package/dist/assets/remote-Bznw9iji.js +1 -0
- package/dist/assets/rotate-cw-CXDpRO6D.js +1 -0
- package/dist/assets/{runtime-config-page-DYH2ejUI.js → runtime-config-page-5IycbIxs.js} +1 -1
- package/dist/assets/{save-BDmIlHvN.js → save-Z1QTovIt.js} +1 -1
- package/dist/assets/search-8nY6PGm0.js +1 -0
- package/dist/assets/{search-config-BRsrQ4zK.js → search-config-DEH80WkC.js} +1 -1
- package/dist/assets/{secrets-config-eYdx5_AW.js → secrets-config-Gzo5nDJG.js} +1 -1
- package/dist/assets/{status-dot-SVPjIyG5.js → status-dot-C1-FKgvv.js} +1 -1
- package/dist/assets/{tabs-Czd2mj6z.js → tabs-BqkKwx1R.js} +1 -1
- package/dist/assets/{tabs-custom-Ix4nkp2h.js → tabs-custom-DOkaJZmt.js} +1 -1
- package/dist/assets/{tag-chip-Cdwb3JSH.js → tag-chip-3kpiMTST.js} +1 -1
- package/dist/assets/{tooltip-CKGxLGnG.js → tooltip-Bw-mXQAM.js} +1 -1
- package/dist/assets/{trash-2-yLA8BEcE.js → trash-2-CmIwAJIT.js} +1 -1
- package/dist/assets/x-CSRTOZnM.js +1 -0
- package/dist/index.html +27 -25
- package/package.json +8 -8
- package/src/features/account/components/account-panel.tsx +2 -2
- package/src/features/account/managers/account.manager.ts +6 -5
- package/src/features/chat/components/chat-session-workspace-file-preview.test.tsx +39 -0
- package/src/features/chat/components/chat-session-workspace-file-preview.tsx +11 -4
- package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +45 -13
- package/src/features/chat/components/chat-session-workspace-panel.tsx +20 -8
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +73 -0
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +4 -0
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
- package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +2 -0
- package/src/features/chat/managers/chat-session-list.manager.test.ts +4 -0
- package/src/features/chat/managers/chat-session-list.manager.ts +2 -0
- package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +153 -25
- package/src/features/chat/managers/ncp-chat-thread.manager.ts +153 -7
- package/src/features/chat/pages/ncp-chat-page.tsx +4 -0
- package/src/features/chat/stores/chat-thread.store.test.ts +148 -0
- package/src/features/chat/stores/chat-thread.store.ts +289 -9
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +21 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +6 -0
- package/src/features/marketplace/components/marketplace-catalog-grid.tsx +19 -1
- package/src/features/marketplace/components/marketplace-external-skill-source-action.tsx +14 -0
- package/src/features/marketplace/components/marketplace-page-parts.tsx +10 -2
- package/src/features/marketplace/components/marketplace-page.test.tsx +37 -1
- package/src/features/marketplace/components/marketplace-page.tsx +38 -16
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +1 -0
- package/src/features/marketplace/hooks/use-marketplace.test.tsx +109 -0
- package/src/features/marketplace/hooks/use-marketplace.ts +3 -5
- package/src/features/side-dock/components/side-dock.test.tsx +25 -0
- package/src/features/side-dock/components/side-dock.tsx +13 -0
- package/src/features/system-status/components/desktop-update-config.tsx +2 -1
- package/src/platforms/desktop/types/desktop-update.types.ts +9 -0
- package/src/shared/components/config/provider-form.tsx +2 -2
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +39 -32
- package/src/shared/components/ui/action-link.tsx +23 -1
- package/src/shared/lib/api/ncp-session.types.ts +1 -0
- package/src/shared/lib/host-capabilities/host-capability.manager.test.ts +50 -0
- package/src/shared/lib/host-capabilities/host-capability.manager.ts +62 -0
- package/src/shared/lib/host-capabilities/index.ts +1 -0
- package/src/shared/lib/i18n/README.md +16 -3
- package/src/shared/lib/i18n/index.ts +93 -426
- package/src/shared/lib/i18n/locales/en-US/agents.json +49 -0
- package/src/shared/lib/i18n/locales/en-US/channel-auth.json +51 -0
- package/src/shared/lib/i18n/locales/en-US/channels.json +50 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +181 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +310 -0
- package/src/shared/lib/i18n/locales/en-US/cron.json +32 -0
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +90 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +87 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +72 -0
- package/src/shared/lib/i18n/locales/en-US/path-picker.json +12 -0
- package/src/shared/lib/i18n/locales/en-US/pwa.json +26 -0
- package/src/shared/lib/i18n/locales/en-US/remote.json +168 -0
- package/src/shared/lib/i18n/locales/en-US/runtime-control.json +105 -0
- package/src/shared/lib/i18n/locales/en-US/search.json +28 -0
- package/src/shared/lib/i18n/locales/zh-CN/agents.json +49 -0
- package/src/shared/lib/i18n/locales/zh-CN/channel-auth.json +51 -0
- package/src/shared/lib/i18n/locales/zh-CN/channels.json +50 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +181 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +310 -0
- package/src/shared/lib/i18n/locales/zh-CN/cron.json +32 -0
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +90 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +87 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +72 -0
- package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +12 -0
- package/src/shared/lib/i18n/locales/zh-CN/pwa.json +26 -0
- package/src/shared/lib/i18n/locales/zh-CN/remote.json +168 -0
- package/src/shared/lib/i18n/locales/zh-CN/runtime-control.json +105 -0
- package/src/shared/lib/i18n/locales/zh-CN/search.json +28 -0
- package/src/shared/lib/navigation-history/index.ts +1 -0
- package/src/shared/lib/navigation-history/navigation-history.utils.test.ts +60 -0
- package/src/shared/lib/navigation-history/navigation-history.utils.ts +105 -0
- package/dist/assets/app-presenter-provider-CDSjiqfE.js +0 -3
- package/dist/assets/arrow-left-Dd45Mvr1.js +0 -1
- package/dist/assets/chat-page-YKdeGoVx.js +0 -105
- package/dist/assets/desktop-update-config-CLXdVz9r.js +0 -1
- package/dist/assets/doc-browser-D5FJcAyz.js +0 -1
- package/dist/assets/index-DKflQCOL.js +0 -101
- package/dist/assets/index-Q891JiF_.css +0 -1
- package/dist/assets/loader-circle-Dz4FssYC.js +0 -1
- package/dist/assets/mcp-marketplace-page-BaBuHB2L.js +0 -40
- package/dist/assets/mcp-marketplace-page-ooEvG_bB.js +0 -1
- package/dist/assets/model-config-B-qg70S4.js +0 -1
- package/dist/assets/play-BziM-Y34.js +0 -1
- package/dist/assets/plus-DrpyArC8.js +0 -1
- package/dist/assets/providers-list-cBeeslbc.js +0 -1
- package/dist/assets/remote-DjhVMpVd.js +0 -1
- package/dist/assets/rotate-cw-DMBnsYKm.js +0 -1
- package/dist/assets/search-CjHGYvTI.js +0 -1
- package/dist/assets/x-dpDISboO.js +0 -1
- package/src/shared/lib/i18n/agents.ts +0 -85
- package/src/shared/lib/i18n/channel-auth.constants.ts +0 -87
- package/src/shared/lib/i18n/channels.ts +0 -53
- package/src/shared/lib/i18n/chat-labels.utils.ts +0 -206
- package/src/shared/lib/i18n/cron-labels.utils.ts +0 -32
- package/src/shared/lib/i18n/desktop-update-labels.utils.ts +0 -165
- package/src/shared/lib/i18n/marketplace-labels.utils.ts +0 -78
- package/src/shared/lib/i18n/pwa.ts +0 -62
- package/src/shared/lib/i18n/remote.ts +0 -273
- package/src/shared/lib/i18n/runtime/doc-browser-labels.utils.ts +0 -120
- package/src/shared/lib/i18n/runtime/i18n.path-picker.ts +0 -12
- package/src/shared/lib/i18n/runtime-control-labels.utils.ts +0 -180
- package/src/shared/lib/i18n/search.ts +0 -37
- /package/dist/assets/{config-hints-Ceiol9x4.js → config-hints-fGnUjDe9.js} +0 -0
- /package/dist/assets/{provider-models-C_yOh6DE.js → provider-models-C9VRroK3.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AlarmClock, ArrowLeft, FileCode2, MessageSquareText, X } from "lucide-react";
|
|
1
|
+
import { AlarmClock, ArrowLeft, ArrowRight, FileCode2, MessageSquareText, X } from "lucide-react";
|
|
2
2
|
import type { ResolvedChildSessionTab } from "@/features/chat/hooks/use-ncp-child-session-tabs-view";
|
|
3
3
|
import type { ChatWorkspaceFileTab } from "@/features/chat/stores/chat-thread.store";
|
|
4
4
|
import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
|
|
@@ -184,12 +184,18 @@ function WorkspaceTabItem({ tab }: { tab: WorkspaceTabViewModel }) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
export function WorkspaceTabsBar({
|
|
187
|
-
|
|
187
|
+
canGoBack,
|
|
188
|
+
canGoForward,
|
|
188
189
|
onClose,
|
|
190
|
+
onGoBack,
|
|
191
|
+
onGoForward,
|
|
189
192
|
tabs,
|
|
190
193
|
}: {
|
|
191
|
-
|
|
194
|
+
canGoBack: boolean;
|
|
195
|
+
canGoForward: boolean;
|
|
192
196
|
onClose: () => void;
|
|
197
|
+
onGoBack: () => void;
|
|
198
|
+
onGoForward: () => void;
|
|
193
199
|
tabs: readonly WorkspaceTabViewModel[];
|
|
194
200
|
}) {
|
|
195
201
|
return (
|
|
@@ -208,16 +214,42 @@ export function WorkspaceTabsBar({
|
|
|
208
214
|
))}
|
|
209
215
|
</div>
|
|
210
216
|
</div>
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
<Tooltip>
|
|
218
|
+
<TooltipTrigger asChild>
|
|
219
|
+
<span className="ml-1 inline-flex self-center">
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
onClick={onGoBack}
|
|
223
|
+
disabled={!canGoBack}
|
|
224
|
+
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 disabled:cursor-not-allowed disabled:text-gray-300 disabled:hover:bg-transparent"
|
|
225
|
+
aria-label={t("chatWorkspaceBack")}
|
|
226
|
+
>
|
|
227
|
+
<ArrowLeft className="h-4 w-4" />
|
|
228
|
+
</button>
|
|
229
|
+
</span>
|
|
230
|
+
</TooltipTrigger>
|
|
231
|
+
<TooltipContent side="bottom" className="text-xs">
|
|
232
|
+
{t("chatWorkspaceBack")}
|
|
233
|
+
</TooltipContent>
|
|
234
|
+
</Tooltip>
|
|
235
|
+
<Tooltip>
|
|
236
|
+
<TooltipTrigger asChild>
|
|
237
|
+
<span className="inline-flex self-center">
|
|
238
|
+
<button
|
|
239
|
+
type="button"
|
|
240
|
+
onClick={onGoForward}
|
|
241
|
+
disabled={!canGoForward}
|
|
242
|
+
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 disabled:cursor-not-allowed disabled:text-gray-300 disabled:hover:bg-transparent"
|
|
243
|
+
aria-label={t("chatWorkspaceForward")}
|
|
244
|
+
>
|
|
245
|
+
<ArrowRight className="h-4 w-4" />
|
|
246
|
+
</button>
|
|
247
|
+
</span>
|
|
248
|
+
</TooltipTrigger>
|
|
249
|
+
<TooltipContent side="bottom" className="text-xs">
|
|
250
|
+
{t("chatWorkspaceForward")}
|
|
251
|
+
</TooltipContent>
|
|
252
|
+
</Tooltip>
|
|
221
253
|
<button
|
|
222
254
|
type="button"
|
|
223
255
|
onClick={onClose}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "@/features/chat/stores/chat-session-list.store";
|
|
19
19
|
import type {
|
|
20
20
|
ChatChildSessionTab,
|
|
21
|
+
ChatWorkspaceNavigationEntry,
|
|
21
22
|
ChatWorkspaceFileTab,
|
|
22
23
|
} from "@/features/chat/stores/chat-thread.store";
|
|
23
24
|
import {
|
|
@@ -33,6 +34,10 @@ import { SessionCronJobContent } from "@/features/chat/components/workspace/sess
|
|
|
33
34
|
import { ResizableRightPanel } from "@/shared/components/resizable-right-panel/resizable-right-panel";
|
|
34
35
|
import { t } from "@/shared/lib/i18n";
|
|
35
36
|
import { cn } from "@/shared/lib/utils";
|
|
37
|
+
import {
|
|
38
|
+
canGoBackInNavigationHistory,
|
|
39
|
+
canGoForwardInNavigationHistory,
|
|
40
|
+
} from "@/shared/lib/navigation-history";
|
|
36
41
|
|
|
37
42
|
type ChatSessionWorkspacePanelProps = {
|
|
38
43
|
sessionKey: string | null;
|
|
@@ -40,9 +45,12 @@ type ChatSessionWorkspacePanelProps = {
|
|
|
40
45
|
activeChildSessionKey: string | null;
|
|
41
46
|
workspaceFileTabs: readonly ChatWorkspaceFileTab[];
|
|
42
47
|
activeWorkspaceFileKey: string | null;
|
|
48
|
+
workspaceNavigationHistory?: readonly ChatWorkspaceNavigationEntry[];
|
|
49
|
+
workspaceNavigationHistoryIndex?: number;
|
|
43
50
|
activePanelKind?: "child-session" | "file" | "cron" | null;
|
|
44
51
|
sessionCronJobs?: readonly CronJobView[];
|
|
45
52
|
sessionProjectRoot: string | null;
|
|
53
|
+
sessionWorkingDir: string | null;
|
|
46
54
|
displayMode?: "docked" | "overlay";
|
|
47
55
|
};
|
|
48
56
|
|
|
@@ -246,9 +254,12 @@ export function ChatSessionWorkspacePanel({
|
|
|
246
254
|
activeChildSessionKey,
|
|
247
255
|
workspaceFileTabs,
|
|
248
256
|
activeWorkspaceFileKey,
|
|
257
|
+
workspaceNavigationHistory = [],
|
|
258
|
+
workspaceNavigationHistoryIndex = 0,
|
|
249
259
|
activePanelKind,
|
|
250
260
|
sessionCronJobs = [],
|
|
251
261
|
sessionProjectRoot,
|
|
262
|
+
sessionWorkingDir,
|
|
252
263
|
displayMode = "docked",
|
|
253
264
|
}: ChatSessionWorkspacePanelProps) {
|
|
254
265
|
const presenter = usePresenter();
|
|
@@ -264,9 +275,10 @@ export function ChatSessionWorkspacePanel({
|
|
|
264
275
|
workspaceFileTabs,
|
|
265
276
|
sessionCronJobCount: sessionCronJobs.length,
|
|
266
277
|
});
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
278
|
+
const workspaceHistory = {
|
|
279
|
+
entries: workspaceNavigationHistory,
|
|
280
|
+
index: workspaceNavigationHistoryIndex,
|
|
281
|
+
};
|
|
270
282
|
|
|
271
283
|
useEffect(() => {
|
|
272
284
|
if (
|
|
@@ -331,11 +343,10 @@ export function ChatSessionWorkspacePanel({
|
|
|
331
343
|
>
|
|
332
344
|
<WorkspaceTabsBar
|
|
333
345
|
tabs={workspaceTabs}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
346
|
+
canGoBack={canGoBackInNavigationHistory(workspaceHistory)}
|
|
347
|
+
canGoForward={canGoForwardInNavigationHistory(workspaceHistory)}
|
|
348
|
+
onGoBack={presenter.chatThreadManager.goBackWorkspacePanel}
|
|
349
|
+
onGoForward={presenter.chatThreadManager.goForwardWorkspacePanel}
|
|
339
350
|
onClose={presenter.chatThreadManager.closeWorkspacePanel}
|
|
340
351
|
/>
|
|
341
352
|
|
|
@@ -357,6 +368,7 @@ export function ChatSessionWorkspacePanel({
|
|
|
357
368
|
<ChatSessionWorkspaceFilePreview
|
|
358
369
|
file={activeSelection.file}
|
|
359
370
|
sessionProjectRoot={sessionProjectRoot}
|
|
371
|
+
sessionWorkingDir={sessionWorkingDir}
|
|
360
372
|
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
361
373
|
/>
|
|
362
374
|
) : (
|
|
@@ -16,6 +16,8 @@ const mocks = vi.hoisted(() => ({
|
|
|
16
16
|
goToChatRoot: vi.fn(),
|
|
17
17
|
goToSession: vi.fn(),
|
|
18
18
|
openSessionCronPanel: vi.fn(),
|
|
19
|
+
goBackWorkspacePanel: vi.fn(),
|
|
20
|
+
goForwardWorkspacePanel: vi.fn(),
|
|
19
21
|
deleteCronJob: vi.fn(),
|
|
20
22
|
cronJobs: [] as CronJobView[],
|
|
21
23
|
setSelectedAgentId: vi.fn(),
|
|
@@ -119,6 +121,8 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
|
119
121
|
selectWorkspaceFile: vi.fn(),
|
|
120
122
|
closeWorkspaceFile: vi.fn(),
|
|
121
123
|
closeWorkspacePanel: vi.fn(),
|
|
124
|
+
goBackWorkspacePanel: mocks.goBackWorkspacePanel,
|
|
125
|
+
goForwardWorkspacePanel: mocks.goForwardWorkspacePanel,
|
|
122
126
|
goToParentSession: vi.fn(),
|
|
123
127
|
},
|
|
124
128
|
chatSessionListManager: {
|
|
@@ -200,8 +204,25 @@ function resetChatConversationPanelTestState() {
|
|
|
200
204
|
mocks.goToChatRoot.mockReset();
|
|
201
205
|
mocks.goToSession.mockReset();
|
|
202
206
|
mocks.openSessionCronPanel.mockReset();
|
|
207
|
+
mocks.goBackWorkspacePanel.mockReset();
|
|
208
|
+
mocks.goForwardWorkspacePanel.mockReset();
|
|
203
209
|
mocks.deleteCronJob.mockReset();
|
|
204
210
|
mocks.cronJobs = [];
|
|
211
|
+
mocks.resolvedChildTabs = [
|
|
212
|
+
{
|
|
213
|
+
sessionKey: "child-session-1",
|
|
214
|
+
parentSessionKey: "parent-session-1",
|
|
215
|
+
title: "北京天气",
|
|
216
|
+
agentId: "weather",
|
|
217
|
+
updatedAt: "2026-04-10T09:00:00.000Z",
|
|
218
|
+
lastMessageAt: "2026-04-10T09:00:00.000Z",
|
|
219
|
+
readAt: "2026-04-10T09:00:00.000Z",
|
|
220
|
+
sessionTypeLabel: "Codex",
|
|
221
|
+
preferredModel: "openai/gpt-5.3-codex",
|
|
222
|
+
projectName: "project-alpha",
|
|
223
|
+
projectRoot: "/Users/demo/project-alpha",
|
|
224
|
+
},
|
|
225
|
+
];
|
|
205
226
|
mocks.setSelectedAgentId.mockReset();
|
|
206
227
|
mocks.setPendingSessionType.mockReset();
|
|
207
228
|
mocks.stickyBottomScroll.mockClear();
|
|
@@ -247,6 +268,8 @@ function resetChatConversationPanelTestState() {
|
|
|
247
268
|
activeChildSessionKey: null,
|
|
248
269
|
workspaceFileTabs: [],
|
|
249
270
|
activeWorkspaceFileKey: null,
|
|
271
|
+
workspaceNavigationHistory: [],
|
|
272
|
+
workspaceNavigationHistoryIndex: 0,
|
|
250
273
|
},
|
|
251
274
|
});
|
|
252
275
|
useChatSessionListStore.setState({
|
|
@@ -406,6 +429,11 @@ describe("ChatConversationPanel", () => {
|
|
|
406
429
|
messageCount: "1",
|
|
407
430
|
sending: "true",
|
|
408
431
|
});
|
|
432
|
+
expect(
|
|
433
|
+
screen
|
|
434
|
+
.getByTestId("chat-message-list")
|
|
435
|
+
.closest('[data-chat-scroll-container="true"]'),
|
|
436
|
+
).toBeTruthy();
|
|
409
437
|
expect(
|
|
410
438
|
screen.queryByText("No messages yet. Send one to start."),
|
|
411
439
|
).toBeNull();
|
|
@@ -520,6 +548,8 @@ describe("ChatConversationPanel", () => {
|
|
|
520
548
|
});
|
|
521
549
|
|
|
522
550
|
describe("ChatSessionWorkspacePanel", () => {
|
|
551
|
+
beforeEach(resetChatConversationPanelTestState);
|
|
552
|
+
|
|
523
553
|
it("renders child session tabs and active child metadata in the workspace sidebar", () => {
|
|
524
554
|
mocks.resolvedChildTabs = [
|
|
525
555
|
{
|
|
@@ -552,6 +582,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
552
582
|
workspaceFileTabs={[]}
|
|
553
583
|
activeWorkspaceFileKey={null}
|
|
554
584
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
585
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
555
586
|
/>,
|
|
556
587
|
);
|
|
557
588
|
|
|
@@ -622,6 +653,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
622
653
|
workspaceFileTabs={[]}
|
|
623
654
|
activeWorkspaceFileKey={null}
|
|
624
655
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
656
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
625
657
|
/>,
|
|
626
658
|
);
|
|
627
659
|
|
|
@@ -645,6 +677,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
645
677
|
]}
|
|
646
678
|
activeWorkspaceFileKey="parent-session-1::preview::README.md"
|
|
647
679
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
680
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
648
681
|
/>,
|
|
649
682
|
);
|
|
650
683
|
|
|
@@ -658,6 +691,45 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
658
691
|
).toContain("workspace-horizontal-scrollbar");
|
|
659
692
|
});
|
|
660
693
|
|
|
694
|
+
it("uses workspace-local backward and forward history actions in the tab bar", async () => {
|
|
695
|
+
const user = userEvent.setup();
|
|
696
|
+
|
|
697
|
+
render(
|
|
698
|
+
<ChatSessionWorkspacePanel
|
|
699
|
+
sessionKey="parent-session-1"
|
|
700
|
+
childSessionTabs={[]}
|
|
701
|
+
activeChildSessionKey={null}
|
|
702
|
+
workspaceFileTabs={[
|
|
703
|
+
{
|
|
704
|
+
key: "parent-session-1::preview::README.md",
|
|
705
|
+
parentSessionKey: "parent-session-1",
|
|
706
|
+
path: "README.md",
|
|
707
|
+
label: "README.md",
|
|
708
|
+
viewMode: "preview",
|
|
709
|
+
},
|
|
710
|
+
]}
|
|
711
|
+
activeWorkspaceFileKey="parent-session-1::preview::README.md"
|
|
712
|
+
workspaceNavigationHistory={[
|
|
713
|
+
{ kind: "child-session", key: "child-session-1" },
|
|
714
|
+
{ kind: "file", key: "parent-session-1::preview::README.md" },
|
|
715
|
+
{ kind: "cron" },
|
|
716
|
+
]}
|
|
717
|
+
workspaceNavigationHistoryIndex={1}
|
|
718
|
+
sessionProjectRoot="/Users/demo/project-alpha"
|
|
719
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
720
|
+
/>,
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
await user.click(screen.getByRole("button", { name: "Back in workspace" }));
|
|
724
|
+
await user.click(
|
|
725
|
+
screen.getByRole("button", { name: "Forward in workspace" }),
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
expect(mocks.goBackWorkspacePanel).toHaveBeenCalledTimes(1);
|
|
729
|
+
expect(mocks.goForwardWorkspacePanel).toHaveBeenCalledTimes(1);
|
|
730
|
+
expect(screen.queryByRole("button", { name: "Back to parent" })).toBeNull();
|
|
731
|
+
});
|
|
732
|
+
|
|
661
733
|
it("renders session cron jobs in the workspace sidebar and deletes with a neutral confirmation", async () => {
|
|
662
734
|
const user = userEvent.setup();
|
|
663
735
|
const job: CronJobView = {
|
|
@@ -691,6 +763,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
691
763
|
activePanelKind="cron"
|
|
692
764
|
sessionCronJobs={[job]}
|
|
693
765
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
766
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
694
767
|
/>,
|
|
695
768
|
);
|
|
696
769
|
|
|
@@ -194,6 +194,7 @@ function ChatConversationContent() {
|
|
|
194
194
|
<div
|
|
195
195
|
ref={threadRef}
|
|
196
196
|
onScroll={onScroll}
|
|
197
|
+
data-chat-scroll-container="true"
|
|
197
198
|
className="flex-1 min-h-0 overflow-y-auto custom-scrollbar"
|
|
198
199
|
>
|
|
199
200
|
{showWelcome ? (
|
|
@@ -363,9 +364,12 @@ function ChatSessionWorkspacePanelContainer({
|
|
|
363
364
|
activeChildSessionKey={snapshot.activeChildSessionKey ?? null}
|
|
364
365
|
workspaceFileTabs={workspaceFileTabs}
|
|
365
366
|
activeWorkspaceFileKey={snapshot.activeWorkspaceFileKey ?? null}
|
|
367
|
+
workspaceNavigationHistory={snapshot.workspaceNavigationHistory}
|
|
368
|
+
workspaceNavigationHistoryIndex={snapshot.workspaceNavigationHistoryIndex}
|
|
366
369
|
activePanelKind={snapshot.activeWorkspacePanelKind ?? null}
|
|
367
370
|
sessionCronJobs={sessionCronJobs}
|
|
368
371
|
sessionProjectRoot={snapshot.sessionProjectRoot ?? null}
|
|
372
|
+
sessionWorkingDir={snapshot.sessionWorkingDir ?? snapshot.sessionProjectRoot ?? null}
|
|
369
373
|
displayMode={layoutMode === "mobile" ? "overlay" : "docked"}
|
|
370
374
|
/>
|
|
371
375
|
);
|
|
@@ -42,6 +42,8 @@ export type ChatThreadManagerLike = {
|
|
|
42
42
|
selectWorkspaceFile: (fileKey: string) => void;
|
|
43
43
|
closeWorkspaceFile: (fileKey: string) => void;
|
|
44
44
|
closeWorkspacePanel: () => void;
|
|
45
|
+
goBackWorkspacePanel: () => void;
|
|
46
|
+
goForwardWorkspacePanel: () => void;
|
|
45
47
|
goToParentSession: () => void;
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -119,6 +119,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
119
119
|
availableAgents: AgentProfileView[];
|
|
120
120
|
currentSessionDisplayName?: string;
|
|
121
121
|
effectiveSessionProjectRoot: string | null;
|
|
122
|
+
effectiveSessionWorkingDir: string | null;
|
|
122
123
|
effectiveSessionProjectName: string | null;
|
|
123
124
|
selectedSession: SessionEntryView | null;
|
|
124
125
|
threadRef: MutableRefObject<HTMLDivElement | null>;
|
|
@@ -159,6 +160,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
159
160
|
availableAgents: params.availableAgents,
|
|
160
161
|
sessionDisplayName: params.currentSessionDisplayName,
|
|
161
162
|
sessionProjectRoot: params.effectiveSessionProjectRoot,
|
|
163
|
+
sessionWorkingDir: params.effectiveSessionWorkingDir,
|
|
162
164
|
sessionProjectName: params.effectiveSessionProjectName,
|
|
163
165
|
canDeleteSession: Boolean(params.selectedSession),
|
|
164
166
|
threadRef: params.threadRef,
|
|
@@ -57,6 +57,10 @@ describe('ChatSessionListManager', () => {
|
|
|
57
57
|
activeWorkspacePanelKind: 'child-session',
|
|
58
58
|
activeChildSessionKey: 'child-session-1',
|
|
59
59
|
activeWorkspaceFileKey: null,
|
|
60
|
+
workspaceNavigationHistory: [
|
|
61
|
+
{ kind: 'child-session', key: 'child-session-1' },
|
|
62
|
+
],
|
|
63
|
+
workspaceNavigationHistoryIndex: 0,
|
|
60
64
|
},
|
|
61
65
|
});
|
|
62
66
|
});
|
|
@@ -19,33 +19,37 @@ vi.mock('@/shared/lib/api', async (importOriginal) => {
|
|
|
19
19
|
};
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
snapshot
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
useChatThreadStore.setState({
|
|
32
|
-
snapshot: {
|
|
33
|
-
...useChatThreadStore.getState().snapshot,
|
|
34
|
-
sessionKey: 'parent-session-1',
|
|
35
|
-
workspacePanelParentKey: null,
|
|
36
|
-
childSessionTabs: [
|
|
37
|
-
{
|
|
38
|
-
sessionKey: 'child-session-1',
|
|
39
|
-
parentSessionKey: 'parent-session-1',
|
|
40
|
-
label: 'Child Session 1',
|
|
41
|
-
agentId: 'reviewer',
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
activeChildSessionKey: null,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
useChatSessionListStore.setState({
|
|
24
|
+
optimisticReadAtBySessionKey: {},
|
|
25
|
+
snapshot: {
|
|
26
|
+
...useChatSessionListStore.getState().snapshot,
|
|
27
|
+
selectedSessionKey: 'parent-session-1',
|
|
28
|
+
},
|
|
47
29
|
});
|
|
30
|
+
useChatThreadStore.setState({
|
|
31
|
+
snapshot: {
|
|
32
|
+
...useChatThreadStore.getState().snapshot,
|
|
33
|
+
sessionKey: 'parent-session-1',
|
|
34
|
+
workspacePanelParentKey: null,
|
|
35
|
+
childSessionTabs: [
|
|
36
|
+
{
|
|
37
|
+
sessionKey: 'child-session-1',
|
|
38
|
+
parentSessionKey: 'parent-session-1',
|
|
39
|
+
label: 'Child Session 1',
|
|
40
|
+
agentId: 'reviewer',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
activeChildSessionKey: null,
|
|
44
|
+
workspaceFileTabs: [],
|
|
45
|
+
activeWorkspaceFileKey: null,
|
|
46
|
+
workspaceNavigationHistory: [],
|
|
47
|
+
workspaceNavigationHistoryIndex: 0,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
});
|
|
48
51
|
|
|
52
|
+
describe('NcpChatThreadManager', () => {
|
|
49
53
|
it('opens the child-session panel for the requested parent session and keeps focus on the chosen child', () => {
|
|
50
54
|
const uiManager = {
|
|
51
55
|
goToSession: vi.fn(),
|
|
@@ -190,7 +194,129 @@ describe('NcpChatThreadManager', () => {
|
|
|
190
194
|
]),
|
|
191
195
|
);
|
|
192
196
|
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('NcpChatThreadManager workspace navigation', () => {
|
|
200
|
+
it('navigates backward and forward through workspace selections', () => {
|
|
201
|
+
const uiManager = {
|
|
202
|
+
goToSession: vi.fn(),
|
|
203
|
+
goToChatRoot: vi.fn(),
|
|
204
|
+
goToProviders: vi.fn(),
|
|
205
|
+
confirm: vi.fn(),
|
|
206
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
207
|
+
const manager = new NcpChatThreadManager(
|
|
208
|
+
uiManager,
|
|
209
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
210
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
manager.openChildSessionPanel({
|
|
214
|
+
parentSessionKey: 'parent-session-1',
|
|
215
|
+
activeChildSessionKey: 'child-session-1',
|
|
216
|
+
});
|
|
217
|
+
manager.openFilePreview({
|
|
218
|
+
path: 'README.md',
|
|
219
|
+
label: 'README.md',
|
|
220
|
+
viewMode: 'preview',
|
|
221
|
+
});
|
|
222
|
+
manager.openSessionCronPanel('parent-session-1');
|
|
223
|
+
|
|
224
|
+
expect(useChatThreadStore.getState().snapshot.workspaceNavigationHistory).toEqual([
|
|
225
|
+
{ kind: 'child-session', key: 'child-session-1' },
|
|
226
|
+
{ kind: 'file', key: 'parent-session-1::preview::README.md' },
|
|
227
|
+
{ kind: 'cron' },
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
manager.goBackWorkspacePanel();
|
|
231
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
232
|
+
activeWorkspacePanelKind: 'file',
|
|
233
|
+
activeWorkspaceFileKey: 'parent-session-1::preview::README.md',
|
|
234
|
+
workspaceNavigationHistoryIndex: 1,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
manager.goBackWorkspacePanel();
|
|
238
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
239
|
+
activeWorkspacePanelKind: 'child-session',
|
|
240
|
+
activeChildSessionKey: 'child-session-1',
|
|
241
|
+
workspaceNavigationHistoryIndex: 0,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
manager.goForwardWorkspacePanel();
|
|
245
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
246
|
+
activeWorkspacePanelKind: 'file',
|
|
247
|
+
activeWorkspaceFileKey: 'parent-session-1::preview::README.md',
|
|
248
|
+
workspaceNavigationHistoryIndex: 1,
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('truncates workspace forward history after selecting a new entry', () => {
|
|
253
|
+
useChatThreadStore.getState().setSnapshot({
|
|
254
|
+
childSessionTabs: [
|
|
255
|
+
...useChatThreadStore.getState().snapshot.childSessionTabs,
|
|
256
|
+
{
|
|
257
|
+
sessionKey: 'child-session-2',
|
|
258
|
+
parentSessionKey: 'parent-session-1',
|
|
259
|
+
label: 'Child Session 2',
|
|
260
|
+
agentId: 'writer',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
});
|
|
264
|
+
const manager = new NcpChatThreadManager(
|
|
265
|
+
{ goToSession: vi.fn() } as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0],
|
|
266
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
267
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
manager.openChildSessionPanel({
|
|
271
|
+
parentSessionKey: 'parent-session-1',
|
|
272
|
+
activeChildSessionKey: 'child-session-1',
|
|
273
|
+
});
|
|
274
|
+
manager.openFilePreview({
|
|
275
|
+
path: 'README.md',
|
|
276
|
+
label: 'README.md',
|
|
277
|
+
viewMode: 'preview',
|
|
278
|
+
});
|
|
279
|
+
manager.goBackWorkspacePanel();
|
|
280
|
+
manager.selectChildSessionDetail('child-session-2');
|
|
281
|
+
|
|
282
|
+
expect(useChatThreadStore.getState().snapshot.workspaceNavigationHistory).toEqual([
|
|
283
|
+
{ kind: 'child-session', key: 'child-session-1' },
|
|
284
|
+
{ kind: 'child-session', key: 'child-session-2' },
|
|
285
|
+
]);
|
|
286
|
+
expect(useChatThreadStore.getState().snapshot.workspaceNavigationHistoryIndex).toBe(1);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('removes a closed active file from workspace history and restores the previous entry', () => {
|
|
290
|
+
const manager = new NcpChatThreadManager(
|
|
291
|
+
{ goToSession: vi.fn() } as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0],
|
|
292
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
293
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
manager.openChildSessionPanel({
|
|
297
|
+
parentSessionKey: 'parent-session-1',
|
|
298
|
+
activeChildSessionKey: 'child-session-1',
|
|
299
|
+
});
|
|
300
|
+
manager.openFilePreview({
|
|
301
|
+
path: 'README.md',
|
|
302
|
+
label: 'README.md',
|
|
303
|
+
viewMode: 'preview',
|
|
304
|
+
});
|
|
305
|
+
manager.closeWorkspaceFile('parent-session-1::preview::README.md');
|
|
306
|
+
|
|
307
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
308
|
+
activeWorkspacePanelKind: 'child-session',
|
|
309
|
+
activeChildSessionKey: 'child-session-1',
|
|
310
|
+
activeWorkspaceFileKey: null,
|
|
311
|
+
workspaceNavigationHistory: [
|
|
312
|
+
{ kind: 'child-session', key: 'child-session-1' },
|
|
313
|
+
],
|
|
314
|
+
workspaceNavigationHistoryIndex: 0,
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
193
318
|
|
|
319
|
+
describe('NcpChatThreadManager deletion', () => {
|
|
194
320
|
it('clears the selected thread state after deleting the current session', async () => {
|
|
195
321
|
const removeQueries = vi.spyOn(appQueryClient, 'removeQueries').mockImplementation(async () => undefined);
|
|
196
322
|
const uiManager = {
|
|
@@ -228,6 +354,8 @@ describe('NcpChatThreadManager', () => {
|
|
|
228
354
|
activeChildSessionKey: null,
|
|
229
355
|
workspaceFileTabs: [],
|
|
230
356
|
activeWorkspaceFileKey: null,
|
|
357
|
+
workspaceNavigationHistory: [],
|
|
358
|
+
workspaceNavigationHistoryIndex: 0,
|
|
231
359
|
});
|
|
232
360
|
expect(streamActionsManager.resetStreamState).toHaveBeenCalledTimes(1);
|
|
233
361
|
expect(deleteSummaryMock).toHaveBeenCalledWith(appQueryClient, 'parent-session-1');
|