@nextclaw/ui 0.12.8 → 0.12.9
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 +35 -0
- package/dist/assets/ChannelsList-Ita2Zm1_.js +8 -0
- package/dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-6ReNjvzF.js} +1 -1
- package/dist/assets/DocBrowser-BNwbPHf4.js +1 -0
- package/dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-B6SpA7Qs.js} +1 -1
- package/dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-ByNLYg65.js} +1 -1
- package/dist/assets/MarketplacePage-CjX2MWww.js +1 -0
- package/dist/assets/{MarketplacePage-BySqkYDh.js → MarketplacePage-D0sDlYX4.js} +1 -1
- package/dist/assets/McpMarketplacePage-BGKJm1sJ.js +40 -0
- package/dist/assets/{ModelConfig-IrmzoslW.js → ModelConfig-BzZenCH-.js} +1 -1
- package/dist/assets/{ProviderScopedModelInput-CmTIzgI7.js → ProviderScopedModelInput-Da7khnBA.js} +1 -1
- package/dist/assets/{ProvidersList-8_Kalfwl.js → ProvidersList-BbVzRxjY.js} +1 -1
- package/dist/assets/RemoteAccessPage-BaDH_X1Q.js +1 -0
- package/dist/assets/RuntimeConfig-F_XKGgLm.js +1 -0
- package/dist/assets/{SearchConfig-DNBR-UbE.js → SearchConfig-BGkzXQP-.js} +1 -1
- package/dist/assets/{SecretsConfig-Ba1RPJaG.js → SecretsConfig-D281Rotl.js} +2 -2
- package/dist/assets/{SessionsConfig-Doqp5ghH.js → SessionsConfig-ChHQ7M5c.js} +2 -2
- package/dist/assets/{app-query-client-DniXoIN5.js → app-query-client-VnFElj4E.js} +1 -1
- package/dist/assets/{book-open-DocgeQtR.js → book-open-BdcxxoQu.js} +1 -1
- package/dist/assets/chat-page-Doe0yTtB.js +58 -0
- package/dist/assets/chat-session-display-cw78aiI_.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-BvKvh1R8.js → chunk-JZWAC4HX-DK5HPmIK.js} +1 -1
- package/dist/assets/{client-CVqPF5ie.js → client-_i4MU2bB.js} +1 -1
- package/dist/assets/{config-Bop2oB18.js → config-DtIQwrHF.js} +1 -1
- package/dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-BSeTgkZW.js} +1 -1
- package/dist/assets/desktop-update-config-Dpcf4BKG.js +1 -0
- package/dist/assets/{dist-Da5Gm_pO.js → dist-6TrrnPCR.js} +1 -1
- package/dist/assets/{dist-DmAlInRu.js → dist-ccBFUi-o.js} +1 -1
- package/dist/assets/download-BhDxnyvU.js +1 -0
- package/dist/assets/{external-link-DFjw3x1B.js → external-link-BgErLCNT.js} +1 -1
- package/dist/assets/{hash-DJtaCejM.js → hash-Bl7dr_UG.js} +1 -1
- package/dist/assets/i18n-eDHeDY0n.js +1 -0
- package/dist/assets/index-CF9xve0E.js +6 -0
- package/dist/assets/index-FgA52VBt.css +1 -0
- package/dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-ZDS92Qpp.js} +1 -1
- package/dist/assets/loader-circle-ACM1s51e.js +1 -0
- package/dist/assets/{logos-DEFUIR12.js → logos-x89HbrZ4.js} +1 -1
- package/dist/assets/{page-layout-Da3i3r6G.js → page-layout-vZnghcFy.js} +1 -1
- package/dist/assets/play-CFUwCA2E.js +1 -0
- package/dist/assets/plus-rYsv72JG.js +1 -0
- package/dist/assets/{popover-C_mWOFzI.js → popover-Bg1VoTZ6.js} +1 -1
- package/dist/assets/{refresh-ccw-D6HkNtfz.js → refresh-ccw-DT98i__E.js} +1 -1
- package/dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-C47QSEwg.js} +1 -1
- package/dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-JtFzpNn6.js} +1 -1
- package/dist/assets/{save-DHGmi2e9.js → save-3S6-H3Xw.js} +1 -1
- package/dist/assets/search-3kFR_zh9.js +1 -0
- package/dist/assets/{security-config-CbXfPZzr.js → security-config-BWaiARNk.js} +1 -1
- package/dist/assets/{select-Caud8QvU.js → select-DJ2MUjBB.js} +1 -1
- package/dist/assets/skeleton-ByQepn0M.js +1 -0
- package/dist/assets/{status-dot-DurKKSwA.js → status-dot-vbanNPFU.js} +1 -1
- package/dist/assets/{switch-0rmPBRKI.js → switch-BsLtHOH-.js} +1 -1
- package/dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-D3HYMt6k.js} +1 -1
- package/dist/assets/{trash-2-C6caKPoz.js → trash-2-G48scll7.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DkNhD-42.js} +1 -1
- package/dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BkvTN-vd.js} +1 -1
- package/dist/assets/{useMutation-BmxxvCNf.js → useMutation-CBWjE2uj.js} +1 -1
- package/dist/assets/x-ByDbItbq.js +1 -0
- package/dist/index.html +95 -21
- package/dist/manifest.webmanifest +30 -0
- package/dist/offline.html +102 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/sw.js +80 -0
- package/index.html +73 -1
- package/package.json +6 -6
- package/public/manifest.webmanifest +30 -0
- package/public/offline.html +102 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/sw.js +80 -0
- package/src/api/server-path.ts +27 -4
- package/src/api/types.ts +17 -10
- package/src/app.tsx +9 -0
- package/src/components/chat/ChatSidebar.test.tsx +43 -1
- package/src/components/chat/ChatSidebar.tsx +24 -0
- package/src/components/chat/adapters/chat-message.summary-truncation.test.ts +66 -0
- package/src/components/chat/adapters/file-operation/card.ts +9 -0
- package/src/components/chat/adapters/file-operation/diff.ts +14 -0
- package/src/components/chat/{ChatConversationPanel.test.tsx → chat-conversation-panel.test.tsx} +107 -206
- package/src/components/chat/chat-conversation-panel.tsx +412 -0
- package/src/components/chat/chat-page-shell.tsx +1 -1
- package/src/components/chat/chat-session-workspace-file-preview.test.tsx +91 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +307 -0
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +197 -0
- package/src/components/chat/chat-session-workspace-panel.tsx +318 -0
- package/src/components/chat/chat-sidebar-session-item.tsx +32 -2
- package/src/components/chat/containers/chat-message-list.container.test.tsx +49 -0
- package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
- package/src/components/chat/managers/chat-session-list.manager.test.ts +12 -0
- package/src/components/chat/managers/chat-session-list.manager.ts +7 -0
- package/src/components/chat/ncp/ncp-chat-page.tsx +7 -7
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +179 -41
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +35 -1
- package/src/components/chat/ncp/ncp-session-adapter.ts +17 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +54 -11
- package/src/components/chat/ncp/tests/ncp-chat-thread.manager.test.ts +189 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +13 -2
- package/src/components/chat/session-header/chat-session-header-actions.test.tsx +26 -0
- package/src/components/chat/session-header/chat-session-header-actions.tsx +19 -1
- package/src/components/chat/stores/chat-thread.store.ts +24 -0
- package/src/components/config/RuntimeConfig.tsx +141 -2
- package/src/components/layout/AppLayout.tsx +1 -1
- package/src/components/providers/ThemeProvider.tsx +5 -0
- package/src/hooks/server-path/use-server-path-read.ts +20 -0
- package/src/lib/chat-message.ts +14 -3
- package/src/lib/i18n.chat.ts +12 -1
- package/src/lib/i18n.pwa.ts +62 -0
- package/src/lib/i18n.ts +2 -2
- package/src/pwa/components/pwa-install-entry.test.tsx +110 -0
- package/src/pwa/components/pwa-install-entry.tsx +205 -0
- package/src/pwa/managers/pwa-install.manager.test.ts +160 -0
- package/src/pwa/managers/pwa-install.manager.ts +232 -0
- package/src/pwa/managers/pwa-runtime.manager.ts +196 -0
- package/src/pwa/managers/pwa-shell-theme.manager.test.ts +30 -0
- package/src/pwa/managers/pwa-shell-theme.manager.ts +46 -0
- package/src/pwa/pwa-install-banner.storage.ts +55 -0
- package/src/pwa/pwa.types.ts +22 -0
- package/src/pwa/register-pwa.ts +14 -0
- package/src/pwa/stores/pwa.store.ts +17 -0
- package/src/vite-env.d.ts +9 -0
- package/dist/assets/ChannelsList-KIQIxluX.js +0 -8
- package/dist/assets/DocBrowser-CyDgAtO9.js +0 -1
- package/dist/assets/MarketplacePage-C0olZaek.js +0 -1
- package/dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
- package/dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
- package/dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
- package/dist/assets/chat-page-Bph8M5zo.js +0 -58
- package/dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
- package/dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
- package/dist/assets/i18n-CwHZ-9vt.js +0 -1
- package/dist/assets/index-DafCdM4F.css +0 -1
- package/dist/assets/index-DdksE6U3.js +0 -6
- package/dist/assets/loader-circle-PsSP0H9n.js +0 -1
- package/dist/assets/play-DBQbBxTA.js +0 -1
- package/dist/assets/plus-DUOVbsyQ.js +0 -1
- package/dist/assets/search-MChQRYR1.js +0 -1
- package/dist/assets/skeleton-B-4vRq_Z.js +0 -1
- package/dist/assets/x-DuMhMATD.js +0 -1
- package/src/components/chat/ChatConversationPanel.tsx +0 -256
- package/src/components/chat/chat-child-session-panel.tsx +0 -270
- /package/dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
- /package/dist/assets/{config-layout-DmlGaay2.js → config-layout-CHs0mAaR.js} +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { appQueryClient } from '@/app-query-client';
|
|
2
2
|
import { deleteNcpSessionSummaryInQueryClient } from '@/api/ncp-session-query-cache';
|
|
3
3
|
import { deleteNcpSession as deleteNcpSessionApi } from '@/api/ncp-session';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ChatFileOpenActionViewModel,
|
|
6
|
+
ChatToolActionViewModel,
|
|
7
|
+
} from '@nextclaw/agent-chat-ui';
|
|
5
8
|
import type { ChatSessionListManager } from '@/components/chat/managers/chat-session-list.manager';
|
|
6
9
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
7
10
|
import type { ChatUiManager } from '@/components/chat/managers/chat-ui.manager';
|
|
8
11
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
9
12
|
import type {
|
|
10
|
-
ChatChildSessionTab,
|
|
11
13
|
ChatThreadSnapshot,
|
|
14
|
+
ChatWorkspaceFileTab,
|
|
12
15
|
} from '@/components/chat/stores/chat-thread.store';
|
|
13
16
|
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
14
17
|
import { t } from '@/lib/i18n';
|
|
@@ -37,6 +40,98 @@ export class NcpChatThreadManager {
|
|
|
37
40
|
useChatThreadStore.getState().setSnapshot(patch);
|
|
38
41
|
};
|
|
39
42
|
|
|
43
|
+
private clearDeletedSessionState = (sessionKey: string) => {
|
|
44
|
+
if (useChatSessionListStore.getState().snapshot.selectedSessionKey === sessionKey) {
|
|
45
|
+
this.sessionListManager.setSelectedSessionKey(null);
|
|
46
|
+
}
|
|
47
|
+
useChatThreadStore.getState().setSnapshot({
|
|
48
|
+
sessionKey: null,
|
|
49
|
+
sessionTypeLabel: null,
|
|
50
|
+
agentId: null,
|
|
51
|
+
agentDisplayName: null,
|
|
52
|
+
agentAvatarUrl: null,
|
|
53
|
+
sessionDisplayName: undefined,
|
|
54
|
+
sessionProjectRoot: null,
|
|
55
|
+
sessionProjectName: null,
|
|
56
|
+
canDeleteSession: false,
|
|
57
|
+
isHistoryLoading: false,
|
|
58
|
+
messages: [],
|
|
59
|
+
isSending: false,
|
|
60
|
+
isAwaitingAssistantOutput: false,
|
|
61
|
+
parentSessionKey: null,
|
|
62
|
+
parentSessionLabel: null,
|
|
63
|
+
workspacePanelParentKey: null,
|
|
64
|
+
childSessionTabs: [],
|
|
65
|
+
activeChildSessionKey: null,
|
|
66
|
+
workspaceFileTabs: [],
|
|
67
|
+
activeWorkspaceFileKey: null,
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
private resolveWorkspaceParentSessionKey = (): string | null => {
|
|
72
|
+
const threadSessionKey = useChatThreadStore.getState().snapshot.sessionKey?.trim();
|
|
73
|
+
if (threadSessionKey) {
|
|
74
|
+
return threadSessionKey;
|
|
75
|
+
}
|
|
76
|
+
return useChatSessionListStore.getState().snapshot.selectedSessionKey ?? null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
private buildWorkspaceFileTab = (
|
|
80
|
+
action: ChatFileOpenActionViewModel,
|
|
81
|
+
parentSessionKey: string | null,
|
|
82
|
+
): ChatWorkspaceFileTab | null => {
|
|
83
|
+
const normalizedPath = action.path.trim();
|
|
84
|
+
if (!normalizedPath) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const normalizedParentSessionKey = parentSessionKey?.trim() || null;
|
|
88
|
+
const key =
|
|
89
|
+
`${normalizedParentSessionKey ?? 'draft'}::${action.viewMode}::${normalizedPath}`;
|
|
90
|
+
return {
|
|
91
|
+
key,
|
|
92
|
+
parentSessionKey: normalizedParentSessionKey,
|
|
93
|
+
path: normalizedPath,
|
|
94
|
+
label: action.label?.trim() || null,
|
|
95
|
+
viewMode: action.viewMode,
|
|
96
|
+
line: action.line ?? null,
|
|
97
|
+
column: action.column ?? null,
|
|
98
|
+
rawText: action.rawText ?? null,
|
|
99
|
+
beforeText: action.beforeText ?? null,
|
|
100
|
+
afterText: action.afterText ?? null,
|
|
101
|
+
patchText: action.patchText ?? null,
|
|
102
|
+
oldStartLine: action.oldStartLine ?? null,
|
|
103
|
+
newStartLine: action.newStartLine ?? null,
|
|
104
|
+
fullLines: action.fullLines,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
private upsertWorkspaceFileTab = (nextTab: ChatWorkspaceFileTab): ChatWorkspaceFileTab[] => {
|
|
109
|
+
const { workspaceFileTabs } = useChatThreadStore.getState().snapshot;
|
|
110
|
+
const existingIndex = workspaceFileTabs.findIndex((tab) => tab.key === nextTab.key);
|
|
111
|
+
if (existingIndex === -1) {
|
|
112
|
+
return [nextTab, ...workspaceFileTabs];
|
|
113
|
+
}
|
|
114
|
+
const nextTabs = [...workspaceFileTabs];
|
|
115
|
+
nextTabs.splice(existingIndex, 1);
|
|
116
|
+
nextTabs.unshift({
|
|
117
|
+
...workspaceFileTabs[existingIndex],
|
|
118
|
+
...nextTab,
|
|
119
|
+
});
|
|
120
|
+
return nextTabs;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
private ensureWorkspaceParentRoute = (parentSessionKey: string | null) => {
|
|
124
|
+
if (!parentSessionKey) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const {
|
|
128
|
+
snapshot: { selectedSessionKey },
|
|
129
|
+
} = useChatSessionListStore.getState();
|
|
130
|
+
if (selectedSessionKey !== parentSessionKey) {
|
|
131
|
+
this.uiManager.goToSession(parentSessionKey);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
40
135
|
deleteSession = () => {
|
|
41
136
|
void this.deleteCurrentSession();
|
|
42
137
|
};
|
|
@@ -49,21 +144,36 @@ export class NcpChatThreadManager {
|
|
|
49
144
|
this.uiManager.goToProviders();
|
|
50
145
|
};
|
|
51
146
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
147
|
+
openChildSessionPanel = (params: {
|
|
148
|
+
parentSessionKey: string;
|
|
149
|
+
activeChildSessionKey?: string | null;
|
|
150
|
+
}) => {
|
|
151
|
+
const parentSessionKey = params.parentSessionKey.trim();
|
|
152
|
+
if (!parentSessionKey) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const activeChildSessionKey = params.activeChildSessionKey?.trim() || null;
|
|
156
|
+
useChatThreadStore.getState().setSnapshot({
|
|
157
|
+
workspacePanelParentKey: parentSessionKey,
|
|
158
|
+
activeChildSessionKey,
|
|
159
|
+
activeWorkspaceFileKey: null,
|
|
160
|
+
});
|
|
161
|
+
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
openFilePreview = (action: ChatFileOpenActionViewModel) => {
|
|
165
|
+
const parentSessionKey = this.resolveWorkspaceParentSessionKey();
|
|
166
|
+
const nextTab = this.buildWorkspaceFileTab(action, parentSessionKey);
|
|
167
|
+
if (!nextTab) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
63
170
|
useChatThreadStore.getState().setSnapshot({
|
|
64
|
-
|
|
65
|
-
|
|
171
|
+
workspacePanelParentKey: parentSessionKey,
|
|
172
|
+
workspaceFileTabs: this.upsertWorkspaceFileTab(nextTab),
|
|
173
|
+
activeWorkspaceFileKey: nextTab.key,
|
|
174
|
+
activeChildSessionKey: null,
|
|
66
175
|
});
|
|
176
|
+
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
67
177
|
};
|
|
68
178
|
|
|
69
179
|
openSessionFromToolAction = (action: ChatToolActionViewModel) => {
|
|
@@ -75,14 +185,19 @@ export class NcpChatThreadManager {
|
|
|
75
185
|
action.parentSessionId?.trim() ||
|
|
76
186
|
useChatSessionListStore.getState().snapshot.selectedSessionKey ||
|
|
77
187
|
null;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
188
|
+
if (parentSessionKey) {
|
|
189
|
+
this.openChildSessionPanel({
|
|
190
|
+
parentSessionKey,
|
|
191
|
+
activeChildSessionKey: action.sessionId,
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
85
195
|
}
|
|
196
|
+
useChatThreadStore.getState().setSnapshot({
|
|
197
|
+
workspacePanelParentKey: null,
|
|
198
|
+
activeChildSessionKey: null,
|
|
199
|
+
activeWorkspaceFileKey: null,
|
|
200
|
+
});
|
|
86
201
|
this.uiManager.goToSession(action.sessionId);
|
|
87
202
|
};
|
|
88
203
|
|
|
@@ -97,34 +212,56 @@ export class NcpChatThreadManager {
|
|
|
97
212
|
}
|
|
98
213
|
useChatThreadStore.getState().setSnapshot({
|
|
99
214
|
activeChildSessionKey: normalizedSessionKey,
|
|
215
|
+
activeWorkspaceFileKey: null,
|
|
100
216
|
});
|
|
101
217
|
};
|
|
102
218
|
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
childSessionTabs,
|
|
107
|
-
activeChildSessionKey,
|
|
108
|
-
} = useChatThreadStore.getState().snapshot;
|
|
109
|
-
if (!sessionKey) {
|
|
110
|
-
useChatThreadStore.getState().setSnapshot({
|
|
111
|
-
childSessionTabs: [],
|
|
112
|
-
activeChildSessionKey: null,
|
|
113
|
-
});
|
|
219
|
+
selectWorkspaceFile = (fileKey: string) => {
|
|
220
|
+
const normalizedFileKey = fileKey.trim();
|
|
221
|
+
if (!normalizedFileKey) {
|
|
114
222
|
return;
|
|
115
223
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
224
|
+
const { workspaceFileTabs } = useChatThreadStore.getState().snapshot;
|
|
225
|
+
if (!workspaceFileTabs.some((tab) => tab.key === normalizedFileKey)) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
useChatThreadStore.getState().setSnapshot({
|
|
229
|
+
activeWorkspaceFileKey: normalizedFileKey,
|
|
230
|
+
activeChildSessionKey: null,
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
closeWorkspaceFile = (fileKey: string) => {
|
|
235
|
+
const normalizedFileKey = fileKey.trim();
|
|
236
|
+
if (!normalizedFileKey) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const { snapshot } = useChatThreadStore.getState();
|
|
240
|
+
const { activeWorkspaceFileKey, workspaceFileTabs } = snapshot;
|
|
241
|
+
const nextTabs = workspaceFileTabs.filter(
|
|
242
|
+
(tab) => tab.key !== normalizedFileKey,
|
|
118
243
|
);
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
244
|
+
const nextPatch: Partial<ChatThreadSnapshot> = {
|
|
245
|
+
workspaceFileTabs: nextTabs,
|
|
246
|
+
};
|
|
247
|
+
if (activeWorkspaceFileKey === normalizedFileKey) {
|
|
248
|
+
nextPatch.activeWorkspaceFileKey = null;
|
|
249
|
+
}
|
|
250
|
+
useChatThreadStore.getState().setSnapshot(nextPatch);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
closeWorkspacePanel = () => {
|
|
122
254
|
useChatThreadStore.getState().setSnapshot({
|
|
123
|
-
|
|
124
|
-
activeChildSessionKey:
|
|
255
|
+
workspacePanelParentKey: null,
|
|
256
|
+
activeChildSessionKey: null,
|
|
257
|
+
activeWorkspaceFileKey: null,
|
|
125
258
|
});
|
|
126
259
|
};
|
|
127
260
|
|
|
261
|
+
closeChildSessionDetail = () => {
|
|
262
|
+
this.closeWorkspacePanel();
|
|
263
|
+
};
|
|
264
|
+
|
|
128
265
|
goToParentSession = () => {
|
|
129
266
|
const {
|
|
130
267
|
parentSessionKey,
|
|
@@ -139,7 +276,7 @@ export class NcpChatThreadManager {
|
|
|
139
276
|
if (!resolvedParentSessionKey) {
|
|
140
277
|
return;
|
|
141
278
|
}
|
|
142
|
-
this.
|
|
279
|
+
this.closeWorkspacePanel();
|
|
143
280
|
this.uiManager.goToSession(resolvedParentSessionKey);
|
|
144
281
|
};
|
|
145
282
|
|
|
@@ -171,6 +308,7 @@ export class NcpChatThreadManager {
|
|
|
171
308
|
deleteNcpSessionSummaryInQueryClient(appQueryClient, selectedSessionKey);
|
|
172
309
|
appQueryClient.removeQueries({ queryKey: ['ncp-session-messages', selectedSessionKey] });
|
|
173
310
|
this.streamActionsManager.resetStreamState();
|
|
311
|
+
this.clearDeletedSessionState(selectedSessionKey);
|
|
174
312
|
this.uiManager.goToChatRoot({ replace: true });
|
|
175
313
|
} finally {
|
|
176
314
|
useChatThreadStore.getState().setSnapshot({ isDeletePending: false });
|
|
@@ -71,7 +71,7 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
71
71
|
});
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
describe('adaptNcpMessageToUiMessage', () => {
|
|
74
|
+
describe('adaptNcpMessageToUiMessage file rendering', () => {
|
|
75
75
|
it('preserves mixed text and image part order for message rendering', () => {
|
|
76
76
|
const adapted = adaptNcpMessageToUiMessage({
|
|
77
77
|
id: 'ncp-message-1',
|
|
@@ -111,6 +111,40 @@ describe('adaptNcpMessageToUiMessage', () => {
|
|
|
111
111
|
]);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
it('maps assetUri file parts into asset content urls for rendering', () => {
|
|
115
|
+
const adapted = adaptNcpMessageToUiMessage({
|
|
116
|
+
id: 'ncp-message-asset-1',
|
|
117
|
+
sessionId: 'ncp-session-1',
|
|
118
|
+
role: 'assistant',
|
|
119
|
+
status: 'final',
|
|
120
|
+
timestamp: '2026-04-16T00:00:00.000Z',
|
|
121
|
+
parts: [
|
|
122
|
+
{
|
|
123
|
+
type: 'file',
|
|
124
|
+
name: 'diagram.png',
|
|
125
|
+
mimeType: 'image/png',
|
|
126
|
+
assetUri: 'asset://store/2026/04/16/asset_123',
|
|
127
|
+
sizeBytes: 42,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(adapted.parts).toHaveLength(1);
|
|
133
|
+
expect(adapted.parts[0]).toMatchObject({
|
|
134
|
+
type: 'file',
|
|
135
|
+
name: 'diagram.png',
|
|
136
|
+
mimeType: 'image/png',
|
|
137
|
+
data: '',
|
|
138
|
+
sizeBytes: 42,
|
|
139
|
+
});
|
|
140
|
+
expect((adapted.parts[0] as { url?: string }).url).toMatch(
|
|
141
|
+
/\/api\/ncp\/assets\/content\?uri=asset%3A%2F%2Fstore%2F2026%2F04%2F16%2Fasset_123$/,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('adaptNcpMessageToUiMessage tool rendering', () => {
|
|
114
148
|
it('keeps streamed native file tool args renderable as a preview before the tool result arrives', () => {
|
|
115
149
|
const uiMessage = adaptNcpMessageToUiMessage({
|
|
116
150
|
id: 'ncp-message-tool-1',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ToolInvocationStatus, type UIMessage } from '@nextclaw/agent-chat';
|
|
2
2
|
import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
3
3
|
import type { NcpMessageView, NcpSessionSummaryView, SessionEntryView, ThinkingLevel } from '@/api/types';
|
|
4
|
+
import { API_BASE } from '@/api/api-base';
|
|
4
5
|
import {
|
|
5
6
|
getSessionProjectName,
|
|
6
7
|
normalizeSessionProjectRootValue,
|
|
@@ -19,6 +20,11 @@ function stringifyUnknown(value: unknown): string {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function buildNcpAssetContentUrl(assetUri: string): string {
|
|
24
|
+
const query = new URLSearchParams({ uri: assetUri });
|
|
25
|
+
return `${API_BASE}/api/ncp/assets/content?${query.toString()}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
function readOptionalString(value: unknown): string | null {
|
|
23
29
|
if (typeof value !== 'string') {
|
|
24
30
|
return null;
|
|
@@ -207,6 +213,17 @@ function toUiParts(parts: NcpMessagePart[]): UIMessage['parts'] {
|
|
|
207
213
|
});
|
|
208
214
|
continue;
|
|
209
215
|
}
|
|
216
|
+
if (part.type === 'file' && part.assetUri) {
|
|
217
|
+
uiParts.push({
|
|
218
|
+
type: 'file',
|
|
219
|
+
...(part.name ? { name: part.name } : {}),
|
|
220
|
+
mimeType: part.mimeType ?? 'application/octet-stream',
|
|
221
|
+
data: '',
|
|
222
|
+
url: buildNcpAssetContentUrl(part.assetUri),
|
|
223
|
+
...(typeof part.sizeBytes === 'number' ? { sizeBytes: part.sizeBytes } : {})
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
210
227
|
if (part.type === 'step-start') {
|
|
211
228
|
uiParts.push({ type: 'step-start' });
|
|
212
229
|
continue;
|
|
@@ -10,8 +10,30 @@ import { adaptNcpSessionSummary } from '@/components/chat/ncp/ncp-session-adapte
|
|
|
10
10
|
import type { NcpChatPresenter } from '@/components/chat/ncp/ncp-chat.presenter';
|
|
11
11
|
import type { UseHydratedNcpAgentResult } from '@nextclaw/ncp-react';
|
|
12
12
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
13
|
+
import type { ChatChildSessionTab } from '@/components/chat/stores/chat-thread.store';
|
|
13
14
|
import { resolveSessionTypeLabel } from '@/components/chat/useChatSessionTypeState';
|
|
15
|
+
|
|
16
|
+
function buildChildSessionTabs(params: {
|
|
17
|
+
parentSessionKey: string | null;
|
|
18
|
+
sessionSummaries: NcpSessionSummaryView[];
|
|
19
|
+
}): ChatChildSessionTab[] {
|
|
20
|
+
if (!params.parentSessionKey) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return params.sessionSummaries
|
|
24
|
+
.map(adaptNcpSessionSummary)
|
|
25
|
+
.filter((session) => session.parentSessionId === params.parentSessionKey)
|
|
26
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
27
|
+
.map((session) => ({
|
|
28
|
+
sessionKey: session.key,
|
|
29
|
+
parentSessionKey: session.parentSessionId ?? null,
|
|
30
|
+
label: session.label ?? null,
|
|
31
|
+
agentId: session.agentId ?? null,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
export function useNcpChatDerivedState(params: {
|
|
36
|
+
sessionKey: string | null;
|
|
15
37
|
selectedSession: SessionEntryView | null;
|
|
16
38
|
selectedAgentId: string;
|
|
17
39
|
availableAgents: AgentProfileView[];
|
|
@@ -20,32 +42,51 @@ export function useNcpChatDerivedState(params: {
|
|
|
20
42
|
selectedSessionType: string;
|
|
21
43
|
sessionTypeOptions: Array<{ value: string; label: string }>;
|
|
22
44
|
}) {
|
|
23
|
-
const
|
|
24
|
-
|
|
45
|
+
const {
|
|
46
|
+
availableAgents,
|
|
47
|
+
parentSessionId,
|
|
48
|
+
selectedAgentId,
|
|
49
|
+
selectedSession,
|
|
50
|
+
selectedSessionType,
|
|
51
|
+
sessionKey,
|
|
52
|
+
sessionSummaries,
|
|
53
|
+
sessionTypeOptions,
|
|
54
|
+
} = params;
|
|
55
|
+
const currentSessionDisplayName = selectedSession
|
|
56
|
+
? sessionDisplayName(selectedSession)
|
|
25
57
|
: undefined;
|
|
26
|
-
const currentAgentId =
|
|
58
|
+
const currentAgentId = selectedSession?.agentId ?? selectedAgentId;
|
|
27
59
|
const currentAgent =
|
|
28
|
-
|
|
60
|
+
availableAgents.find((agent) => agent.id === currentAgentId) ?? null;
|
|
29
61
|
const parentSession = useMemo(() => {
|
|
30
|
-
if (!
|
|
62
|
+
if (!parentSessionId) {
|
|
31
63
|
return null;
|
|
32
64
|
}
|
|
33
65
|
const parentSummary =
|
|
34
|
-
|
|
35
|
-
(summary) => summary.sessionId ===
|
|
66
|
+
sessionSummaries.find(
|
|
67
|
+
(summary) => summary.sessionId === parentSessionId,
|
|
36
68
|
) ?? null;
|
|
37
69
|
return parentSummary ? adaptNcpSessionSummary(parentSummary) : null;
|
|
38
|
-
}, [
|
|
70
|
+
}, [parentSessionId, sessionSummaries]);
|
|
39
71
|
const currentSessionTypeLabel =
|
|
40
|
-
|
|
41
|
-
?.label ?? resolveSessionTypeLabel(
|
|
72
|
+
sessionTypeOptions.find((option) => option.value === selectedSessionType)
|
|
73
|
+
?.label ?? resolveSessionTypeLabel(selectedSessionType);
|
|
74
|
+
const currentChildSessionTabs = useMemo(
|
|
75
|
+
() =>
|
|
76
|
+
buildChildSessionTabs({
|
|
77
|
+
parentSessionKey: sessionKey,
|
|
78
|
+
sessionSummaries,
|
|
79
|
+
}),
|
|
80
|
+
[sessionKey, sessionSummaries],
|
|
81
|
+
);
|
|
42
82
|
|
|
43
83
|
return {
|
|
44
84
|
currentSessionDisplayName,
|
|
45
85
|
currentAgentId,
|
|
46
86
|
currentAgent,
|
|
47
87
|
parentSession,
|
|
48
|
-
currentSessionTypeLabel
|
|
88
|
+
currentSessionTypeLabel,
|
|
89
|
+
currentChildSessionTabs,
|
|
49
90
|
};
|
|
50
91
|
}
|
|
51
92
|
|
|
@@ -78,6 +119,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
78
119
|
agent: Pick<UseHydratedNcpAgentResult, 'isHydrating' | 'visibleMessages'>;
|
|
79
120
|
isAwaitingAssistantOutput: boolean;
|
|
80
121
|
parentSession: SessionEntryView | null;
|
|
122
|
+
childSessionTabs: ChatChildSessionTab[];
|
|
81
123
|
}) {
|
|
82
124
|
useEffect(() => {
|
|
83
125
|
params.presenter.chatInputManager.syncSnapshot({
|
|
@@ -121,6 +163,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
121
163
|
parentSessionLabel: params.parentSession
|
|
122
164
|
? sessionDisplayName(params.parentSession)
|
|
123
165
|
: null,
|
|
166
|
+
childSessionTabs: params.childSessionTabs,
|
|
124
167
|
});
|
|
125
168
|
}, [
|
|
126
169
|
params
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { appQueryClient } from '@/app-query-client';
|
|
3
|
+
import { NcpChatThreadManager } from '@/components/chat/ncp/ncp-chat-thread.manager';
|
|
4
|
+
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
5
|
+
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
6
|
+
|
|
7
|
+
const { deleteNcpSessionMock, deleteSummaryMock } = vi.hoisted(() => ({
|
|
8
|
+
deleteNcpSessionMock: vi.fn(async () => ({ deleted: true, sessionId: 'parent-session-1' })),
|
|
9
|
+
deleteSummaryMock: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock('@/api/ncp-session', () => ({
|
|
13
|
+
deleteNcpSession: deleteNcpSessionMock,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('@/api/ncp-session-query-cache', () => ({
|
|
17
|
+
deleteNcpSessionSummaryInQueryClient: deleteSummaryMock,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
describe('NcpChatThreadManager', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
useChatSessionListStore.setState({
|
|
23
|
+
optimisticReadAtBySessionKey: {},
|
|
24
|
+
snapshot: {
|
|
25
|
+
...useChatSessionListStore.getState().snapshot,
|
|
26
|
+
selectedSessionKey: 'parent-session-1',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
useChatThreadStore.setState({
|
|
30
|
+
snapshot: {
|
|
31
|
+
...useChatThreadStore.getState().snapshot,
|
|
32
|
+
sessionKey: 'parent-session-1',
|
|
33
|
+
workspacePanelParentKey: null,
|
|
34
|
+
childSessionTabs: [
|
|
35
|
+
{
|
|
36
|
+
sessionKey: 'child-session-1',
|
|
37
|
+
parentSessionKey: 'parent-session-1',
|
|
38
|
+
label: 'Child Session 1',
|
|
39
|
+
agentId: 'reviewer',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
activeChildSessionKey: null,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('opens the child-session panel for the requested parent session and keeps focus on the chosen child', () => {
|
|
48
|
+
const uiManager = {
|
|
49
|
+
goToSession: vi.fn(),
|
|
50
|
+
goToChatRoot: vi.fn(),
|
|
51
|
+
goToProviders: vi.fn(),
|
|
52
|
+
confirm: vi.fn(),
|
|
53
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
54
|
+
|
|
55
|
+
const manager = new NcpChatThreadManager(
|
|
56
|
+
uiManager,
|
|
57
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
58
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
manager.openChildSessionPanel({
|
|
62
|
+
parentSessionKey: 'parent-session-1',
|
|
63
|
+
activeChildSessionKey: 'child-session-1',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(useChatThreadStore.getState().snapshot.workspacePanelParentKey).toBe('parent-session-1');
|
|
67
|
+
expect(useChatThreadStore.getState().snapshot.activeChildSessionKey).toBe('child-session-1');
|
|
68
|
+
expect(uiManager.goToSession).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('routes to the parent session before opening the child-session panel when needed', () => {
|
|
72
|
+
useChatSessionListStore.setState({
|
|
73
|
+
snapshot: {
|
|
74
|
+
...useChatSessionListStore.getState().snapshot,
|
|
75
|
+
selectedSessionKey: 'another-session',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const uiManager = {
|
|
79
|
+
goToSession: vi.fn(),
|
|
80
|
+
goToChatRoot: vi.fn(),
|
|
81
|
+
goToProviders: vi.fn(),
|
|
82
|
+
confirm: vi.fn(),
|
|
83
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
84
|
+
|
|
85
|
+
const manager = new NcpChatThreadManager(
|
|
86
|
+
uiManager,
|
|
87
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
88
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
manager.openChildSessionPanel({
|
|
92
|
+
parentSessionKey: 'parent-session-1',
|
|
93
|
+
activeChildSessionKey: 'child-session-1',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(uiManager.goToSession).toHaveBeenCalledWith('parent-session-1');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('keeps preview and diff for the same file as separate workspace tabs', () => {
|
|
100
|
+
const uiManager = {
|
|
101
|
+
goToSession: vi.fn(),
|
|
102
|
+
goToChatRoot: vi.fn(),
|
|
103
|
+
goToProviders: vi.fn(),
|
|
104
|
+
confirm: vi.fn(),
|
|
105
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
106
|
+
|
|
107
|
+
const manager = new NcpChatThreadManager(
|
|
108
|
+
uiManager,
|
|
109
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
110
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
manager.openFilePreview({
|
|
114
|
+
path: 'README.md',
|
|
115
|
+
label: 'README.md',
|
|
116
|
+
viewMode: 'preview',
|
|
117
|
+
});
|
|
118
|
+
manager.openFilePreview({
|
|
119
|
+
path: 'README.md',
|
|
120
|
+
label: 'README.md',
|
|
121
|
+
viewMode: 'diff',
|
|
122
|
+
beforeText: 'old\n',
|
|
123
|
+
afterText: 'new\n',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(useChatThreadStore.getState().snapshot.workspaceFileTabs).toEqual(
|
|
127
|
+
expect.arrayContaining([
|
|
128
|
+
expect.objectContaining({
|
|
129
|
+
key: 'parent-session-1::preview::README.md',
|
|
130
|
+
path: 'README.md',
|
|
131
|
+
viewMode: 'preview',
|
|
132
|
+
}),
|
|
133
|
+
expect.objectContaining({
|
|
134
|
+
key: 'parent-session-1::diff::README.md',
|
|
135
|
+
path: 'README.md',
|
|
136
|
+
viewMode: 'diff',
|
|
137
|
+
}),
|
|
138
|
+
]),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('clears the selected thread state after deleting the current session', async () => {
|
|
143
|
+
const removeQueries = vi.spyOn(appQueryClient, 'removeQueries').mockImplementation(async () => undefined);
|
|
144
|
+
const uiManager = {
|
|
145
|
+
goToSession: vi.fn(),
|
|
146
|
+
goToChatRoot: vi.fn(),
|
|
147
|
+
goToProviders: vi.fn(),
|
|
148
|
+
confirm: vi.fn(async () => true),
|
|
149
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
150
|
+
const sessionListManager = {
|
|
151
|
+
setSelectedSessionKey: vi.fn((value: string | null) => {
|
|
152
|
+
useChatSessionListStore.getState().setSnapshot({
|
|
153
|
+
selectedSessionKey: value,
|
|
154
|
+
});
|
|
155
|
+
}),
|
|
156
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[1];
|
|
157
|
+
const streamActionsManager = {
|
|
158
|
+
resetStreamState: vi.fn(),
|
|
159
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[2];
|
|
160
|
+
const manager = new NcpChatThreadManager(
|
|
161
|
+
uiManager,
|
|
162
|
+
sessionListManager,
|
|
163
|
+
streamActionsManager,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
await (manager as unknown as { deleteCurrentSession: () => Promise<void> }).deleteCurrentSession();
|
|
167
|
+
|
|
168
|
+
expect(sessionListManager.setSelectedSessionKey).toHaveBeenCalledWith(null);
|
|
169
|
+
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
|
|
170
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
171
|
+
sessionKey: null,
|
|
172
|
+
canDeleteSession: false,
|
|
173
|
+
messages: [],
|
|
174
|
+
workspacePanelParentKey: null,
|
|
175
|
+
childSessionTabs: [],
|
|
176
|
+
activeChildSessionKey: null,
|
|
177
|
+
workspaceFileTabs: [],
|
|
178
|
+
activeWorkspaceFileKey: null,
|
|
179
|
+
});
|
|
180
|
+
expect(streamActionsManager.resetStreamState).toHaveBeenCalledTimes(1);
|
|
181
|
+
expect(deleteSummaryMock).toHaveBeenCalledWith(appQueryClient, 'parent-session-1');
|
|
182
|
+
expect(removeQueries).toHaveBeenCalledWith({
|
|
183
|
+
queryKey: ['ncp-session-messages', 'parent-session-1'],
|
|
184
|
+
});
|
|
185
|
+
expect(uiManager.goToChatRoot).toHaveBeenCalledWith({ replace: true });
|
|
186
|
+
|
|
187
|
+
removeQueries.mockRestore();
|
|
188
|
+
});
|
|
189
|
+
});
|