@nextclaw/ui 0.12.4 → 0.12.5
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 +41 -0
- package/dist/assets/{ChannelsList-CobWeI2V.js → ChannelsList-C6-lh55g.js} +2 -2
- package/dist/assets/ChatPage-DOW0gPc2.js +45 -0
- package/dist/assets/DocBrowser-CGyeswYP.js +1 -0
- package/dist/assets/{DocBrowser-NSzgVKka.js → DocBrowser-QUZ3nfmH.js} +1 -1
- package/dist/assets/{DocBrowserContext-DpgVdRgk.js → DocBrowserContext-CpiIfhJO.js} +1 -1
- package/dist/assets/{LogoBadge-CHS4YNLw.js → LogoBadge-BUK13xK5.js} +1 -1
- package/dist/assets/MarketplacePage-BDVwhIYE.js +1 -0
- package/dist/assets/MarketplacePage-LnKKL3xK.js +49 -0
- package/dist/assets/McpMarketplacePage-BG4T_Pcx.js +40 -0
- package/dist/assets/ModelConfig-LtWuogIw.js +1 -0
- package/dist/assets/ProviderScopedModelInput-DGn6sFEN.js +1 -0
- package/dist/assets/ProvidersList-ma-_MlLo.js +1 -0
- package/dist/assets/{RemoteAccessPage-yfbrveNQ.js → RemoteAccessPage-ff15qO-c.js} +1 -1
- package/dist/assets/RuntimeConfig-TgPandXF.js +1 -0
- package/dist/assets/SearchConfig-C9iBt7pl.js +1 -0
- package/dist/assets/{SecretsConfig-CLFSSoTl.js → SecretsConfig-Bew4EF2A.js} +2 -2
- package/dist/assets/{SessionsConfig-vYrvc2Fk.js → SessionsConfig-2r2yAGZg.js} +2 -2
- package/dist/assets/{book-open-C7TAghTk.js → book-open-CJG8Yz3U.js} +1 -1
- package/dist/assets/{chat-session-display-5dVFkJyw.js → chat-session-display-DkAC5OMC.js} +1 -1
- package/dist/assets/{chunk-JZWAC4HX-DbL4EmiT.js → chunk-JZWAC4HX-D5b3Iyas.js} +1 -1
- package/dist/assets/{config-CMiW0yaK.js → config-zvnxSXSP.js} +1 -1
- package/dist/assets/{createLucideIcon-BRLFtf-8.js → createLucideIcon-_FMJqZw2.js} +1 -1
- package/dist/assets/{dist-DP-JKR4G.js → dist-B1fpOuON.js} +1 -1
- package/dist/assets/{dist-BFc_H-lY.js → dist-BCXX7FD-.js} +2 -2
- package/dist/assets/{external-link-BkJkiWbH.js → external-link-b7gAJWYY.js} +1 -1
- package/dist/assets/{hash-CbP6-6R9.js → hash-Bhy4TwfZ.js} +1 -1
- package/dist/assets/{i18n-C_2dKw6w.js → i18n-DJg9BPYk.js} +1 -1
- package/dist/assets/index-BoJbxdvZ.css +1 -0
- package/dist/assets/index-CtlT4E9Y.js +6 -0
- package/dist/assets/infiniteQueryBehavior-CTcVlD9s.js +1 -0
- package/dist/assets/loader-circle-B60I0hEk.js +1 -0
- package/dist/assets/{logos-N3dbS6-I.js → logos-GMeYU9vc.js} +1 -1
- package/dist/assets/{page-layout-DyuvlNrg.js → page-layout-C8UbWuMt.js} +1 -1
- package/dist/assets/plus-CR7RfK3H.js +1 -0
- package/dist/assets/{popover-BKKWGUaG.js → popover-8HSx9wQj.js} +1 -1
- package/dist/assets/react-BB4jko2M.js +1 -0
- package/dist/assets/{refresh-ccw-BGMdiNGq.js → refresh-ccw-CA4_C7Zg.js} +1 -1
- package/dist/assets/{save-Dh4GQzzX.js → save-BtvMy4lk.js} +1 -1
- package/dist/assets/search-C60UA27E.js +1 -0
- package/dist/assets/security-config-BkFDYZ6j.js +1 -0
- package/dist/assets/{select-BtIi5fnh.js → select-xp_Ac8ip.js} +1 -1
- package/dist/assets/skeleton-uxz_5h3A.js +1 -0
- package/dist/assets/{status-dot-C4O-2jZP.js → status-dot-Cn4Pp7DZ.js} +1 -1
- package/dist/assets/{switch-DPegGIa_.js → switch-BTi6UOij.js} +1 -1
- package/dist/assets/{tabs-custom-x5GZexrF.js → tabs-custom-BiiN8DME.js} +1 -1
- package/dist/assets/{trash-2-CU3LYIpQ.js → trash-2-BpsF0N-r.js} +1 -1
- package/dist/assets/use-infinite-scroll-loader-C8jBv11-.js +1 -0
- package/dist/assets/{useConfirmDialog-S5WsGOGf.js → useConfirmDialog-BJIwUZjH.js} +1 -1
- package/dist/assets/{useMutation-DSinpgEq.js → useMutation-BjBOKHj_.js} +1 -1
- package/dist/assets/x-BfTu-g7D.js +1 -0
- package/dist/index.html +19 -18
- package/package.json +4 -4
- package/src/account/components/account-panel.tsx +46 -4
- package/src/account/managers/account.manager.ts +19 -4
- package/src/api/remote.ts +9 -0
- package/src/api/remote.types.ts +5 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +183 -141
- package/src/components/chat/adapters/chat-message-tool-agent-id.test.ts +11 -11
- package/src/components/chat/adapters/chat-message.adapter.test.ts +43 -6
- package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +182 -44
- package/src/components/chat/adapters/chat-message.session-spawn-tool-card.test.ts +104 -0
- package/src/components/chat/chat-child-session-panel.tsx +103 -45
- package/src/components/chat/chat-page-runtime.test.ts +16 -19
- package/src/components/chat/chat-session-preference-sync.test.ts +13 -0
- package/src/components/chat/chat-session-preference-sync.ts +9 -7
- package/src/components/chat/hooks/use-chat-session-project.test.tsx +5 -5
- package/src/components/chat/hooks/use-chat-session-project.ts +0 -5
- package/src/components/chat/hooks/use-chat-session-update.test.tsx +75 -0
- package/src/components/chat/hooks/use-chat-session-update.ts +4 -2
- package/src/components/chat/managers/chat-session-list.manager.test.ts +45 -5
- package/src/components/chat/managers/chat-session-list.manager.ts +18 -4
- package/src/components/chat/ncp/NcpChatPage.tsx +32 -51
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +3 -5
- package/src/components/chat/ncp/ncp-chat-page-data.ts +0 -1
- package/src/components/chat/ncp/ncp-chat.presenter.ts +1 -11
- package/src/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view.ts +20 -7
- package/src/components/chat/stores/chat-session-list.store.ts +3 -0
- package/src/components/chat/useChatSessionTypeState.test.tsx +0 -3
- package/src/components/chat/useChatSessionTypeState.ts +3 -5
- package/src/components/config/ChannelsList.test.tsx +68 -0
- package/src/components/config/ChannelsList.tsx +22 -4
- package/src/components/config/ProvidersList.tsx +17 -3
- package/src/components/config/providers-list.test.tsx +68 -0
- package/src/components/layout/Sidebar.tsx +13 -13
- package/src/components/layout/sidebar.layout.test.tsx +32 -1
- package/src/components/marketplace/MarketplacePage.tsx +30 -30
- package/src/components/marketplace/marketplace-page-parts.tsx +16 -24
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +28 -26
- package/src/hooks/marketplace-list-pages.ts +27 -0
- package/src/hooks/use-infinite-scroll-loader.ts +88 -0
- package/src/hooks/useMarketplace.ts +14 -3
- package/src/hooks/useMcpMarketplace.ts +14 -3
- package/src/lib/i18n.remote.ts +15 -0
- package/dist/assets/ChatPage-ZIdFFVAv.js +0 -43
- package/dist/assets/DocBrowser-D55C0iyl.js +0 -1
- package/dist/assets/MarketplacePage-BFYsRss_.js +0 -49
- package/dist/assets/MarketplacePage-DII-q-Y1.js +0 -1
- package/dist/assets/McpMarketplacePage-CPqsGJzz.js +0 -40
- package/dist/assets/ModelConfig-Bvuo_IpS.js +0 -1
- package/dist/assets/ProviderScopedModelInput-BfY8rGsf.js +0 -1
- package/dist/assets/ProvidersList-3tlaqwSS.js +0 -1
- package/dist/assets/RuntimeConfig-CAd5Kta3.js +0 -1
- package/dist/assets/SearchConfig-DFwgaAa7.js +0 -1
- package/dist/assets/index-ChUXhq0G.css +0 -1
- package/dist/assets/index-DAE8Srx-.js +0 -6
- package/dist/assets/label-D8yyejJS.js +0 -1
- package/dist/assets/loader-circle-B0sKKO29.js +0 -1
- package/dist/assets/marketplace-localization-CxSTG9wr.js +0 -1
- package/dist/assets/plus-CYXs3JtZ.js +0 -1
- package/dist/assets/react-8EIEQjMP.js +0 -1
- package/dist/assets/search-DOsLw-P9.js +0 -1
- package/dist/assets/security-config-CM_tQRXQ.js +0 -1
- package/dist/assets/skeleton-GbHLjPC0.js +0 -1
- package/dist/assets/x-Bnco_K8b.js +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { render, screen } from
|
|
2
|
-
import userEvent from
|
|
3
|
-
import { beforeEach, describe, expect, it, vi } from
|
|
4
|
-
import { ChatChildSessionPanel } from
|
|
5
|
-
import { ChatConversationPanel } from
|
|
6
|
-
import { useChatInputStore } from
|
|
7
|
-
import { useChatThreadStore } from
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { ChatChildSessionPanel } from "@/components/chat/chat-child-session-panel";
|
|
5
|
+
import { ChatConversationPanel } from "@/components/chat/ChatConversationPanel";
|
|
6
|
+
import { useChatInputStore } from "@/components/chat/stores/chat-input.store";
|
|
7
|
+
import { useChatThreadStore } from "@/components/chat/stores/chat-thread.store";
|
|
8
8
|
|
|
9
9
|
const mocks = vi.hoisted(() => ({
|
|
10
10
|
deleteSession: vi.fn(),
|
|
@@ -12,41 +12,44 @@ const mocks = vi.hoisted(() => ({
|
|
|
12
12
|
createSession: vi.fn(),
|
|
13
13
|
setSelectedAgentId: vi.fn(),
|
|
14
14
|
setPendingSessionType: vi.fn(),
|
|
15
|
+
stickyBottomScroll: vi.fn(() => ({
|
|
16
|
+
onScroll: vi.fn(),
|
|
17
|
+
})),
|
|
15
18
|
resolvedChildTabs: [
|
|
16
19
|
{
|
|
17
|
-
sessionKey:
|
|
18
|
-
parentSessionKey:
|
|
19
|
-
title:
|
|
20
|
-
agentId:
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
sessionKey: "child-session-1",
|
|
21
|
+
parentSessionKey: "parent-session-1",
|
|
22
|
+
title: "北京天气",
|
|
23
|
+
agentId: "weather",
|
|
24
|
+
sessionTypeLabel: "Codex",
|
|
25
|
+
preferredModel: "openai/gpt-5.3-codex",
|
|
26
|
+
projectName: "project-alpha",
|
|
27
|
+
projectRoot: "/Users/demo/project-alpha",
|
|
23
28
|
},
|
|
24
29
|
],
|
|
25
30
|
}));
|
|
26
31
|
|
|
27
|
-
vi.mock(
|
|
32
|
+
vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
|
|
28
33
|
const actual = await importOriginal();
|
|
29
34
|
return {
|
|
30
35
|
...(actual as object),
|
|
31
|
-
useStickyBottomScroll:
|
|
32
|
-
onScroll: vi.fn()
|
|
33
|
-
})
|
|
36
|
+
useStickyBottomScroll: mocks.stickyBottomScroll,
|
|
34
37
|
};
|
|
35
38
|
});
|
|
36
39
|
|
|
37
|
-
vi.mock(
|
|
40
|
+
vi.mock("@/components/chat/nextclaw", () => ({
|
|
38
41
|
ChatInputBarContainer: () => <div data-testid="chat-input-bar" />,
|
|
39
|
-
ChatMessageListContainer: () => <div data-testid="chat-message-list"
|
|
42
|
+
ChatMessageListContainer: () => <div data-testid="chat-message-list" />,
|
|
40
43
|
}));
|
|
41
44
|
|
|
42
|
-
vi.mock(
|
|
45
|
+
vi.mock("@/components/chat/containers/chat-message-list.container", () => ({
|
|
43
46
|
ChatMessageListContainer: () => <div data-testid="child-chat-message-list" />,
|
|
44
47
|
}));
|
|
45
48
|
|
|
46
|
-
vi.mock(
|
|
49
|
+
vi.mock("@/components/chat/ChatWelcome", () => ({
|
|
47
50
|
ChatWelcome: ({
|
|
48
51
|
onCreateSession,
|
|
49
|
-
onSelectAgent
|
|
52
|
+
onSelectAgent,
|
|
50
53
|
}: {
|
|
51
54
|
onCreateSession: () => void;
|
|
52
55
|
onSelectAgent: (agentId: string) => void;
|
|
@@ -55,87 +58,102 @@ vi.mock('@/components/chat/ChatWelcome', () => ({
|
|
|
55
58
|
<button type="button" onClick={onCreateSession}>
|
|
56
59
|
create draft session
|
|
57
60
|
</button>
|
|
58
|
-
<button type="button" onClick={() => onSelectAgent(
|
|
61
|
+
<button type="button" onClick={() => onSelectAgent("engineer")}>
|
|
59
62
|
switch draft agent
|
|
60
63
|
</button>
|
|
61
64
|
</div>
|
|
62
|
-
)
|
|
65
|
+
),
|
|
63
66
|
}));
|
|
64
67
|
|
|
65
|
-
vi.mock(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
vi.mock("@/components/chat/presenter/chat-presenter-context", () => ({
|
|
69
|
+
usePresenter: () => ({
|
|
70
|
+
chatThreadManager: {
|
|
71
|
+
deleteSession: mocks.deleteSession,
|
|
72
|
+
goToProviders: mocks.goToProviders,
|
|
73
|
+
openSessionFromToolAction: vi.fn(),
|
|
74
|
+
selectChildSessionDetail: vi.fn(),
|
|
75
|
+
closeChildSessionDetail: vi.fn(),
|
|
76
|
+
goToParentSession: vi.fn(),
|
|
77
|
+
},
|
|
75
78
|
chatSessionListManager: {
|
|
76
79
|
selectSession: vi.fn(),
|
|
77
80
|
createSession: mocks.createSession,
|
|
78
|
-
setSelectedAgentId: mocks.setSelectedAgentId
|
|
81
|
+
setSelectedAgentId: mocks.setSelectedAgentId,
|
|
79
82
|
},
|
|
80
83
|
chatInputManager: {
|
|
81
|
-
setPendingSessionType: mocks.setPendingSessionType
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
}));
|
|
85
|
-
|
|
86
|
-
vi.mock('@/components/chat/session-header/chat-session-header-actions', () => ({
|
|
87
|
-
ChatSessionHeaderActions: () => <button aria-label="More actions" />
|
|
84
|
+
setPendingSessionType: mocks.setPendingSessionType,
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
88
87
|
}));
|
|
89
88
|
|
|
90
|
-
vi.mock(
|
|
91
|
-
|
|
89
|
+
vi.mock("@/components/chat/session-header/chat-session-header-actions", () => ({
|
|
90
|
+
ChatSessionHeaderActions: () => <button aria-label="More actions" />,
|
|
92
91
|
}));
|
|
93
92
|
|
|
94
|
-
vi.mock(
|
|
95
|
-
|
|
93
|
+
vi.mock("@/components/chat/session-header/chat-session-project-badge", () => ({
|
|
94
|
+
ChatSessionProjectBadge: ({ projectName }: { projectName: string }) => (
|
|
95
|
+
<button>{projectName}</button>
|
|
96
|
+
),
|
|
96
97
|
}));
|
|
97
98
|
|
|
98
|
-
vi.mock(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
hydrateError: null,
|
|
103
|
-
isRunning: false,
|
|
99
|
+
vi.mock(
|
|
100
|
+
"@/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view",
|
|
101
|
+
() => ({
|
|
102
|
+
useNcpChildSessionTabsView: () => mocks.resolvedChildTabs,
|
|
104
103
|
}),
|
|
105
|
-
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
vi.mock(
|
|
107
|
+
"@/components/chat/ncp/session-conversation/use-ncp-session-conversation",
|
|
108
|
+
() => ({
|
|
109
|
+
useNcpSessionConversation: () => ({
|
|
110
|
+
visibleMessages: [],
|
|
111
|
+
isHydrating: false,
|
|
112
|
+
hydrateError: null,
|
|
113
|
+
isRunning: false,
|
|
114
|
+
}),
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
106
117
|
|
|
107
|
-
vi.mock(
|
|
118
|
+
vi.mock("@/components/common/AgentAvatar", () => ({
|
|
108
119
|
AgentAvatar: ({ agentId }: { agentId: string }) => (
|
|
109
120
|
<div data-testid="agent-avatar">{agentId}</div>
|
|
110
121
|
),
|
|
111
122
|
}));
|
|
112
123
|
|
|
113
|
-
vi.mock(
|
|
124
|
+
vi.mock("@/components/common/agent-identity", () => ({
|
|
114
125
|
AgentIdentityAvatar: ({ agentId }: { agentId: string }) => (
|
|
115
126
|
<div data-testid="agent-identity-avatar">{agentId}</div>
|
|
116
127
|
),
|
|
117
128
|
}));
|
|
118
129
|
|
|
119
|
-
describe(
|
|
130
|
+
describe("ChatConversationPanel", () => {
|
|
120
131
|
beforeEach(() => {
|
|
121
132
|
mocks.deleteSession.mockReset();
|
|
122
133
|
mocks.goToProviders.mockReset();
|
|
123
134
|
mocks.createSession.mockReset();
|
|
124
135
|
mocks.setSelectedAgentId.mockReset();
|
|
125
136
|
mocks.setPendingSessionType.mockReset();
|
|
137
|
+
mocks.stickyBottomScroll.mockClear();
|
|
126
138
|
useChatInputStore.setState({
|
|
127
139
|
snapshot: {
|
|
128
140
|
...useChatInputStore.getState().snapshot,
|
|
129
|
-
defaultSessionType:
|
|
130
|
-
}
|
|
141
|
+
defaultSessionType: "native",
|
|
142
|
+
},
|
|
131
143
|
});
|
|
132
144
|
useChatThreadStore.setState({
|
|
133
145
|
snapshot: {
|
|
134
146
|
...useChatThreadStore.getState().snapshot,
|
|
135
147
|
isProviderStateResolved: true,
|
|
136
|
-
modelOptions: [
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
modelOptions: [
|
|
149
|
+
{
|
|
150
|
+
value: "openai/gpt-5.1",
|
|
151
|
+
modelLabel: "gpt-5.1",
|
|
152
|
+
providerLabel: "OpenAI",
|
|
153
|
+
} as never,
|
|
154
|
+
],
|
|
155
|
+
sessionTypeLabel: "Codex",
|
|
156
|
+
sessionKey: "draft-session-1",
|
|
139
157
|
sessionDisplayName: undefined,
|
|
140
158
|
agentId: null,
|
|
141
159
|
agentDisplayName: null,
|
|
@@ -150,121 +168,127 @@ describe('ChatConversationPanel', () => {
|
|
|
150
168
|
parentSessionKey: null,
|
|
151
169
|
parentSessionLabel: null,
|
|
152
170
|
availableAgents: [
|
|
153
|
-
{ id:
|
|
154
|
-
{ id:
|
|
171
|
+
{ id: "main", displayName: "Main", runtime: "native" },
|
|
172
|
+
{ id: "engineer", displayName: "Engineer", runtime: "codex" },
|
|
155
173
|
],
|
|
156
174
|
childSessionTabs: [],
|
|
157
175
|
activeChildSessionKey: null,
|
|
158
|
-
}
|
|
176
|
+
},
|
|
159
177
|
});
|
|
160
178
|
});
|
|
161
179
|
|
|
162
|
-
it(
|
|
180
|
+
it("shows the draft session type in the conversation header", () => {
|
|
163
181
|
render(<ChatConversationPanel />);
|
|
164
182
|
|
|
165
|
-
expect(screen.getByText(
|
|
166
|
-
expect(screen.getByText(
|
|
167
|
-
expect(screen.getByLabelText(
|
|
183
|
+
expect(screen.getByText("New Task")).toBeTruthy();
|
|
184
|
+
expect(screen.getByText("Codex")).toBeTruthy();
|
|
185
|
+
expect(screen.getByLabelText("More actions")).toBeTruthy();
|
|
168
186
|
});
|
|
169
187
|
|
|
170
|
-
it(
|
|
188
|
+
it("shows the selected session project badge and more actions trigger", () => {
|
|
171
189
|
useChatThreadStore.setState({
|
|
172
190
|
snapshot: {
|
|
173
191
|
...useChatThreadStore.getState().snapshot,
|
|
174
|
-
sessionKey:
|
|
175
|
-
sessionDisplayName:
|
|
176
|
-
sessionProjectRoot:
|
|
177
|
-
sessionProjectName:
|
|
192
|
+
sessionKey: "session-1",
|
|
193
|
+
sessionDisplayName: "Project Thread",
|
|
194
|
+
sessionProjectRoot: "/Users/demo/workspace/project-alpha",
|
|
195
|
+
sessionProjectName: "project-alpha",
|
|
178
196
|
canDeleteSession: true,
|
|
179
|
-
}
|
|
197
|
+
},
|
|
180
198
|
});
|
|
181
199
|
|
|
182
200
|
render(<ChatConversationPanel />);
|
|
183
201
|
|
|
184
|
-
expect(screen.getByText(
|
|
185
|
-
expect(screen.getByText(
|
|
186
|
-
expect(screen.getByLabelText(
|
|
202
|
+
expect(screen.getByText("Project Thread")).toBeTruthy();
|
|
203
|
+
expect(screen.getByText("project-alpha")).toBeTruthy();
|
|
204
|
+
expect(screen.getByLabelText("More actions")).toBeTruthy();
|
|
187
205
|
});
|
|
188
206
|
|
|
189
|
-
it(
|
|
207
|
+
it("does not show a header agent marker for the main agent", () => {
|
|
190
208
|
useChatThreadStore.setState({
|
|
191
209
|
snapshot: {
|
|
192
210
|
...useChatThreadStore.getState().snapshot,
|
|
193
|
-
agentId:
|
|
194
|
-
agentDisplayName:
|
|
195
|
-
}
|
|
211
|
+
agentId: "main",
|
|
212
|
+
agentDisplayName: "Main",
|
|
213
|
+
},
|
|
196
214
|
});
|
|
197
215
|
|
|
198
216
|
render(<ChatConversationPanel />);
|
|
199
217
|
|
|
200
|
-
expect(screen.queryByTestId(
|
|
218
|
+
expect(screen.queryByTestId("agent-avatar")).toBeNull();
|
|
201
219
|
});
|
|
202
220
|
|
|
203
|
-
it(
|
|
221
|
+
it("shows only a lightweight avatar marker for a specialist agent", () => {
|
|
204
222
|
useChatThreadStore.setState({
|
|
205
223
|
snapshot: {
|
|
206
224
|
...useChatThreadStore.getState().snapshot,
|
|
207
|
-
agentId:
|
|
208
|
-
agentDisplayName:
|
|
209
|
-
}
|
|
225
|
+
agentId: "engineer",
|
|
226
|
+
agentDisplayName: "Engineer",
|
|
227
|
+
},
|
|
210
228
|
});
|
|
211
229
|
|
|
212
230
|
render(<ChatConversationPanel />);
|
|
213
231
|
|
|
214
|
-
expect(screen.getByTestId(
|
|
215
|
-
expect(screen.queryByText(
|
|
232
|
+
expect(screen.getByTestId("agent-avatar").textContent).toBe("engineer");
|
|
233
|
+
expect(screen.queryByText("Engineer")).toBeNull();
|
|
216
234
|
});
|
|
217
235
|
|
|
218
|
-
it(
|
|
236
|
+
it("creates a draft session with the selected draft agent runtime", async () => {
|
|
219
237
|
const user = userEvent.setup();
|
|
220
238
|
|
|
221
239
|
useChatThreadStore.setState({
|
|
222
240
|
snapshot: {
|
|
223
241
|
...useChatThreadStore.getState().snapshot,
|
|
224
|
-
agentId:
|
|
225
|
-
agentDisplayName:
|
|
226
|
-
}
|
|
242
|
+
agentId: "engineer",
|
|
243
|
+
agentDisplayName: "Engineer",
|
|
244
|
+
},
|
|
227
245
|
});
|
|
228
246
|
|
|
229
247
|
render(<ChatConversationPanel />);
|
|
230
248
|
|
|
231
|
-
await user.click(
|
|
249
|
+
await user.click(
|
|
250
|
+
screen.getByRole("button", { name: "create draft session" }),
|
|
251
|
+
);
|
|
232
252
|
|
|
233
|
-
expect(mocks.createSession).toHaveBeenCalledWith(
|
|
253
|
+
expect(mocks.createSession).toHaveBeenCalledWith("codex");
|
|
234
254
|
});
|
|
235
255
|
|
|
236
|
-
it(
|
|
256
|
+
it("syncs the pending session type when switching the draft agent", async () => {
|
|
237
257
|
const user = userEvent.setup();
|
|
238
258
|
|
|
239
259
|
render(<ChatConversationPanel />);
|
|
240
260
|
|
|
241
|
-
await user.click(
|
|
261
|
+
await user.click(
|
|
262
|
+
screen.getByRole("button", { name: "switch draft agent" }),
|
|
263
|
+
);
|
|
242
264
|
|
|
243
|
-
expect(mocks.setSelectedAgentId).toHaveBeenCalledWith(
|
|
244
|
-
expect(mocks.setPendingSessionType).toHaveBeenCalledWith(
|
|
265
|
+
expect(mocks.setSelectedAgentId).toHaveBeenCalledWith("engineer");
|
|
266
|
+
expect(mocks.setPendingSessionType).toHaveBeenCalledWith("codex");
|
|
245
267
|
});
|
|
246
268
|
});
|
|
247
269
|
|
|
248
|
-
describe(
|
|
249
|
-
it(
|
|
270
|
+
describe("ChatChildSessionPanel", () => {
|
|
271
|
+
it("keeps the header compact for a single child session", () => {
|
|
250
272
|
mocks.resolvedChildTabs = [
|
|
251
273
|
{
|
|
252
|
-
sessionKey:
|
|
253
|
-
parentSessionKey:
|
|
254
|
-
title:
|
|
255
|
-
agentId:
|
|
256
|
-
|
|
257
|
-
|
|
274
|
+
sessionKey: "child-session-1",
|
|
275
|
+
parentSessionKey: "parent-session-1",
|
|
276
|
+
title: "北京天气",
|
|
277
|
+
agentId: "weather",
|
|
278
|
+
sessionTypeLabel: "Codex",
|
|
279
|
+
preferredModel: "openai/gpt-5.3-codex",
|
|
280
|
+
projectName: "project-alpha",
|
|
281
|
+
projectRoot: "/Users/demo/project-alpha",
|
|
258
282
|
},
|
|
259
283
|
];
|
|
260
284
|
render(
|
|
261
285
|
<ChatChildSessionPanel
|
|
262
286
|
tabs={[
|
|
263
287
|
{
|
|
264
|
-
sessionKey:
|
|
265
|
-
parentSessionKey:
|
|
266
|
-
label:
|
|
267
|
-
agentId:
|
|
288
|
+
sessionKey: "child-session-1",
|
|
289
|
+
parentSessionKey: "parent-session-1",
|
|
290
|
+
label: "北京天气",
|
|
291
|
+
agentId: "weather",
|
|
268
292
|
},
|
|
269
293
|
]}
|
|
270
294
|
activeSessionKey="child-session-1"
|
|
@@ -274,28 +298,42 @@ describe('ChatChildSessionPanel', () => {
|
|
|
274
298
|
/>,
|
|
275
299
|
);
|
|
276
300
|
|
|
277
|
-
expect(screen.getByText(
|
|
278
|
-
expect(screen.
|
|
279
|
-
expect(screen.
|
|
301
|
+
expect(screen.getByText("北京天气")).toBeTruthy();
|
|
302
|
+
expect(screen.getByText("Codex")).toBeTruthy();
|
|
303
|
+
expect(screen.getByText("openai/gpt-5.3-codex")).toBeTruthy();
|
|
304
|
+
expect(screen.getByText("project-alpha")).toBeTruthy();
|
|
305
|
+
expect(screen.getByText("/Users/demo/project-alpha")).toBeTruthy();
|
|
306
|
+
expect(screen.queryByText("Child Sessions")).toBeNull();
|
|
307
|
+
expect(screen.queryByText("child-session-1")).toBeNull();
|
|
308
|
+
expect(mocks.stickyBottomScroll).toHaveBeenCalledWith(
|
|
309
|
+
expect.objectContaining({
|
|
310
|
+
resetKey: "child-session-1",
|
|
311
|
+
stickyThresholdPx: 20,
|
|
312
|
+
}),
|
|
313
|
+
);
|
|
280
314
|
});
|
|
281
315
|
|
|
282
|
-
it(
|
|
316
|
+
it("uses tabs as the only title layer when multiple child sessions are open", () => {
|
|
283
317
|
mocks.resolvedChildTabs = [
|
|
284
318
|
{
|
|
285
|
-
sessionKey:
|
|
286
|
-
parentSessionKey:
|
|
287
|
-
title:
|
|
288
|
-
agentId:
|
|
289
|
-
|
|
290
|
-
|
|
319
|
+
sessionKey: "child-session-1",
|
|
320
|
+
parentSessionKey: "parent-session-1",
|
|
321
|
+
title: "北京天气",
|
|
322
|
+
agentId: "weather",
|
|
323
|
+
sessionTypeLabel: "Codex",
|
|
324
|
+
preferredModel: "openai/gpt-5.3-codex",
|
|
325
|
+
projectName: "project-alpha",
|
|
326
|
+
projectRoot: "/Users/demo/project-alpha",
|
|
291
327
|
},
|
|
292
328
|
{
|
|
293
|
-
sessionKey:
|
|
294
|
-
parentSessionKey:
|
|
295
|
-
title:
|
|
296
|
-
agentId:
|
|
297
|
-
|
|
298
|
-
|
|
329
|
+
sessionKey: "child-session-2",
|
|
330
|
+
parentSessionKey: "parent-session-1",
|
|
331
|
+
title: "上海天气",
|
|
332
|
+
agentId: "weather",
|
|
333
|
+
sessionTypeLabel: "Claude Code",
|
|
334
|
+
preferredModel: "anthropic/claude-sonnet-4",
|
|
335
|
+
projectName: "project-beta",
|
|
336
|
+
projectRoot: "/Users/demo/project-beta",
|
|
299
337
|
},
|
|
300
338
|
];
|
|
301
339
|
|
|
@@ -303,16 +341,16 @@ describe('ChatChildSessionPanel', () => {
|
|
|
303
341
|
<ChatChildSessionPanel
|
|
304
342
|
tabs={[
|
|
305
343
|
{
|
|
306
|
-
sessionKey:
|
|
307
|
-
parentSessionKey:
|
|
308
|
-
label:
|
|
309
|
-
agentId:
|
|
344
|
+
sessionKey: "child-session-1",
|
|
345
|
+
parentSessionKey: "parent-session-1",
|
|
346
|
+
label: "北京天气",
|
|
347
|
+
agentId: "weather",
|
|
310
348
|
},
|
|
311
349
|
{
|
|
312
|
-
sessionKey:
|
|
313
|
-
parentSessionKey:
|
|
314
|
-
label:
|
|
315
|
-
agentId:
|
|
350
|
+
sessionKey: "child-session-2",
|
|
351
|
+
parentSessionKey: "parent-session-1",
|
|
352
|
+
label: "上海天气",
|
|
353
|
+
agentId: "weather",
|
|
316
354
|
},
|
|
317
355
|
]}
|
|
318
356
|
activeSessionKey="child-session-1"
|
|
@@ -322,13 +360,17 @@ describe('ChatChildSessionPanel', () => {
|
|
|
322
360
|
/>,
|
|
323
361
|
);
|
|
324
362
|
|
|
325
|
-
expect(screen.getAllByText(
|
|
326
|
-
expect(screen.getByText(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
);
|
|
363
|
+
expect(screen.getAllByText("北京天气")).toHaveLength(1);
|
|
364
|
+
expect(screen.getByText("上海天气")).toBeTruthy();
|
|
365
|
+
expect(screen.getByText("Codex")).toBeTruthy();
|
|
366
|
+
expect(screen.getByText("openai/gpt-5.3-codex")).toBeTruthy();
|
|
367
|
+
expect(screen.getByText("project-alpha")).toBeTruthy();
|
|
368
|
+
expect(screen.getByText("/Users/demo/project-alpha")).toBeTruthy();
|
|
369
|
+
const tabButtons = screen
|
|
370
|
+
.getAllByRole("button")
|
|
371
|
+
.filter((element) => element.getAttribute("aria-pressed") !== null);
|
|
330
372
|
expect(tabButtons).toHaveLength(2);
|
|
331
|
-
expect(tabButtons[0]?.getAttribute(
|
|
332
|
-
expect(tabButtons[1]?.getAttribute(
|
|
373
|
+
expect(tabButtons[0]?.getAttribute("aria-pressed")).toBe("true");
|
|
374
|
+
expect(tabButtons[1]?.getAttribute("aria-pressed")).toBe("false");
|
|
333
375
|
});
|
|
334
376
|
});
|
|
@@ -33,19 +33,19 @@ function adapt(uiMessages: ChatMessageSource[]) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
it("exposes agentId on
|
|
36
|
+
it("exposes agentId on sessions_spawn call cards when the invocation args include it", () => {
|
|
37
37
|
const adapted = adapt([
|
|
38
38
|
{
|
|
39
|
-
id: "assistant-spawn-call",
|
|
39
|
+
id: "assistant-sessions-spawn-call",
|
|
40
40
|
role: "assistant",
|
|
41
41
|
parts: [
|
|
42
42
|
{
|
|
43
43
|
type: "tool-invocation",
|
|
44
44
|
toolInvocation: {
|
|
45
45
|
status: ToolInvocationStatus.PARTIAL_CALL,
|
|
46
|
-
toolCallId: "spawn-call-args-1",
|
|
47
|
-
toolName: "
|
|
48
|
-
args: '{"agentId":"planner-agent","
|
|
46
|
+
toolCallId: "sessions-spawn-call-args-1",
|
|
47
|
+
toolName: "sessions_spawn",
|
|
48
|
+
args: '{"agentId":"planner-agent","scope":"child","title":"Planner","task":"Plan the rollout","request":{"notify":"final_reply"}}',
|
|
49
49
|
result: {
|
|
50
50
|
kind: "nextclaw.session_request",
|
|
51
51
|
requestId: "request-3",
|
|
@@ -64,7 +64,7 @@ it("exposes agentId on spawn call cards when the invocation args include it", ()
|
|
|
64
64
|
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
65
65
|
type: "tool-card",
|
|
66
66
|
card: {
|
|
67
|
-
toolName: "
|
|
67
|
+
toolName: "sessions_spawn",
|
|
68
68
|
agentId: "planner-agent",
|
|
69
69
|
statusTone: "running",
|
|
70
70
|
},
|
|
@@ -74,16 +74,16 @@ it("exposes agentId on spawn call cards when the invocation args include it", ()
|
|
|
74
74
|
it("exposes agentId on running tool call cards even before a session-request result exists", () => {
|
|
75
75
|
const adapted = adapt([
|
|
76
76
|
{
|
|
77
|
-
id: "assistant-spawn-call-running",
|
|
77
|
+
id: "assistant-sessions-spawn-call-running",
|
|
78
78
|
role: "assistant",
|
|
79
79
|
parts: [
|
|
80
80
|
{
|
|
81
81
|
type: "tool-invocation",
|
|
82
82
|
toolInvocation: {
|
|
83
83
|
status: ToolInvocationStatus.PARTIAL_CALL,
|
|
84
|
-
toolCallId: "spawn-call-running-1",
|
|
85
|
-
toolName: "
|
|
86
|
-
args: '{"agentId":"planner-agent","task":"Plan the rollout"}',
|
|
84
|
+
toolCallId: "sessions-spawn-call-running-1",
|
|
85
|
+
toolName: "sessions_spawn",
|
|
86
|
+
args: '{"agentId":"planner-agent","scope":"child","task":"Plan the rollout"}',
|
|
87
87
|
},
|
|
88
88
|
},
|
|
89
89
|
],
|
|
@@ -93,7 +93,7 @@ it("exposes agentId on running tool call cards even before a session-request res
|
|
|
93
93
|
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
94
94
|
type: "tool-card",
|
|
95
95
|
card: {
|
|
96
|
-
toolName: "
|
|
96
|
+
toolName: "sessions_spawn",
|
|
97
97
|
agentId: "planner-agent",
|
|
98
98
|
statusTone: "running",
|
|
99
99
|
titleLabel: "Tool Call",
|