@nextclaw/ui 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/assets/{api-DPcvCLfa.js → api-a14tR_z1.js} +7 -7
  3. package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
  4. package/dist/assets/chat-page-CEdavJXc.js +105 -0
  5. package/dist/assets/{config-split-page-BCJhqdK9.js → config-split-page-B5b9B0uE.js} +1 -1
  6. package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-ClH_Ec7n.js} +1 -1
  7. package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-DPNDnVpk.js} +1 -1
  8. package/dist/assets/{dist-DPrgcEC0.js → dist-BtCS2Ucq.js} +1 -1
  9. package/dist/assets/{dist-DsE4OsSS.js → dist-CLGGLm8b.js} +1 -1
  10. package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
  11. package/dist/assets/doc-browser-BJRLhztv.js +1 -0
  12. package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
  13. package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-CtJXjIol.js} +1 -1
  14. package/dist/assets/{external-link-C_dmG-WJ.js → external-link-CdJiG13p.js} +1 -1
  15. package/dist/assets/index-BDN1zzMQ.js +105 -0
  16. package/dist/assets/index-g3uSZXoc.css +1 -0
  17. package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-D6hYXROd.js +1 -0
  21. package/dist/assets/plus-DPjb0piO.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
  23. package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
  24. package/dist/assets/{react-sZz5HSCm.js → react-C6cZgP44.js} +1 -1
  25. package/dist/assets/remote-B0G80uPr.js +1 -0
  26. package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
  27. package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
  28. package/dist/assets/{save-MzjoiHri.js → save-DKA0x4zk.js} +1 -1
  29. package/dist/assets/{search-B29dE4GN.js → search-BNlcoj5c.js} +1 -1
  30. package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-Cq9uJ-N8.js} +1 -1
  31. package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page-CxSbZLBo.js} +2 -2
  32. package/dist/assets/{select-BKmh05hd.js → select-CvTlDZTQ.js} +1 -1
  33. package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-8ffF2y2C.js} +1 -1
  34. package/dist/assets/use-config-DmT0o1gv.js +1 -0
  35. package/dist/assets/x-BiSSpgqB.js +1 -0
  36. package/dist/index.html +15 -14
  37. package/package.json +9 -9
  38. package/src/app/components/app-presenter-provider.tsx +11 -2
  39. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  40. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  41. package/src/app/components/layout/app-layout.tsx +18 -4
  42. package/src/app/components/layout/sidebar-items.tsx +194 -71
  43. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  44. package/src/app/components/layout/sidebar.tsx +340 -83
  45. package/src/app/managers/viewport-layout.manager.ts +10 -0
  46. package/src/app/stores/viewport-layout.store.ts +39 -3
  47. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  48. package/src/features/agents/components/agents-page.tsx +10 -6
  49. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  50. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  51. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  52. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  53. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
  54. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
  55. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  56. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  57. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +16 -14
  58. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  59. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +80 -7
  60. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
  61. package/src/features/chat/components/layout/chat-sidebar.tsx +59 -92
  62. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  63. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
  64. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
  65. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
  66. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  67. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
  68. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  69. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  70. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  71. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  72. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  73. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  74. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  75. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  76. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  77. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  78. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  79. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  80. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  81. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  82. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  83. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  84. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  85. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  86. package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
  87. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  88. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  89. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  90. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
  91. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  92. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  93. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  94. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  95. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
  96. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
  97. package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
  98. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
  99. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  100. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
  101. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  102. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
  103. package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
  104. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  105. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  106. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  107. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  108. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  109. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  110. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  111. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  112. package/src/features/chat/index.ts +0 -1
  113. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  114. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  115. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  116. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  117. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  118. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  119. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  120. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  121. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  122. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  123. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  124. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  125. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  126. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  127. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  128. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  129. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  130. package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
  131. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  132. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
  133. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  134. package/src/shared/lib/ui-document-title/index.ts +50 -21
  135. package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
  136. package/dist/assets/chat-page-BBnNJoKG.js +0 -105
  137. package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
  138. package/dist/assets/doc-browser-DgQedeAn.js +0 -1
  139. package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
  140. package/dist/assets/index-7o1WdUbU.js +0 -103
  141. package/dist/assets/index-DQWmEWzk.css +0 -1
  142. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
  143. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
  144. package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
  145. package/dist/assets/plus-Bnt56oqG.js +0 -1
  146. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
  147. package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
  148. package/dist/assets/remote-B0LMs8u7.js +0 -1
  149. package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
  150. package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
  151. package/dist/assets/use-config-DZkBWIdL.js +0 -1
  152. package/dist/assets/x-CW2KO-Hz.js +0 -1
  153. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  154. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  155. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  156. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  157. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  158. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  159. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  160. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  161. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  162. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  163. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  164. package/src/features/chat/stores/chat-input.store.ts +0 -104
  165. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -1,13 +1,10 @@
