@nextclaw/ui 0.15.0 → 0.15.1
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 +19 -0
- package/dist/assets/{api-DLtsiBKm.js → api-b1R9VdeH.js} +1 -1
- package/dist/assets/{appearance-settings-page-CtkeYXux.js → appearance-settings-page-D2O9pch9.js} +1 -1
- package/dist/assets/{book-open-BQwWBDKb.js → book-open-B77lQ2Tg.js} +1 -1
- package/dist/assets/{channels-list-page-Cv8o9Hxg.js → channels-list-page-DcsIQc6A.js} +2 -2
- package/dist/assets/chat-page-CZ7WxPir.js +1 -0
- package/dist/assets/{config-split-page-EidRBpP2.js → config-split-page-BWQ6_Raq.js} +1 -1
- package/dist/assets/{confirm-dialog-iIJFFp2T.js → confirm-dialog-C-NXSioX.js} +1 -1
- package/dist/assets/desktop-update-config-usHpXugP.js +1 -0
- package/dist/assets/{dist-CBxWOazz.js → dist-YtoqU-AT.js} +1 -1
- package/dist/assets/{dist-D2eZC3OR.js → dist-jdGmbb6w.js} +1 -1
- package/dist/assets/doc-browser-B_Tf9n70.js +1 -0
- package/dist/assets/{doc-browser-B76XOXmW.js → doc-browser-BdZPFOVa.js} +1 -1
- package/dist/assets/{doc-browser-context-Sv-044Cl.js → doc-browser-context-DwokemUn.js} +1 -1
- package/dist/assets/{es2015-DhQo_oS6.js → es2015-bNpL2LKy.js} +1 -1
- package/dist/assets/{external-link-CM5dnUI6.js → external-link-BM6fk8q-.js} +1 -1
- package/dist/assets/{i18n-provider-di1waTGX.js → i18n-provider-CntWolRA.js} +1 -1
- package/dist/assets/index-BIcEw90u.js +104 -0
- package/dist/assets/index-BiNSTKTU.css +1 -0
- package/dist/assets/loader-circle-MBgNSVIH.js +1 -0
- package/dist/assets/{mcp-marketplace-page-C_MVQUbT.js → mcp-marketplace-page-CLmUstLN.js} +1 -1
- package/dist/assets/mcp-marketplace-page-rZkmWPMy.js +1 -0
- package/dist/assets/{model-config-page-Cdr9P6TQ.js → model-config-page-Di0ySJD2.js} +1 -1
- package/dist/assets/plus-vDqW5Kxp.js +1 -0
- package/dist/assets/{provider-scoped-model-input-g1WkGiJ8.js → provider-scoped-model-input-Cm_a4GHf.js} +1 -1
- package/dist/assets/providers-config-page-BAf2W0MV.js +1 -0
- package/dist/assets/{react-DjpzoZXB.js → react-BN8uzPnp.js} +1 -1
- package/dist/assets/{refresh-cw-DaTv3zxE.js → refresh-cw-BpVcFPmJ.js} +1 -1
- package/dist/assets/remote-CLQ0IBM8.js +1 -0
- package/dist/assets/rotate-cw-CvNBv7_w.js +1 -0
- package/dist/assets/{runtime-config-page-CfTqh1nv.js → runtime-config-page-CflvvoDc.js} +1 -1
- package/dist/assets/{save-BZof1uie.js → save-BpFW5fpk.js} +1 -1
- package/dist/assets/{search-MxAarS_g.js → search-D0uO7enf.js} +1 -1
- package/dist/assets/{search-config-page-Dm8daYny.js → search-config-page-D0klFUXd.js} +1 -1
- package/dist/assets/{secrets-config-page-D8AbcR05.js → secrets-config-page-dVZ631Wp.js} +1 -1
- package/dist/assets/security-config-C5gnlkuj.js +1 -0
- package/dist/assets/{select-D7GcNP0z.js → select-Bh-CrA4p.js} +1 -1
- package/dist/assets/{setting-row-CuozlnJd.js → setting-row-BJfCHRcb.js} +1 -1
- package/dist/assets/settings-2-Blzi1vr6.js +1 -0
- package/dist/assets/{side-dock-BcLau5S_.js → side-dock-ZdaSrQVB.js} +1 -1
- package/dist/assets/{skeleton-shfmXuYp.js → skeleton-BLlEzebZ.js} +1 -1
- package/dist/assets/{switch-C1BWMU5c.js → switch-CFLJFNSd.js} +1 -1
- package/dist/assets/{tag-chip-DCUrQUCP.js → tag-chip-CzGihiQx.js} +1 -1
- package/dist/assets/{tooltip-DfAHHA76.js → tooltip-CdcrBjrC.js} +1 -1
- package/dist/assets/{use-config-DAdbT5QJ.js → use-config-CVPgsBmU.js} +1 -1
- package/dist/assets/{use-confirm-dialog-eWcJ6V-s.js → use-confirm-dialog-CSeBk9SF.js} +1 -1
- package/dist/assets/x-BF2KQ--V.js +1 -0
- package/dist/index.html +26 -26
- package/package.json +9 -9
- package/src/features/agents/components/__tests__/agents-page.test.tsx +135 -2
- package/src/features/agents/components/agent-advanced-config-fields.tsx +70 -0
- package/src/features/agents/components/agent-details-dialog.tsx +398 -0
- package/src/features/agents/components/agent-dialogs.tsx +20 -47
- package/src/features/agents/components/agents-page.tsx +76 -10
- package/src/features/agents/types/agent-form.types.ts +21 -0
- package/src/features/agents/utils/agent-form.utils.ts +39 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +132 -1
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +8 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -3
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +18 -3
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +1 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +23 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-list-view.test.tsx +74 -0
- package/src/features/chat/features/ncp/hooks/use-ncp-session-list-view.ts +3 -2
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +294 -0
- package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +15 -1
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +24 -4
- package/src/features/chat/features/session/utils/chat-session-display.utils.ts +3 -0
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +1 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +34 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +1 -7
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +46 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +47 -0
- package/src/features/chat/managers/chat-thread.manager.ts +8 -2
- package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +97 -5
- package/src/features/system-status/components/desktop-update-config.tsx +91 -2
- package/src/features/system-status/utils/update-release-notes.utils.ts +94 -0
- package/src/shared/lib/api/ncp-session.types.ts +1 -1
- package/src/shared/lib/api/types.ts +12 -3
- package/src/shared/lib/i18n/locales/en-US/agents.json +41 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +3 -0
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +8 -0
- package/src/shared/lib/i18n/locales/zh-CN/agents.json +41 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +3 -0
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +8 -0
- package/dist/assets/chat-page-BtA7H1Ea.js +0 -1
- package/dist/assets/desktop-update-config-C6n_TB1r.js +0 -1
- package/dist/assets/doc-browser-DBnj9UMB.js +0 -1
- package/dist/assets/ellipsis-2uzhypw5.js +0 -1
- package/dist/assets/index-CBRP-7-a.css +0 -1
- package/dist/assets/index-Z1fYpbNd.js +0 -104
- package/dist/assets/loader-circle-BjmN5DeJ.js +0 -1
- package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +0 -1
- package/dist/assets/plus-3ztRZD_l.js +0 -1
- package/dist/assets/providers-config-page-CWlO7J4A.js +0 -1
- package/dist/assets/remote-CKWZfVVB.js +0 -1
- package/dist/assets/rotate-cw-CHcaRUDz.js +0 -1
- package/dist/assets/security-config-Ds0LsJmo.js +0 -1
- package/dist/assets/x-DqL1mYLC.js +0 -1
|
@@ -7,12 +7,17 @@ import { ChatConversationHeaderSection } from "@/features/chat/components/conver
|
|
|
7
7
|
import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
|
|
8
8
|
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
9
9
|
import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
|
|
10
|
-
import
|
|
10
|
+
import { viewportLayoutManager } from "@/app/managers/viewport-layout.manager";
|
|
11
|
+
import type { NcpSessionListItemView } from "@/features/chat/features/ncp/hooks/use-ncp-session-list-view";
|
|
12
|
+
import type { NcpSessionSummaryView, SessionEntryView } from "@/shared/lib/api";
|
|
11
13
|
|
|
12
14
|
const mocks = vi.hoisted(() => ({
|
|
13
15
|
deleteSession: vi.fn(),
|
|
14
16
|
openChildSessionPanel: vi.fn(),
|
|
15
17
|
openSessionCronPanel: vi.fn(),
|
|
18
|
+
selectSession: vi.fn(),
|
|
19
|
+
sessionItems: [] as NcpSessionListItemView[],
|
|
20
|
+
isSessionListLoading: false,
|
|
16
21
|
}));
|
|
17
22
|
|
|
18
23
|
vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
@@ -22,9 +27,35 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
|
22
27
|
openChildSessionPanel: mocks.openChildSessionPanel,
|
|
23
28
|
openSessionCronPanel: mocks.openSessionCronPanel,
|
|
24
29
|
},
|
|
30
|
+
chatSessionListManager: {
|
|
31
|
+
selectSession: mocks.selectSession,
|
|
32
|
+
},
|
|
25
33
|
}),
|
|
26
34
|
}));
|
|
27
35
|
|
|
36
|
+
vi.mock("@/features/chat/features/ncp/hooks/use-ncp-session-list-view", () => ({
|
|
37
|
+
useNcpSessionListView: (params?: { query?: string | null }) => {
|
|
38
|
+
const query = params?.query?.trim().toLowerCase() ?? "";
|
|
39
|
+
const items = query
|
|
40
|
+
? mocks.sessionItems.filter(({ session }) =>
|
|
41
|
+
[
|
|
42
|
+
session.key,
|
|
43
|
+
session.label ?? "",
|
|
44
|
+
session.projectRoot ?? "",
|
|
45
|
+
session.projectName ?? "",
|
|
46
|
+
]
|
|
47
|
+
.join(" ")
|
|
48
|
+
.toLowerCase()
|
|
49
|
+
.includes(query),
|
|
50
|
+
)
|
|
51
|
+
: mocks.sessionItems;
|
|
52
|
+
return {
|
|
53
|
+
isLoading: mocks.isSessionListLoading,
|
|
54
|
+
items,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
58
|
+
|
|
28
59
|
vi.mock("@/features/cron", () => ({
|
|
29
60
|
useCronJobs: () => ({ data: { jobs: [] } }),
|
|
30
61
|
}));
|
|
@@ -54,6 +85,24 @@ function createSummary(
|
|
|
54
85
|
};
|
|
55
86
|
}
|
|
56
87
|
|
|
88
|
+
function createSessionListItem(
|
|
89
|
+
overrides: Partial<SessionEntryView> & Pick<SessionEntryView, "key">,
|
|
90
|
+
): NcpSessionListItemView {
|
|
91
|
+
return {
|
|
92
|
+
session: {
|
|
93
|
+
createdAt: "2026-06-18T00:00:00.000Z",
|
|
94
|
+
updatedAt: "2026-06-18T00:00:00.000Z",
|
|
95
|
+
lastMessageAt: "2026-06-18T00:00:00.000Z",
|
|
96
|
+
readAt: "2026-06-18T00:00:00.000Z",
|
|
97
|
+
sessionType: "native",
|
|
98
|
+
sessionTypeMutable: false,
|
|
99
|
+
messageCount: 1,
|
|
100
|
+
...overrides,
|
|
101
|
+
},
|
|
102
|
+
runStatus: overrides.status === "running" ? "running" : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
57
106
|
function renderHeaderSection() {
|
|
58
107
|
const queryClient = new QueryClient({
|
|
59
108
|
defaultOptions: {
|
|
@@ -73,6 +122,9 @@ describe("ChatConversationHeaderSection", () => {
|
|
|
73
122
|
mocks.deleteSession.mockReset();
|
|
74
123
|
mocks.openChildSessionPanel.mockReset();
|
|
75
124
|
mocks.openSessionCronPanel.mockReset();
|
|
125
|
+
mocks.selectSession.mockReset();
|
|
126
|
+
mocks.isSessionListLoading = false;
|
|
127
|
+
viewportLayoutManager.resetForTests();
|
|
76
128
|
useChatSessionListStore.persist.setOptions({
|
|
77
129
|
storage: new MemoryPersistStorage(),
|
|
78
130
|
});
|
|
@@ -129,6 +181,18 @@ describe("ChatConversationHeaderSection", () => {
|
|
|
129
181
|
} as never,
|
|
130
182
|
},
|
|
131
183
|
});
|
|
184
|
+
mocks.sessionItems = [
|
|
185
|
+
createSessionListItem({
|
|
186
|
+
key: "parent-session-1",
|
|
187
|
+
label: "Parent Task",
|
|
188
|
+
}),
|
|
189
|
+
createSessionListItem({
|
|
190
|
+
key: "session:ncp-2",
|
|
191
|
+
label: "Background Task",
|
|
192
|
+
lastMessageAt: "2026-06-18T01:00:00.000Z",
|
|
193
|
+
readAt: "2026-06-18T01:00:00.000Z",
|
|
194
|
+
}),
|
|
195
|
+
];
|
|
132
196
|
});
|
|
133
197
|
|
|
134
198
|
it("shows the child-session entry from session query state before a tool card opens it", async () => {
|
|
@@ -142,4 +206,71 @@ describe("ChatConversationHeaderSection", () => {
|
|
|
142
206
|
activeChildSessionKey: "child-session-1",
|
|
143
207
|
});
|
|
144
208
|
});
|
|
209
|
+
|
|
210
|
+
it("uses the collapsed desktop title as a session switcher", async () => {
|
|
211
|
+
const user = userEvent.setup();
|
|
212
|
+
viewportLayoutManager.setSidebarCollapsed(true);
|
|
213
|
+
|
|
214
|
+
renderHeaderSection();
|
|
215
|
+
|
|
216
|
+
await user.click(screen.getByRole("button", { name: /Switch session/ }));
|
|
217
|
+
await user.click(screen.getByRole("button", { name: /Background Task/ }));
|
|
218
|
+
|
|
219
|
+
expect(mocks.selectSession).toHaveBeenCalledWith("session:ncp-2");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("keeps the desktop title plain while the sidebar is expanded", () => {
|
|
223
|
+
renderHeaderSection();
|
|
224
|
+
|
|
225
|
+
expect(screen.queryByRole("button", { name: /Switch session/ })).toBeNull();
|
|
226
|
+
expect(screen.getByText("Parent Task")).toBeTruthy();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("keeps session switching available from a collapsed new-session header", async () => {
|
|
230
|
+
const user = userEvent.setup();
|
|
231
|
+
viewportLayoutManager.setSidebarCollapsed(true);
|
|
232
|
+
useChatSessionListStore.setState({
|
|
233
|
+
snapshot: {
|
|
234
|
+
...useChatSessionListStore.getState().snapshot,
|
|
235
|
+
selectedSessionKey: null,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
useChatThreadStore.setState({
|
|
239
|
+
snapshot: {
|
|
240
|
+
...useChatThreadStore.getState().snapshot,
|
|
241
|
+
sessionKey: null,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
renderHeaderSection();
|
|
246
|
+
|
|
247
|
+
await user.click(screen.getByRole("button", { name: /Switch session/ }));
|
|
248
|
+
await user.click(screen.getByRole("button", { name: /Background Task/ }));
|
|
249
|
+
|
|
250
|
+
expect(mocks.selectSession).toHaveBeenCalledWith("session:ncp-2");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("filters the collapsed title switcher with a local search query", async () => {
|
|
254
|
+
const user = userEvent.setup();
|
|
255
|
+
viewportLayoutManager.setSidebarCollapsed(true);
|
|
256
|
+
|
|
257
|
+
renderHeaderSection();
|
|
258
|
+
|
|
259
|
+
await user.click(screen.getByRole("button", { name: /Switch session/ }));
|
|
260
|
+
const searchInput = screen.getByRole("textbox", {
|
|
261
|
+
name: "Search session key / label",
|
|
262
|
+
});
|
|
263
|
+
await user.type(searchInput, "Background");
|
|
264
|
+
await user.click(screen.getByRole("button", { name: /Background Task/ }));
|
|
265
|
+
|
|
266
|
+
expect(mocks.selectSession).toHaveBeenCalledWith("session:ncp-2");
|
|
267
|
+
|
|
268
|
+
await user.click(screen.getByRole("button", { name: /Switch session/ }));
|
|
269
|
+
await user.type(
|
|
270
|
+
screen.getByRole("textbox", { name: "Search session key / label" }),
|
|
271
|
+
"missing",
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(screen.getByText("No matching sessions")).toBeTruthy();
|
|
275
|
+
});
|
|
145
276
|
});
|
|
@@ -2,6 +2,7 @@ import { useMemo } from "react";
|
|
|
2
2
|
import { ChatConversationHeader } from "@/features/chat/components/conversation/chat-conversation-header";
|
|
3
3
|
import { ChatSessionHeaderActions } from "@/features/chat/features/session/components/session-header/chat-session-header-actions";
|
|
4
4
|
import { ChatSessionProjectBadge } from "@/features/chat/features/session/components/session-header/chat-session-project-badge";
|
|
5
|
+
import { ChatSessionTitleSwitcher } from "@/features/chat/features/session/components/session-header/chat-session-title-switcher";
|
|
5
6
|
import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
|
|
6
7
|
import { SessionContextIconNode } from "@/features/chat/features/session/components/session-context-icon";
|
|
7
8
|
import { useChatConversationWorkspaceState } from "@/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state";
|
|
@@ -89,6 +90,13 @@ export function ChatConversationHeaderSection({
|
|
|
89
90
|
<ChatConversationHeader
|
|
90
91
|
layoutMode={layoutMode}
|
|
91
92
|
title={sessionHeaderTitle}
|
|
93
|
+
titleContent={
|
|
94
|
+
<ChatSessionTitleSwitcher
|
|
95
|
+
layoutMode={layoutMode}
|
|
96
|
+
selectedSessionKey={sessionKey}
|
|
97
|
+
title={sessionHeaderTitle}
|
|
98
|
+
/>
|
|
99
|
+
}
|
|
92
100
|
shouldShow={shouldShowSessionHeader}
|
|
93
101
|
onBackToList={onBackToList}
|
|
94
102
|
leading={
|
|
@@ -41,6 +41,7 @@ export function ChatConversationHeader({
|
|
|
41
41
|
sessionTypeBadge,
|
|
42
42
|
shouldShow,
|
|
43
43
|
title,
|
|
44
|
+
titleContent,
|
|
44
45
|
}: {
|
|
45
46
|
layoutMode: "desktop" | "mobile";
|
|
46
47
|
actions?: ReactNode;
|
|
@@ -50,6 +51,7 @@ export function ChatConversationHeader({
|
|
|
50
51
|
sessionTypeBadge?: ReactNode;
|
|
51
52
|
shouldShow: boolean;
|
|
52
53
|
title: string;
|
|
54
|
+
titleContent?: ReactNode;
|
|
53
55
|
}) {
|
|
54
56
|
const isMobileLayout = layoutMode === "mobile";
|
|
55
57
|
|
|
@@ -78,9 +80,11 @@ export function ChatConversationHeader({
|
|
|
78
80
|
/>
|
|
79
81
|
) : null}
|
|
80
82
|
{leading}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
{titleContent ?? (
|
|
84
|
+
<span className="text-sm font-medium text-foreground truncate">
|
|
85
|
+
{title}
|
|
86
|
+
</span>
|
|
87
|
+
)}
|
|
84
88
|
{sessionTypeBadge}
|
|
85
89
|
{projectBadge}
|
|
86
90
|
</div>
|
|
@@ -50,7 +50,7 @@ const mocks = vi.hoisted(() => {
|
|
|
50
50
|
modelOptions: [],
|
|
51
51
|
selectedSession: null as null | {
|
|
52
52
|
activityPreview?: {
|
|
53
|
-
state: 'running' | 'completed' | 'failed' | 'idle';
|
|
53
|
+
state: 'running' | 'completed' | 'failed' | 'cancelled' | 'idle';
|
|
54
54
|
statusText?: string;
|
|
55
55
|
replyText?: string;
|
|
56
56
|
};
|
|
@@ -260,7 +260,7 @@ describe('SessionConversationArea input boundary', () => {
|
|
|
260
260
|
mocks.inputQuery.selectedSession = {
|
|
261
261
|
activityPreview: {
|
|
262
262
|
state: 'failed',
|
|
263
|
-
statusText: '
|
|
263
|
+
statusText: 'Run failed: Invalid API Key',
|
|
264
264
|
},
|
|
265
265
|
status: 'idle',
|
|
266
266
|
};
|
|
@@ -269,6 +269,21 @@ describe('SessionConversationArea input boundary', () => {
|
|
|
269
269
|
|
|
270
270
|
expect(screen.getByTestId('conversation-bottom-slot')).toBeTruthy();
|
|
271
271
|
expect(screen.getByText(/出错了|Something went wrong/)).toBeTruthy();
|
|
272
|
-
expect(screen.getByText('
|
|
272
|
+
expect(screen.getByText('Run failed: Invalid API Key')).toBeTruthy();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('does not surface user-cancelled previews as conversation errors', () => {
|
|
276
|
+
mocks.inputQuery.selectedSession = {
|
|
277
|
+
activityPreview: {
|
|
278
|
+
state: 'cancelled',
|
|
279
|
+
statusText: 'Run interrupted: User stopped the current run.',
|
|
280
|
+
},
|
|
281
|
+
status: 'idle',
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
renderArea('session-1');
|
|
285
|
+
|
|
286
|
+
expect(screen.queryByTestId('conversation-bottom-slot')).toBeNull();
|
|
287
|
+
expect(screen.queryByText(/出错了|Something went wrong/)).toBeNull();
|
|
273
288
|
});
|
|
274
289
|
});
|
package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx
CHANGED
|
@@ -497,7 +497,7 @@ it("renders context compaction as an in-flow divider instead of a chat message",
|
|
|
497
497
|
updatedAt: "2026-05-05T12:00:00.000Z",
|
|
498
498
|
},
|
|
499
499
|
},
|
|
500
|
-
parts: [{ type: "text", text: "
|
|
500
|
+
parts: [{ type: "text", text: "Earlier context was auto-compacted" }],
|
|
501
501
|
} satisfies NcpMessage;
|
|
502
502
|
|
|
503
503
|
const { getByText } = render(
|
|
@@ -217,7 +217,15 @@ describe("useNcpAgentRuntime", () => {
|
|
|
217
217
|
await result.current.abort();
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
-
expect(client.abort).toHaveBeenCalledWith({
|
|
220
|
+
expect(client.abort).toHaveBeenCalledWith({
|
|
221
|
+
sessionId: "session-running",
|
|
222
|
+
runId: undefined,
|
|
223
|
+
reason: {
|
|
224
|
+
code: "abort-error",
|
|
225
|
+
message: "User stopped the current run.",
|
|
226
|
+
details: { source: "chat-ui" },
|
|
227
|
+
},
|
|
228
|
+
});
|
|
221
229
|
});
|
|
222
230
|
|
|
223
231
|
it("clears the local running state when the live stream publishes message.abort", async () => {
|
|
@@ -243,6 +251,20 @@ describe("useNcpAgentRuntime", () => {
|
|
|
243
251
|
expect(result.current.activeRunId).toBe("run-1");
|
|
244
252
|
});
|
|
245
253
|
|
|
254
|
+
await act(async () => {
|
|
255
|
+
await result.current.abort();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(client.abort).toHaveBeenCalledWith({
|
|
259
|
+
sessionId: "session-running",
|
|
260
|
+
runId: "run-1",
|
|
261
|
+
reason: {
|
|
262
|
+
code: "abort-error",
|
|
263
|
+
message: "User stopped the current run.",
|
|
264
|
+
details: { source: "chat-ui" },
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
246
268
|
await act(async () => {
|
|
247
269
|
await client.emit({
|
|
248
270
|
type: NcpEventType.MessageAbort,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { useNcpSessionListView } from "@/features/chat/features/ncp/hooks/use-ncp-session-list-view";
|
|
4
|
+
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
5
|
+
import type { NcpSessionSummaryView } from "@/shared/lib/api";
|
|
6
|
+
|
|
7
|
+
const mocks = vi.hoisted(() => ({
|
|
8
|
+
sessions: [] as NcpSessionSummaryView[],
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock("@/features/chat/features/ncp/hooks/use-ncp-session-queries", () => ({
|
|
12
|
+
useNcpSessions: () => ({
|
|
13
|
+
data: {
|
|
14
|
+
sessions: mocks.sessions,
|
|
15
|
+
total: mocks.sessions.length,
|
|
16
|
+
},
|
|
17
|
+
isLoading: false,
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
function createSummary(
|
|
22
|
+
sessionId: string,
|
|
23
|
+
label: string,
|
|
24
|
+
): NcpSessionSummaryView {
|
|
25
|
+
return {
|
|
26
|
+
sessionId,
|
|
27
|
+
status: "idle",
|
|
28
|
+
updatedAt: "2026-06-18T00:00:00.000Z",
|
|
29
|
+
lastMessageAt: "2026-06-18T00:00:00.000Z",
|
|
30
|
+
messageCount: 1,
|
|
31
|
+
metadata: {
|
|
32
|
+
label,
|
|
33
|
+
session_type: "native",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("useNcpSessionListView", () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
mocks.sessions = [
|
|
41
|
+
createSummary("session:alpha", "Alpha Task"),
|
|
42
|
+
createSummary("session:beta", "Beta Task"),
|
|
43
|
+
];
|
|
44
|
+
useChatSessionListStore.setState({
|
|
45
|
+
snapshot: {
|
|
46
|
+
...useChatSessionListStore.getState().snapshot,
|
|
47
|
+
query: "",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uses the sidebar query by default", () => {
|
|
53
|
+
useChatSessionListStore.getState().setSnapshot({ query: "Alpha" });
|
|
54
|
+
|
|
55
|
+
const { result } = renderHook(() => useNcpSessionListView());
|
|
56
|
+
|
|
57
|
+
expect(result.current.items.map((item) => item.session.key)).toEqual([
|
|
58
|
+
"session:alpha",
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("can override the hidden sidebar query for header switching", () => {
|
|
63
|
+
useChatSessionListStore.getState().setSnapshot({ query: "Alpha" });
|
|
64
|
+
|
|
65
|
+
const { result } = renderHook(() =>
|
|
66
|
+
useNcpSessionListView({ query: "" }),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(result.current.items.map((item) => item.session.key)).toEqual([
|
|
70
|
+
"session:alpha",
|
|
71
|
+
"session:beta",
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -22,8 +22,9 @@ function shouldShowSessionInSidebar(session: SessionEntryView): boolean {
|
|
|
22
22
|
return session.isPromotedChildSession === true;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export function useNcpSessionListView(params: { limit?: number } = {}) {
|
|
26
|
-
const
|
|
25
|
+
export function useNcpSessionListView(params: { limit?: number; query?: string | null } = {}) {
|
|
26
|
+
const storedQuery = useChatSessionListStore((state) => state.snapshot.query);
|
|
27
|
+
const query = params.query ?? storedQuery;
|
|
27
28
|
const sessionsQuery = useNcpSessions({ limit: params.limit ?? 200 });
|
|
28
29
|
|
|
29
30
|
const items = useMemo<NcpSessionListItemView[]>(() => {
|