1
1
  import { render, screen } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { MemoryRouter } from "react-router-dom";
4
+ import type * as ReactRouterDom from "react-router-dom";
4
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
5
6
  import { AgentsPage } from "@/features/agents";
6
- import type * as ChatFeature from "@/features/chat";
7
7
  import {
8
- ChatPresenter,
9
- ChatPresenterProvider,
10
- useChatInputStore,
11
8
  useChatSessionListStore,
12
9
  useChatThreadStore,
13
10
  } from "@/features/chat";
@@ -17,6 +14,8 @@ const mocks = vi.hoisted(() => ({
17
14
  createAgent: vi.fn(),
18
15
  updateAgent: vi.fn(),
19
16
  deleteAgent: vi.fn(),
17
+ navigate: vi.fn(),
18
+ requestDraft: vi.fn(),
20
19
  sessionTypesQuery: {
21
20
  data: {
22
21
  defaultType: "native",
@@ -90,6 +89,35 @@ const mocks = vi.hoisted(() => ({
90
89
  },
91
90
  },
92
91
  }));
92
+ const persistStorage = new Map<string, unknown>();
93
+
94
+ function createPersistStorage() {
95
+ return {
96
+ getItem: (name: string) => persistStorage.get(name) ?? null,
97
+ setItem: (name: string, value: unknown) => {
98
+ persistStorage.set(name, value);
99
+ },
100
+ removeItem: (name: string) => {
101
+ persistStorage.delete(name);
102
+ },
103
+ };
104
+ }
105
+
106
+ vi.mock("react-router-dom", async (importOriginal) => {
107
+ const actual = await importOriginal<typeof ReactRouterDom>();
108
+ return {
109
+ ...actual,
110
+ useNavigate: () => mocks.navigate,
111
+ };
112
+ });
113
+
114
+ vi.mock("@/app/components/app-presenter-provider", () => ({
115
+ useAppPresenter: () => ({
116
+ chatDraftIntentManager: {
117
+ requestDraft: mocks.requestDraft,
118
+ },
119
+ }),
120
+ }));
93
121
 
94
122
  vi.mock("@/shared/hooks/use-agents", () => ({
95
123
  useAgents: () => mocks.agentsQuery,
@@ -147,35 +175,50 @@ vi.mock("@/shared/hooks/use-config", () => ({
147
175
  }),
148
176
  }));
149
177
 
150
- vi.mock("@/features/chat", async (importOriginal) => {
151
- const actual = await importOriginal<typeof ChatFeature>();
178
+ vi.mock("@/features/chat", async () => {
179
+ const sessionListStore = await import("@/features/chat/stores/chat-session-list.store");
180
+ const threadStore = await import("@/features/chat/stores/chat-thread.store");
181
+ const sessionTypeUtils = await import("@/features/chat/features/session-type/utils/chat-session-type.utils");
152
182
  return {
153
- ...actual,
183
+ ...sessionTypeUtils,
184
+ usePresenter: () => ({
185
+ chatSessionListManager: {
186
+ startAgentDraftChat: (agentId: string) => {
187
+ sessionListStore.useChatSessionListStore.getState().setSnapshot({
188
+ selectedAgentId: agentId,
189
+ selectedSessionKey: null,
190
+ });
191
+ threadStore.useChatThreadStore.getState().setSnapshot({
192
+ sessionKey: null,
193
+ });
194
+ },
195
+ },
196
+ }),
197
+ useChatSessionListStore: sessionListStore.useChatSessionListStore,
198
+ useChatThreadStore: threadStore.useChatThreadStore,
154
199
  useNcpChatSessionTypes: () => mocks.sessionTypesQuery,
155
200
  };
156
201
  });
157
202
 
158
203
  function renderAgentsPage() {
159
- const presenter = new ChatPresenter({
160
- docBrowserManager: {
161
- open: vi.fn(),
162
- },
163
- } as unknown as ConstructorParameters<typeof ChatPresenter>[0]);
164
204
  return render(
165
205
  <MemoryRouter>
166
- <ChatPresenterProvider presenter={presenter}>
167
- <AgentsPage />
168
- </ChatPresenterProvider>
206
+ <AgentsPage />
169
207
  </MemoryRouter>,
170
208
  );
171
209
  }
172
210
 
173
211
  describe("AgentsPage", () => {
174
212
  beforeEach(() => {
213
+ persistStorage.clear();
214
+ useChatSessionListStore.persist.setOptions({ storage: createPersistStorage() as never });
215
+ useChatThreadStore.persist.setOptions({ storage: createPersistStorage() as never });
175
216
  setLanguage("zh");
176
217
  mocks.createAgent.mockReset();
177
218
  mocks.updateAgent.mockReset();
178
219
  mocks.deleteAgent.mockReset();
220
+ mocks.navigate.mockReset();
221
+ mocks.requestDraft.mockReset();
179
222
  if (!HTMLElement.prototype.hasPointerCapture) {
180
223
  HTMLElement.prototype.hasPointerCapture = () => false;
181
224
  }
@@ -185,15 +228,6 @@ describe("AgentsPage", () => {
185
228
  if (!HTMLElement.prototype.releasePointerCapture) {
186
229
  HTMLElement.prototype.releasePointerCapture = () => {};
187
230
  }
188
- useChatInputStore.setState({
189
- snapshot: {
190
- ...useChatInputStore.getState().snapshot,
191
- draft: "",
192
- pendingSessionType: "native",
193
- pendingProjectRoot: "/tmp/demo-project",
194
- pendingProjectRootSessionKey: "draft-session",
195
- },
196
- });
197
231
  useChatSessionListStore.setState({
198
232
  snapshot: {
199
233
  ...useChatSessionListStore.getState().snapshot,
@@ -227,13 +261,9 @@ describe("AgentsPage", () => {
227
261
 
228
262
  await user.click(screen.getByRole("button", { name: "新增 Agent" }));
229
263
 
230
- expect(useChatSessionListStore.getState().snapshot.selectedAgentId).toBe(
231
- "main",
232
- );
233
- expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
234
- expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
235
- expect(useChatInputStore.getState().snapshot.draft).toContain(
236
- "请直接创建一个默认示例 Agent,不要问我问题",
264
+ expect(mocks.navigate).toHaveBeenCalledWith("/chat");
265
+ expect(mocks.requestDraft).toHaveBeenCalledWith(
266
+ expect.stringContaining("请直接创建一个默认示例 Agent,不要问我问题"),
237
267
  );
238
268
  expect(mocks.createAgent).not.toHaveBeenCalled();
239
269
  expect(screen.queryByText("创建新的 Agent 身份")).toBeNull();
@@ -281,7 +311,7 @@ describe("AgentsPage", () => {
281
311
  });
282
312
  });
283
313
 
284
- it("starts a draft chat with the agent runtime as the pending session type", async () => {
314
+ it("starts a draft chat with the agent selected through the session list owner", async () => {
285
315
  const user = userEvent.setup();
286
316
 
287
317
  renderAgentsPage();
@@ -294,12 +324,5 @@ describe("AgentsPage", () => {
294
324
  );
295
325
  expect(sessionListSnapshot.selectedSessionKey).toBeNull();
296
326
  expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
297
- expect(useChatInputStore.getState().snapshot.pendingSessionType).toBe(
298
- "codex",
299
- );
300
- expect(useChatInputStore.getState().snapshot.pendingProjectRoot).toBeNull();
301
- expect(
302
- useChatInputStore.getState().snapshot.pendingProjectRootSessionKey,
303
- ).toBeNull();
304
327
  });
305
328
  });
@@ -1,4 +1,5 @@
1
1
  import { useMemo, useState } from "react";
2
+ import { useNavigate } from "react-router-dom";
2
3
  import {
3
4
  useDeleteAgent,
4
5
  useAgents,
@@ -18,6 +19,7 @@ import {
18
19
  usePresenter,
19
20
  useNcpChatSessionTypes,
20
21
  } from "@/features/chat";
22
+ import { useAppPresenter } from "@/app/components/app-presenter-provider";
21
23
  import { AgentAvatar } from "@/shared/components/common/agent-avatar";
22
24
  import { Button } from "@/shared/components/ui/button";
23
25
  import { Card, CardContent } from "@/shared/components/ui/card";
@@ -235,6 +237,8 @@ function AgentListCard(props: {
235
237
 
236
238
  export function AgentsPage() {
237
239
  const presenter = usePresenter();
240
+ const appPresenter = useAppPresenter();
241
+ const navigate = useNavigate();
238
242
  const agentsQuery = useAgents();
239
243
  const configQuery = useConfig();
240
244
  const providersQuery = useProviders();
@@ -309,14 +313,16 @@ export function AgentsPage() {
309
313
  resolveAgentRuntimeSessionType(agent, defaultRuntime),
310
314
  );
311
315
  };
316
+ const requestAgentCreationDraft = () => {
317
+ navigate("/chat");
318
+ appPresenter.chatDraftIntentManager.requestDraft(AGENT_CREATION_PROMPT);
319
+ };
312
320
 
313
321
  return (
314
322
  <PageLayout className="space-y-5">
315
323
  <AgentsHero
316
324
  agentCount={agents.length}
317
- onCreate={() =>
318
- presenter.startAgentCreationDraft(AGENT_CREATION_PROMPT)
319
- }
325
+ onCreate={requestAgentCreationDraft}
320
326
  />
321
327
 
322
328
  <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
@@ -342,9 +348,7 @@ export function AgentsPage() {
342
348
  type="button"
343
349
  variant="primary"
344
350
  className="mt-5 rounded-2xl px-5"
345
- onClick={() =>
346
- presenter.startAgentCreationDraft(AGENT_CREATION_PROMPT)
347
- }
351
+ onClick={requestAgentCreationDraft}
348
352
  >
349
353
  <Plus className="mr-2 h-4 w-4" />
350
354
  {t("agentsCreateButton")}
@@ -0,0 +1,145 @@
1
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import type { PersistStorage, StorageValue } from "zustand/middleware";
5
+ import { beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { ChatConversationHeaderSection } from "@/features/chat/components/conversation/chat-conversation-header-section";
7
+ import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
8
+ import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
9
+ import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
10
+ import type { NcpSessionSummaryView } from "@/shared/lib/api";
11
+
12
+ const mocks = vi.hoisted(() => ({
13
+ deleteSession: vi.fn(),
14
+ openChildSessionPanel: vi.fn(),
15
+ openSessionCronPanel: vi.fn(),
16
+ }));
17
+
18
+ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
19
+ usePresenter: () => ({
20
+ chatThreadManager: {
21
+ deleteSession: mocks.deleteSession,
22
+ openChildSessionPanel: mocks.openChildSessionPanel,
23
+ openSessionCronPanel: mocks.openSessionCronPanel,
24
+ },
25
+ }),
26
+ }));
27
+
28
+ vi.mock("@/features/cron", () => ({
29
+ useCronJobs: () => ({ data: { jobs: [] } }),
30
+ }));
31
+
32
+ class MemoryPersistStorage implements PersistStorage<unknown> {
33
+ private readonly values = new Map<string, StorageValue<unknown>>();
34
+
35
+ getItem = (name: string) => this.values.get(name) ?? null;
36
+
37
+ setItem = (name: string, value: StorageValue<unknown>) => {
38
+ this.values.set(name, value);
39
+ };
40
+
41
+ removeItem = (name: string) => {
42
+ this.values.delete(name);
43
+ };
44
+ }
45
+
46
+ function createSummary(
47
+ overrides: Partial<NcpSessionSummaryView> & Pick<NcpSessionSummaryView, "sessionId">,
48
+ ): NcpSessionSummaryView {
49
+ return {
50
+ messageCount: 1,
51
+ status: "idle",
52
+ updatedAt: "2026-06-18T00:00:00.000Z",
53
+ ...overrides,
54
+ };
55
+ }
56
+
57
+ function renderHeaderSection() {
58
+ const queryClient = new QueryClient({
59
+ defaultOptions: {
60
+ queries: { retry: false },
61
+ mutations: { retry: false },
62
+ },
63
+ });
64
+ return render(
65
+ <QueryClientProvider client={queryClient}>
66
+ <ChatConversationHeaderSection layoutMode="desktop" />
67
+ </QueryClientProvider>,
68
+ );
69
+ }
70
+
71
+ describe("ChatConversationHeaderSection", () => {
72
+ beforeEach(() => {
73
+ mocks.deleteSession.mockReset();
74
+ mocks.openChildSessionPanel.mockReset();
75
+ mocks.openSessionCronPanel.mockReset();
76
+ useChatSessionListStore.persist.setOptions({
77
+ storage: new MemoryPersistStorage(),
78
+ });
79
+ useChatThreadStore.persist.setOptions({
80
+ storage: new MemoryPersistStorage(),
81
+ });
82
+ useChatSessionListStore.setState({
83
+ snapshot: {
84
+ ...useChatSessionListStore.getState().snapshot,
85
+ selectedAgentId: "main",
86
+ selectedSessionKey: "parent-session-1",
87
+ },
88
+ });
89
+ useChatThreadStore.setState({
90
+ snapshot: {
91
+ ...useChatThreadStore.getState().snapshot,
92
+ sessionKey: "parent-session-1",
93
+ childSessionTabs: [],
94
+ workspaceFileTabs: [],
95
+ workspacePanelParentKey: null,
96
+ activeWorkspacePanelKind: null,
97
+ },
98
+ });
99
+ useChatQueryStore.setState({
100
+ snapshot: {
101
+ sessionsQuery: {
102
+ data: {
103
+ sessions: [
104
+ createSummary({
105
+ sessionId: "parent-session-1",
106
+ metadata: {
107
+ label: "Parent Task",
108
+ session_type: "native",
109
+ },
110
+ }),
111
+ createSummary({
112
+ sessionId: "child-session-1",
113
+ agentId: "verifier",
114
+ metadata: {
115
+ label: "Child Task",
116
+ parent_session_id: "parent-session-1",
117
+ session_type: "native",
118
+ },
119
+ }),
120
+ ],
121
+ total: 2,
122
+ },
123
+ } as never,
124
+ sessionTypesQuery: {
125
+ data: {
126
+ defaultType: "native",
127
+ options: [],
128
+ },
129
+ } as never,
130
+ },
131
+ });
132
+ });
133
+
134
+ it("shows the child-session entry from session query state before a tool card opens it", async () => {
135
+ const user = userEvent.setup();
136
+
137
+ renderHeaderSection();
138
+ await user.click(screen.getByRole("button", { name: "View child sessions" }));
139
+
140
+ expect(mocks.openChildSessionPanel).toHaveBeenCalledWith({
141
+ parentSessionKey: "parent-session-1",
142
+ activeChildSessionKey: "child-session-1",
143
+ });
144
+ });
145
+ });