@nextclaw/ui 0.12.23 → 0.12.25

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 (215) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/assets/api-DGD9_Bg4.js +15 -0
  3. package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
  4. package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
  5. package/dist/assets/channels-list-page-FJDuPwU6.js +8 -0
  6. package/dist/assets/chat-page-D1fMNBrT.js +1 -0
  7. package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
  8. package/dist/assets/cpu-DPPwMzoC.js +3 -0
  9. package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
  10. package/dist/assets/desktop-kk7qvZ-v.js +3 -0
  11. package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
  12. package/dist/assets/{dialog-dxsKz7jJ.js → dialog-BKo0RItd.js} +1 -1
  13. package/dist/assets/{dist-DsYTOyq7.js → dist-CFiwgaLs.js} +1 -1
  14. package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
  15. package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
  16. package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
  17. package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
  18. package/dist/assets/download-CMM8po31.js +1 -0
  19. package/dist/assets/{es2015-V75WQJ2s.js → es2015-BhznEEyJ.js} +1 -1
  20. package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
  21. package/dist/assets/i18n-D1144VAA.js +1 -0
  22. package/dist/assets/index-D-AAMKCt.js +103 -0
  23. package/dist/assets/index-DnBeV2Xm.css +1 -0
  24. package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
  25. package/dist/assets/marketplace-page-BrCLRIc4.js +105 -0
  26. package/dist/assets/marketplace-page-odDpPYEs.js +1 -0
  27. package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +1 -0
  28. package/dist/assets/mcp-marketplace-page-DIq_SpMe.js +40 -0
  29. package/dist/assets/model-config-Bc6VVnxy.js +1 -0
  30. package/dist/assets/{notice-card-D1RNsTn_.js → notice-card-Dr6xCwva.js} +1 -1
  31. package/dist/assets/play-AqrNslHI.js +1 -0
  32. package/dist/assets/plus-B-YHtTNC.js +1 -0
  33. package/dist/assets/{popover-BMyiifTA.js → popover-BDFNiLlg.js} +1 -1
  34. package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
  35. package/dist/assets/providers-list-DN0tvISH.js +1 -0
  36. package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
  37. package/dist/assets/remote-Dr3jcfWP.js +1 -0
  38. package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
  39. package/dist/assets/runtime-config-page-CRWOwBbl.js +1 -0
  40. package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
  41. package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
  42. package/dist/assets/search-config-C4c1yZSP.js +1 -0
  43. package/dist/assets/secrets-config-zAF30YfO.js +3 -0
  44. package/dist/assets/{select-DTdzR8j8.js → select-BUTwE_lC.js} +1 -1
  45. package/dist/assets/{setting-row-CvKngoNI.js → setting-row-BavcnXw1.js} +1 -1
  46. package/dist/assets/settings-MWL2SMyk.js +1 -0
  47. package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
  48. package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
  49. package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
  50. package/dist/assets/{tag-chip-BywQeHJj.js → tag-chip-Dm2Lqnpu.js} +1 -1
  51. package/dist/assets/use-config-Cyv5IuSt.js +1 -0
  52. package/dist/assets/use-infinite-scroll-loader-Cvz8ZteY.js +1 -0
  53. package/dist/assets/x-BeyYA_h6.js +1 -0
  54. package/dist/index.html +29 -40
  55. package/package.json +9 -9
  56. package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
  57. package/src/app/components/theme-provider.tsx +1 -0
  58. package/src/app/configs/app-navigation.config.ts +0 -6
  59. package/src/app/index.tsx +4 -7
  60. package/src/features/agents/components/agents-page.test.tsx +25 -15
  61. package/src/features/agents/components/agents-page.tsx +133 -172
  62. package/src/features/channels/components/config/channel-form.test.tsx +1 -0
  63. package/src/features/channels/components/config/channel-form.tsx +4 -3
  64. package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
  65. package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
  66. package/src/features/channels/index.ts +1 -1
  67. package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
  68. package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
  69. package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
  70. package/src/features/chat/components/chat-session-workspace-panel.tsx +34 -2
  71. package/src/features/chat/components/chat-sidebar-session-item.tsx +9 -3
  72. package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +71 -0
  73. package/src/features/chat/components/conversation/chat-conversation-header.tsx +6 -0
  74. package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +181 -61
  75. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +56 -25
  76. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
  77. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +26 -5
  78. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
  79. package/src/features/chat/components/layout/chat-sidebar.test.tsx +119 -8
  80. package/src/features/chat/components/layout/chat-sidebar.tsx +57 -75
  81. package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
  82. package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
  83. package/src/features/chat/hooks/use-hydrated-ncp-agent.test.tsx +6 -0
  84. package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +172 -69
  85. package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +2 -2
  86. package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
  87. package/src/features/chat/hooks/use-ncp-chat-page-data.ts +7 -7
  88. package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
  89. package/src/features/chat/hooks/use-ncp-session-conversation.test.tsx +10 -0
  90. package/src/features/chat/hooks/use-ncp-session-conversation.ts +2 -1
  91. package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
  92. package/src/features/chat/hooks/use-selected-session-context-window-indicator.ts +2 -4
  93. package/src/features/chat/managers/chat-session-list.manager.test.ts +21 -20
  94. package/src/features/chat/managers/chat-session-list.manager.ts +15 -24
  95. package/src/features/chat/managers/ncp-chat-input.manager.test.ts +22 -13
  96. package/src/features/chat/managers/ncp-chat-input.manager.ts +4 -2
  97. package/src/features/chat/managers/ncp-chat-presenter.manager.ts +6 -0
  98. package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
  99. package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
  100. package/src/features/chat/pages/ncp-chat-page.tsx +28 -17
  101. package/src/features/chat/stores/chat-session-list.store.ts +0 -3
  102. package/src/features/chat/stores/chat-thread.store.ts +4 -0
  103. package/src/features/chat/types/chat-stream.types.ts +1 -1
  104. package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
  105. package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
  106. package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
  107. package/src/features/chat/utils/ncp-session-adapter.utils.ts +33 -1
  108. package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
  109. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
  110. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
  111. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
  112. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
  113. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
  114. package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
  115. package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
  116. package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
  117. package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
  118. package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
  119. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  120. package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
  121. package/src/features/marketplace/components/marketplace-page.tsx +154 -132
  122. package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
  123. package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
  124. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
  125. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
  126. package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
  127. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
  128. package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
  129. package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
  130. package/src/features/system-status/components/runtime-control-card.tsx +7 -6
  131. package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
  132. package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
  133. package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
  134. package/src/features/system-status/utils/system-status.utils.ts +31 -6
  135. package/src/index.css +8 -0
  136. package/src/platforms/desktop/components/desktop-app-shell.test.tsx +67 -0
  137. package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
  138. package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
  139. package/src/platforms/desktop/index.ts +6 -0
  140. package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
  141. package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
  142. package/src/shared/components/common/brand-header.tsx +36 -16
  143. package/src/shared/components/config/provider-form-support.ts +2 -22
  144. package/src/shared/components/cron-config.tsx +12 -58
  145. package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
  146. package/src/shared/components/ui/select.tsx +19 -7
  147. package/src/shared/lib/api/channel-auth.types.ts +1 -0
  148. package/src/shared/lib/api/ncp-session-query-cache.test.ts +26 -1
  149. package/src/shared/lib/api/ncp-session-query-cache.ts +5 -1
  150. package/src/shared/lib/api/ncp-session.types.ts +9 -0
  151. package/src/shared/lib/api/types.ts +12 -1
  152. package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
  153. package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
  154. package/src/shared/lib/cron/index.ts +1 -0
  155. package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
  156. package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
  157. package/src/shared/lib/i18n/index.ts +20 -59
  158. package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
  159. package/src/shared/lib/provider-models/index.test.ts +39 -0
  160. package/src/shared/lib/provider-models/index.ts +1 -3
  161. package/src/shared/lib/ui-document-title/index.ts +0 -1
  162. package/tsconfig.json +1 -0
  163. package/vite.config.ts +1 -1
  164. package/vitest.config.ts +1 -1
  165. package/dist/assets/api-BGd3rgv_.js +0 -15
  166. package/dist/assets/app-manager-provider-BuJ_U9eC.js +0 -1
  167. package/dist/assets/app-navigation.config-BTdUuqXS.js +0 -1
  168. package/dist/assets/channels-list-page-BrwymXPe.js +0 -8
  169. package/dist/assets/chat-DGM6K3Qs.js +0 -61
  170. package/dist/assets/chat-page-DpmXMWNS.js +0 -1
  171. package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
  172. package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
  173. package/dist/assets/desktop-update-config-BGKiqc6q.js +0 -1
  174. package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
  175. package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
  176. package/dist/assets/doc-browser-p82AdNO-.js +0 -1
  177. package/dist/assets/folder-CeJKPx5P.js +0 -1
  178. package/dist/assets/hash-BqxRTZW5.js +0 -1
  179. package/dist/assets/i18n-DnTGDIRw.js +0 -1
  180. package/dist/assets/index-BrEdR78s.js +0 -2
  181. package/dist/assets/index-D8MKmXtO.css +0 -1
  182. package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
  183. package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
  184. package/dist/assets/logos-C4sYP1Vl.js +0 -1
  185. package/dist/assets/marketplace-page-B2Pm2RDJ.js +0 -1
  186. package/dist/assets/marketplace-page-CPHxlYL8.js +0 -49
  187. package/dist/assets/mcp-marketplace-page-BcjVmw36.js +0 -1
  188. package/dist/assets/mcp-marketplace-page-CswPXSjf.js +0 -40
  189. package/dist/assets/message-square-z_osm9c0.js +0 -1
  190. package/dist/assets/model-config-Cmruiqdx.js +0 -1
  191. package/dist/assets/play-Dv6Nr1Ew.js +0 -1
  192. package/dist/assets/plus-D8eKFY7h.js +0 -1
  193. package/dist/assets/provider-scoped-model-input-D7ACiMAO.js +0 -1
  194. package/dist/assets/providers-list-gg7LrfuB.js +0 -1
  195. package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
  196. package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
  197. package/dist/assets/remote-Db2M39Cv.js +0 -1
  198. package/dist/assets/runtime-config-page-BT_VV41p.js +0 -1
  199. package/dist/assets/search-config-0VTPpz-w.js +0 -1
  200. package/dist/assets/secrets-config-DwQbLLEy.js +0 -3
  201. package/dist/assets/sessions-config-page-CAG7Zevv.js +0 -2
  202. package/dist/assets/settings-drbWqzA4.js +0 -1
  203. package/dist/assets/skeleton-BK1SOSRA.js +0 -1
  204. package/dist/assets/theme-provider-COAwWFv8.js +0 -2
  205. package/dist/assets/tooltip-BOYp8Ue7.js +0 -1
  206. package/dist/assets/trash-2-CBsHCfqq.js +0 -1
  207. package/dist/assets/use-config-DTwhNDQE.js +0 -1
  208. package/dist/assets/use-confirm-dialog-oeSqhmrx.js +0 -1
  209. package/dist/assets/use-infinite-scroll-loader-X3KGuME8.js +0 -1
  210. package/dist/assets/use-viewport-layout-C0NJAVXs.js +0 -1
  211. package/dist/assets/x-CM-XDMpk.js +0 -1
  212. package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
  213. package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
  214. package/src/features/chat/pages/sessions-config-page.tsx +0 -192
  215. /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
@@ -0,0 +1,70 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { useNcpChatPageData } from './use-ncp-chat-page-data';
4
+
5
+ const useNcpSessionSkillsMock = vi.fn();
6
+
7
+ vi.mock('@/shared/hooks/use-config', () => ({
8
+ useConfig: () => ({
9
+ data: {
10
+ agents: { defaults: {} },
11
+ providers: {}
12
+ },
13
+ isFetched: true,
14
+ isSuccess: true
15
+ }),
16
+ useConfigMeta: () => ({
17
+ data: { providers: [] },
18
+ isFetched: true,
19
+ isSuccess: true
20
+ }),
21
+ useNcpSessions: () => ({
22
+ data: { sessions: [] }
23
+ }),
24
+ useNcpSessionSkills: (params: unknown) => useNcpSessionSkillsMock(params)
25
+ }));
26
+
27
+ vi.mock('./use-ncp-chat-session-types', () => ({
28
+ useNcpChatSessionTypes: () => ({
29
+ data: {
30
+ defaultType: 'native',
31
+ options: [{ value: 'native', label: 'Native' }]
32
+ }
33
+ })
34
+ }));
35
+
36
+ function renderPageData(params: { sessionKey: string | null }) {
37
+ return renderHook(() =>
38
+ useNcpChatPageData({
39
+ sessionKey: params.sessionKey,
40
+ query: '',
41
+ currentSelectedModel: '',
42
+ pendingSessionType: '',
43
+ setPendingSessionType: vi.fn(),
44
+ setSelectedModel: vi.fn(),
45
+ setSelectedThinkingLevel: vi.fn()
46
+ })
47
+ );
48
+ }
49
+
50
+ describe('useNcpChatPageData skills query', () => {
51
+ it('loads draft-session skills before a new chat materializes', () => {
52
+ useNcpSessionSkillsMock.mockReturnValue({ data: { records: [] }, isLoading: false });
53
+
54
+ renderPageData({ sessionKey: null });
55
+
56
+ expect(useNcpSessionSkillsMock).toHaveBeenCalledWith({
57
+ sessionId: 'draft-session'
58
+ });
59
+ });
60
+
61
+ it('loads real session skills after materialization', () => {
62
+ useNcpSessionSkillsMock.mockReturnValue({ data: { records: [] }, isLoading: false });
63
+
64
+ renderPageData({ sessionKey: 'session-1' });
65
+
66
+ expect(useNcpSessionSkillsMock).toHaveBeenCalledWith({
67
+ sessionId: 'session-1'
68
+ });
69
+ });
70
+ });
@@ -23,7 +23,7 @@ export type { ChatModelOption } from '@/features/chat/types/chat-input.types';
23
23
 
24
24
  type UseNcpChatPageDataParams = {
25
25
  query: string;
26
- sessionKey: string;
26
+ sessionKey: string | null;
27
27
  projectRootOverride?: string | null;
28
28
  currentSelectedModel: string;
29
29
  pendingSessionType: string;
@@ -76,7 +76,7 @@ function useNcpChatModelOptions(params: {
76
76
 
77
77
  function useRecentSessionPreferences(params: {
78
78
  sessions: SessionEntryView[];
79
- sessionKey: string;
79
+ sessionKey: string | null;
80
80
  sessionType: string;
81
81
  }) {
82
82
  const { sessions, sessionKey, sessionType } = params;
@@ -84,7 +84,7 @@ function useRecentSessionPreferences(params: {
84
84
  () =>
85
85
  resolveRecentSessionPreferredValue<string>({
86
86
  sessions,
87
- selectedSessionKey: sessionKey,
87
+ selectedSessionKey: sessionKey ?? '',
88
88
  sessionType,
89
89
  readPreference: (session) => session.preferredModel?.trim() || undefined
90
90
  }),
@@ -94,7 +94,7 @@ function useRecentSessionPreferences(params: {
94
94
  () =>
95
95
  resolveRecentSessionPreferredValue<ThinkingLevel>({
96
96
  sessions,
97
- selectedSessionKey: sessionKey,
97
+ selectedSessionKey: sessionKey ?? '',
98
98
  sessionType,
99
99
  readPreference: (session) => session.preferredThinking ?? undefined
100
100
  }),
@@ -158,7 +158,7 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
158
158
  const sessionsQuery = useNcpSessions({ limit: 200 });
159
159
  const sessionTypesQuery = useNcpChatSessionTypes();
160
160
  const sessionSkillsQuery = useNcpSessionSkills({
161
- sessionId: sessionKey,
161
+ sessionId: sessionKey?.trim() || 'draft-session',
162
162
  ...(Object.prototype.hasOwnProperty.call(params, 'projectRootOverride')
163
163
  ? { projectRoot: projectRootOverride ?? null }
164
164
  : {})
@@ -219,7 +219,7 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
219
219
 
220
220
  useSyncSelectedModel({
221
221
  modelOptions: filteredModelOptions,
222
- selectedSessionKey: sessionKey,
222
+ selectedSessionKey: sessionKey ?? '',
223
223
  selectedSessionExists: Boolean(selectedSession),
224
224
  selectedSessionPreferredModel: selectedSession?.preferredModel,
225
225
  fallbackPreferredModel: sessionTypeState.selectedSessionTypeOption?.recommendedModel ?? recentSessionPreferredModel,
@@ -228,7 +228,7 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
228
228
  });
229
229
  useSyncSelectedThinking({
230
230
  supportedThinkingLevels,
231
- selectedSessionKey: sessionKey,
231
+ selectedSessionKey: sessionKey ?? '',
232
232
  selectedSessionExists: Boolean(selectedSession),
233
233
  selectedSessionPreferredThinking: selectedSession?.preferredThinking ?? null,
234
234
  fallbackPreferredThinking: recentSessionPreferredThinking ?? null,
@@ -49,16 +49,10 @@ export function useNcpChildSessionTabsView(
49
49
  return new Map(sessions.map((session) => [session.key, session]));
50
50
  }, [summaries]);
51
51
 
52
- const summaryByKey = useMemo(
53
- () => new Map(summaries.map((summary) => [summary.sessionId, summary])),
54
- [summaries],
55
- );
56
-
57
52
  return useMemo(
58
53
  () =>
59
54
  tabs.map((tab) => {
60
55
  const session = sessionByKey.get(tab.sessionKey) ?? null;
61
- const summary = summaryByKey.get(tab.sessionKey) ?? null;
62
56
  const agentId = tab.agentId?.trim() || session?.agentId || null;
63
57
  return {
64
58
  sessionKey: tab.sessionKey,
@@ -68,7 +62,7 @@ export function useNcpChildSessionTabsView(
68
62
  updatedAt: session?.updatedAt ?? null,
69
63
  lastMessageAt: session?.lastMessageAt ?? null,
70
64
  readAt: session?.readAt ?? null,
71
- runStatus: summary?.status === "running" ? "running" : undefined,
65
+ runStatus: session?.status === "running" ? "running" : undefined,
72
66
  sessionTypeLabel: session?.sessionType
73
67
  ? resolveSessionTypeLabel(session.sessionType)
74
68
  : null,
@@ -77,6 +71,6 @@ export function useNcpChildSessionTabsView(
77
71
  projectRoot: session?.projectRoot?.trim() || null,
78
72
  };
79
73
  }),
80
- [sessionByKey, summaryByKey, tabs],
74
+ [sessionByKey, tabs],
81
75
  );
82
76
  }
@@ -146,6 +146,16 @@ describe("useNcpSessionConversation", () => {
146
146
  expect(mocks.hydratedCalls[1]?.client).toBe(mocks.clientInstances[1]);
147
147
  });
148
148
 
149
+ it("passes an empty session through without requesting a draft history seed", () => {
150
+ renderHook(() => useNcpSessionConversation(undefined));
151
+
152
+ expect(mocks.useHydratedNcpAgent).toHaveBeenCalledTimes(1);
153
+ expect(mocks.hydratedCalls[0]).toMatchObject({
154
+ sessionId: undefined,
155
+ });
156
+ expect(mocks.fetchNcpSessionMessages).not.toHaveBeenCalled();
157
+ });
158
+
149
159
  it("exposes the hydrated session context window without changing the generic ncp agent seed", async () => {
150
160
  const contextWindow = {
151
161
  usedContextTokens: 42,
@@ -93,7 +93,7 @@ function useSyncReadyRetryVersion(
93
93
  }
94
94
 
95
95
  export function useNcpSessionConversation(
96
- sessionId: string,
96
+ sessionId: string | undefined,
97
97
  options: UseNcpSessionConversationOptions = {},
98
98
  ) {
99
99
  const [client] = useState(() => createNcpSessionConversationClient());
@@ -126,6 +126,7 @@ export function useNcpSessionConversation(
126
126
  const currentAgentError =
127
127
  agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
128
128
  const readyRetrySignature =
129
+ sessionId &&
129
130
  systemStatus.phase === "ready" &&
130
131
  isNcpAgentStartupUnavailableErrorMessage(currentAgentError)
131
132
  ? `${sessionId}:${systemStatus.lastReadyAt ?? 0}`
@@ -32,11 +32,10 @@ export function useNcpSessionListView(params: { limit?: number } = {}) {
32
32
  shouldShowSessionInSidebar,
33
33
  );
34
34
  const filteredSessions = filterSessionsByQuery(sessions, query);
35
- const summaryBySessionId = new Map(summaries.map((summary) => [summary.sessionId, summary]));
36
35
 
37
36
  return filteredSessions.map((session) => ({
38
37
  session,
39
- runStatus: summaryBySessionId.get(session.key)?.status === 'running' ? 'running' : undefined
38
+ runStatus: session.status === 'running' ? 'running' : undefined
40
39
  }));
41
40
  }, [query, sessionsQuery.data?.sessions]);
42
41
 
@@ -6,15 +6,13 @@ import { buildChatContextWindowIndicator } from '@/features/chat/utils/chat-cont
6
6
 
7
7
  export function useSelectedSessionContextWindowIndicator(): ChatContextWindowIndicator | null {
8
8
  const selectedSessionKey = useChatSessionListStore((state) => state.snapshot.selectedSessionKey);
9
- const draftSessionKey = useChatSessionListStore((state) => state.snapshot.draftSessionKey);
10
9
  const liveSessionKey = useChatThreadStore((state) => state.snapshot.sessionKey);
11
10
  const liveContextWindow = useChatThreadStore((state) => state.snapshot.contextWindow);
12
- const currentSessionKey = selectedSessionKey ?? draftSessionKey;
13
11
 
14
12
  return useMemo(() => {
15
- if (liveSessionKey === currentSessionKey && liveContextWindow) {
13
+ if (selectedSessionKey && liveSessionKey === selectedSessionKey && liveContextWindow) {
16
14
  return buildChatContextWindowIndicator(liveContextWindow);
17
15
  }
18
16
  return null;
19
- }, [currentSessionKey, liveContextWindow, liveSessionKey]);
17
+ }, [liveContextWindow, liveSessionKey, selectedSessionKey]);
20
18
  }
@@ -1,4 +1,5 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import type * as SharedApi from '@/shared/lib/api';
2
3
  import { ChatSessionListManager } from '@/features/chat/managers/chat-session-list.manager';
3
4
  import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
4
5
  import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
@@ -9,7 +10,7 @@ const mocks = vi.hoisted(() => ({
9
10
  }));
10
11
 
11
12
  vi.mock('@/shared/lib/api', async (importOriginal) => {
12
- const actual = await importOriginal<typeof import('@/shared/lib/api')>();
13
+ const actual = await importOriginal<typeof SharedApi>();
13
14
  return {
14
15
  ...actual,
15
16
  updateNcpSession: mocks.updateNcpSession,
@@ -34,7 +35,6 @@ describe('ChatSessionListManager', () => {
34
35
  snapshot: {
35
36
  ...useChatSessionListStore.getState().snapshot,
36
37
  selectedSessionKey: 'session-1',
37
- draftSessionKey: 'draft-root-1',
38
38
  listMode: 'time-first'
39
39
  }
40
40
  });
@@ -59,13 +59,13 @@ describe('ChatSessionListManager', () => {
59
59
  } as unknown as ConstructorParameters<typeof ChatSessionListManager>[1];
60
60
 
61
61
  const manager = new ChatSessionListManager(uiManager, streamActionsManager);
62
- const sessionKey = manager.createSession('codex');
62
+ manager.createSession('codex');
63
63
 
64
64
  expect(streamActionsManager.resetStreamState).toHaveBeenCalledTimes(1);
65
65
  expect(uiManager.goToChatRoot).toHaveBeenCalledTimes(1);
66
66
  expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
67
- expect(useChatSessionListStore.getState().snapshot.draftSessionKey).toBe(sessionKey);
68
- expect(useChatSessionListStore.getState().snapshot.draftSessionKey).not.toBe('draft-root-1');
67
+ expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
68
+ expect(useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage).toBe(false);
69
69
  expect(useChatInputStore.getState().snapshot.pendingSessionType).toBe('codex');
70
70
  expect(useChatInputStore.getState().snapshot.pendingProjectRoot).toBeNull();
71
71
  expect(useChatInputStore.getState().snapshot.pendingProjectRootSessionKey).toBeNull();
@@ -82,14 +82,14 @@ describe('ChatSessionListManager', () => {
82
82
  } as unknown as ConstructorParameters<typeof ChatSessionListManager>[1];
83
83
 
84
84
  const manager = new ChatSessionListManager(uiManager, streamActionsManager);
85
- const sessionKey = manager.startAgentDraftChat('researcher', 'codex');
85
+ manager.startAgentDraftChat('researcher', 'codex');
86
86
 
87
87
  expect(streamActionsManager.resetStreamState).toHaveBeenCalledTimes(1);
88
88
  expect(uiManager.goToChatRoot).toHaveBeenCalledTimes(1);
89
89
  expect(useChatSessionListStore.getState().snapshot.selectedAgentId).toBe('researcher');
90
90
  expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
91
- expect(useChatSessionListStore.getState().snapshot.draftSessionKey).toBe(sessionKey);
92
- expect(useChatThreadStore.getState().snapshot.sessionKey).toBe(sessionKey);
91
+ expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
92
+ expect(useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage).toBe(false);
93
93
  expect(useChatInputStore.getState().snapshot.pendingSessionType).toBe('codex');
94
94
  expect(useChatInputStore.getState().snapshot.pendingProjectRoot).toBeNull();
95
95
  expect(useChatInputStore.getState().snapshot.pendingProjectRootSessionKey).toBeNull();
@@ -106,18 +106,17 @@ describe('ChatSessionListManager', () => {
106
106
  } as unknown as ConstructorParameters<typeof ChatSessionListManager>[1];
107
107
 
108
108
  const manager = new ChatSessionListManager(uiManager, streamActionsManager);
109
- const sessionKey = manager.createSession('native', '/tmp/project-alpha');
109
+ manager.createSession('native', '/tmp/project-alpha');
110
110
 
111
111
  expect(useChatInputStore.getState().snapshot.pendingProjectRoot).toBe('/tmp/project-alpha');
112
- expect(useChatInputStore.getState().snapshot.pendingProjectRootSessionKey).toBe(sessionKey);
112
+ expect(useChatInputStore.getState().snapshot.pendingProjectRootSessionKey).toBeNull();
113
113
  });
114
114
 
115
- it('reuses the current root draft when send flow needs a concrete session key', () => {
115
+ it('keeps the root draft key empty when send flow has no concrete session yet', () => {
116
116
  useChatSessionListStore.setState({
117
117
  snapshot: {
118
118
  ...useChatSessionListStore.getState().snapshot,
119
- selectedSessionKey: null,
120
- draftSessionKey: 'draft-root-2'
119
+ selectedSessionKey: null
121
120
  }
122
121
  });
123
122
  const uiManager = {
@@ -132,10 +131,11 @@ describe('ChatSessionListManager', () => {
132
131
  const manager = new ChatSessionListManager(uiManager, streamActionsManager);
133
132
  const sessionKey = manager.ensureDraftSession('native');
134
133
 
135
- expect(sessionKey).toBe('draft-root-2');
134
+ expect(sessionKey).toBeNull();
136
135
  expect(uiManager.goToChatRoot).not.toHaveBeenCalled();
137
136
  expect(uiManager.goToSession).not.toHaveBeenCalled();
138
137
  expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
138
+ expect(useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage).toBe(true);
139
139
  });
140
140
 
141
141
  it('does not eagerly replace the old selected session before the route finishes switching', () => {
@@ -149,10 +149,10 @@ describe('ChatSessionListManager', () => {
149
149
  } as unknown as ConstructorParameters<typeof ChatSessionListManager>[1];
150
150
 
151
151
  const manager = new ChatSessionListManager(uiManager, streamActionsManager);
152
- const sessionKey = manager.createSession('native', '/tmp/project-alpha');
152
+ manager.createSession('native', '/tmp/project-alpha');
153
153
 
154
154
  expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
155
- expect(useChatInputStore.getState().snapshot.pendingProjectRootSessionKey).toBe(sessionKey);
155
+ expect(useChatInputStore.getState().snapshot.pendingProjectRootSessionKey).toBeNull();
156
156
  });
157
157
 
158
158
  it('delegates existing-session selection to routing without eagerly mutating the selected session state', () => {
@@ -224,12 +224,11 @@ describe('ChatSessionListManager', () => {
224
224
  expect(mocks.updateNcpSession).not.toHaveBeenCalled();
225
225
  });
226
226
 
227
- it('promotes the active root draft to a concrete session route after the draft has been sent successfully', () => {
227
+ it('routes to the backend-materialized root session without duplicating route-owned selection state', () => {
228
228
  useChatSessionListStore.setState({
229
229
  snapshot: {
230
230
  ...useChatSessionListStore.getState().snapshot,
231
231
  selectedSessionKey: null,
232
- draftSessionKey: 'draft-root-2',
233
232
  }
234
233
  });
235
234
  const uiManager = {
@@ -243,8 +242,10 @@ describe('ChatSessionListManager', () => {
243
242
  const manager = new ChatSessionListManager(uiManager, streamActionsManager);
244
243
 
245
244
  manager.ensureDraftSession('native');
246
- manager.promoteRootDraftSessionRoute('draft-root-2');
245
+ manager.materializeRootSessionRoute('ncp-materialized-session');
247
246
 
248
- expect(uiManager.goToSession).toHaveBeenCalledWith('draft-root-2', { replace: true });
247
+ expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
248
+ expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
249
+ expect(uiManager.goToSession).toHaveBeenCalledWith('ncp-materialized-session', { replace: true });
249
250
  });
250
251
  });
@@ -5,7 +5,6 @@ import type { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
5
5
  import type { SetStateAction } from 'react';
6
6
  import type { ChatStreamActionsManager } from '@/features/chat/managers/chat-stream-actions.manager';
7
7
  import { normalizeSessionProjectRootValue } from '@/shared/lib/session-project';
8
- import { createNcpSessionId } from '@/features/chat/utils/ncp-session-adapter.utils';
9
8
  import { updateNcpSession } from '@/shared/lib/api';
10
9
  export class ChatSessionListManager {
11
10
  constructor(
@@ -13,18 +12,20 @@ export class ChatSessionListManager {
13
12
  private streamActionsManager: ChatStreamActionsManager
14
13
  ) {}
15
14
 
16
- private syncDraftThreadState = (sessionKey: string) => {
15
+ private syncDraftThreadState = (hasSubmittedDraftMessage = false) => {
17
16
  useChatThreadStore.getState().setSnapshot({
18
- sessionKey,
17
+ sessionKey: null,
19
18
  sessionDisplayName: undefined,
20
19
  canDeleteSession: false,
21
20
  isHistoryLoading: false,
22
21
  messages: [],
23
22
  isSending: false,
24
23
  isAwaitingAssistantOutput: false,
24
+ hasSubmittedDraftMessage,
25
25
  parentSessionKey: null,
26
26
  parentSessionLabel: null,
27
27
  workspacePanelParentKey: null,
28
+ activeWorkspacePanelKind: null,
28
29
  childSessionTabs: [],
29
30
  activeChildSessionKey: null,
30
31
  activeWorkspaceFileKey: null,
@@ -97,7 +98,7 @@ export class ChatSessionListManager {
97
98
  void updateNcpSession(normalizedSessionKey, { uiReadAt: normalizedReadAt }).catch(() => undefined);
98
99
  };
99
100
 
100
- createSession = (sessionType?: string, projectRoot?: string | null): string => {
101
+ createSession = (sessionType?: string, projectRoot?: string | null): void => {
101
102
  const { snapshot } = useChatInputStore.getState();
102
103
  const { defaultSessionType: configuredDefaultSessionType } = snapshot;
103
104
  const defaultSessionType = configuredDefaultSessionType || 'native';
@@ -106,30 +107,26 @@ export class ChatSessionListManager {
106
107
  ? sessionType.trim()
107
108
  : defaultSessionType;
108
109
  const normalizedProjectRoot = normalizeSessionProjectRootValue(projectRoot);
109
- const nextSessionKey = createNcpSessionId();
110
110
  this.streamActionsManager.resetStreamState();
111
111
  useChatSessionListStore.getState().setSnapshot({
112
112
  selectedSessionKey: null,
113
- draftSessionKey: nextSessionKey
114
113
  });
115
- this.syncDraftThreadState(nextSessionKey);
114
+ this.syncDraftThreadState();
116
115
  useChatInputStore.getState().setSnapshot({
117
116
  pendingSessionType: nextSessionType,
118
117
  pendingProjectRoot: normalizedProjectRoot,
119
- pendingProjectRootSessionKey: normalizedProjectRoot ? nextSessionKey : null
118
+ pendingProjectRootSessionKey: null
120
119
  });
121
120
  this.uiManager.goToChatRoot();
122
- return nextSessionKey;
123
121
  };
124
122
 
125
- startAgentDraftChat = (agentId: string, sessionType: string): string => {
123
+ startAgentDraftChat = (agentId: string, sessionType: string): void => {
126
124
  const normalizedAgentId = agentId.trim() || 'main';
127
- const nextSessionKey = this.createSession(sessionType);
125
+ this.createSession(sessionType);
128
126
  this.setSelectedAgentId(normalizedAgentId);
129
- return nextSessionKey;
130
127
  };
131
128
 
132
- ensureDraftSession = (sessionType?: string): string => {
129
+ ensureDraftSession = (sessionType?: string): string | null => {
133
130
  const { snapshot } = useChatSessionListStore.getState();
134
131
  if (snapshot.selectedSessionKey) {
135
132
  return snapshot.selectedSessionKey;
@@ -138,26 +135,19 @@ export class ChatSessionListManager {
138
135
  typeof sessionType === 'string' && sessionType.trim().length > 0
139
136
  ? sessionType.trim()
140
137
  : null;
141
- this.syncDraftThreadState(snapshot.draftSessionKey);
138
+ this.syncDraftThreadState(true);
142
139
  if (normalizedSessionType) {
143
140
  useChatInputStore.getState().setSnapshot({ pendingSessionType: normalizedSessionType });
144
141
  }
145
- return snapshot.draftSessionKey;
142
+ return null;
146
143
  };
147
144
 
148
- promoteRootDraftSessionRoute = (sessionKey: string) => {
145
+ materializeRootSessionRoute = (sessionKey: string) => {
149
146
  const normalizedSessionKey = sessionKey.trim();
150
147
  if (!normalizedSessionKey) {
151
148
  return;
152
149
  }
153
- const { snapshot } = useChatSessionListStore.getState();
154
- const { sessionKey: currentThreadSessionKey } = useChatThreadStore.getState().snapshot;
155
- if (
156
- snapshot.selectedSessionKey !== null ||
157
- snapshot.draftSessionKey !== normalizedSessionKey ||
158
- currentThreadSessionKey !== normalizedSessionKey ||
159
- !this.uiManager.isAtChatRoot()
160
- ) {
150
+ if (!this.uiManager.isAtChatRoot()) {
161
151
  return;
162
152
  }
163
153
  this.uiManager.goToSession(normalizedSessionKey, { replace: true });
@@ -166,6 +156,7 @@ export class ChatSessionListManager {
166
156
  selectSession = (sessionKey: string) => {
167
157
  useChatThreadStore.getState().setSnapshot({
168
158
  workspacePanelParentKey: null,
159
+ activeWorkspacePanelKind: null,
169
160
  activeChildSessionKey: null,
170
161
  activeWorkspaceFileKey: null,
171
162
  });
@@ -58,7 +58,6 @@ describe('NcpChatInputManager', () => {
58
58
  snapshot: {
59
59
  ...useChatSessionListStore.getState().snapshot,
60
60
  selectedSessionKey: 'stale-selected-session',
61
- draftSessionKey: 'draft-root-session',
62
61
  selectedAgentId: 'main',
63
62
  },
64
63
  });
@@ -77,7 +76,7 @@ describe('NcpChatInputManager', () => {
77
76
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[1];
78
77
  const sessionListManager = {
79
78
  ensureDraftSession: vi.fn(() => 'draft-session'),
80
- promoteRootDraftSessionRoute: vi.fn(),
79
+ materializeRootSessionRoute: vi.fn(),
81
80
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[2];
82
81
  const manager = new NcpChatInputManager(
83
82
  {} as ConstructorParameters<typeof NcpChatInputManager>[0],
@@ -95,14 +94,20 @@ describe('NcpChatInputManager', () => {
95
94
  }),
96
95
  );
97
96
  expect(sessionListManager.ensureDraftSession).not.toHaveBeenCalled();
98
- expect(sessionListManager.promoteRootDraftSessionRoute).toHaveBeenCalledWith('current-route-session');
97
+ expect(sessionListManager.materializeRootSessionRoute).not.toHaveBeenCalled();
99
98
  });
100
99
 
101
- it('keeps sending through the current root draft session while /chat is still in blank-draft mode', async () => {
100
+ it('sends without a session key while /chat is still in blank-draft mode', async () => {
102
101
  useChatThreadStore.setState({
103
102
  snapshot: {
104
103
  ...useChatThreadStore.getState().snapshot,
105
- sessionKey: 'draft-root-session',
104
+ sessionKey: null,
105
+ },
106
+ });
107
+ useChatSessionListStore.setState({
108
+ snapshot: {
109
+ ...useChatSessionListStore.getState().snapshot,
110
+ selectedSessionKey: null,
106
111
  },
107
112
  });
108
113
  const streamActionsManager = {
@@ -111,7 +116,7 @@ describe('NcpChatInputManager', () => {
111
116
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[1];
112
117
  const sessionListManager = {
113
118
  ensureDraftSession: vi.fn(() => 'materialized-draft-session'),
114
- promoteRootDraftSessionRoute: vi.fn(),
119
+ materializeRootSessionRoute: vi.fn(),
115
120
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[2];
116
121
  const manager = new NcpChatInputManager(
117
122
  {} as ConstructorParameters<typeof NcpChatInputManager>[0],
@@ -121,14 +126,18 @@ describe('NcpChatInputManager', () => {
121
126
 
122
127
  await manager.send();
123
128
 
124
- expect(sessionListManager.ensureDraftSession).not.toHaveBeenCalled();
129
+ expect(sessionListManager.ensureDraftSession).toHaveBeenCalledWith('native');
130
+ expect(streamActionsManager.sendMessage).toHaveBeenCalledWith(
131
+ expect.not.objectContaining({
132
+ sessionKey: expect.any(String),
133
+ }),
134
+ );
125
135
  expect(streamActionsManager.sendMessage).toHaveBeenCalledWith(
126
136
  expect.objectContaining({
127
- sessionKey: 'draft-root-session',
128
137
  message: 'hello from current thread',
129
138
  }),
130
139
  );
131
- expect(sessionListManager.promoteRootDraftSessionRoute).toHaveBeenCalledWith('draft-root-session');
140
+ expect(sessionListManager.materializeRootSessionRoute).not.toHaveBeenCalled();
132
141
  });
133
142
 
134
143
  it('does not send while the runtime is still blocked during startup', async () => {
@@ -158,7 +167,7 @@ describe('NcpChatInputManager', () => {
158
167
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[1];
159
168
  const sessionListManager = {
160
169
  ensureDraftSession: vi.fn(() => 'draft-session'),
161
- promoteRootDraftSessionRoute: vi.fn(),
170
+ materializeRootSessionRoute: vi.fn(),
162
171
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[2];
163
172
  const manager = new NcpChatInputManager(
164
173
  {} as ConstructorParameters<typeof NcpChatInputManager>[0],
@@ -169,7 +178,7 @@ describe('NcpChatInputManager', () => {
169
178
  await manager.send();
170
179
 
171
180
  expect(streamActionsManager.sendMessage).not.toHaveBeenCalled();
172
- expect(sessionListManager.promoteRootDraftSessionRoute).not.toHaveBeenCalled();
181
+ expect(sessionListManager.materializeRootSessionRoute).not.toHaveBeenCalled();
173
182
  });
174
183
 
175
184
  it('still attempts to send when provider metadata is stale or the session type is marked unavailable', async () => {
@@ -187,7 +196,7 @@ describe('NcpChatInputManager', () => {
187
196
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[1];
188
197
  const sessionListManager = {
189
198
  ensureDraftSession: vi.fn(() => 'draft-session'),
190
- promoteRootDraftSessionRoute: vi.fn(),
199
+ materializeRootSessionRoute: vi.fn(),
191
200
  } as unknown as ConstructorParameters<typeof NcpChatInputManager>[2];
192
201
  const manager = new NcpChatInputManager(
193
202
  {} as ConstructorParameters<typeof NcpChatInputManager>[0],
@@ -198,6 +207,6 @@ describe('NcpChatInputManager', () => {
198
207
  await manager.send();
199
208
 
200
209
  expect(streamActionsManager.sendMessage).toHaveBeenCalledTimes(1);
201
- expect(sessionListManager.promoteRootDraftSessionRoute).toHaveBeenCalledWith('current-route-session');
210
+ expect(sessionListManager.materializeRootSessionRoute).not.toHaveBeenCalled();
202
211
  });
203
212
  });
@@ -197,11 +197,14 @@ export class NcpChatInputManager {
197
197
  const sessionKey =
198
198
  threadSnapshot.sessionKey ??
199
199
  sessionSnapshot.selectedSessionKey ??
200
+ null;
201
+ if (!sessionKey && inputSnapshot.selectedSessionType?.trim()) {
200
202
  this.sessionListManager.ensureDraftSession(inputSnapshot.selectedSessionType);
203
+ }
201
204
  this.setComposerNodes(createInitialChatComposerNodes());
202
205
  await this.streamActionsManager.sendMessage({
203
206
  message,
204
- sessionKey,
207
+ ...(sessionKey ? { sessionKey } : {}),
205
208
  agentId: sessionSnapshot.selectedAgentId,
206
209
  sessionType: inputSnapshot.selectedSessionType,
207
210
  model: inputSnapshot.selectedModel || undefined,
@@ -213,7 +216,6 @@ export class NcpChatInputManager {
213
216
  restoreDraftOnError: true,
214
217
  composerNodes
215
218
  });
216
- this.sessionListManager.promoteRootDraftSessionRoute(sessionKey);
217
219
  };
218
220
 
219
221
  stop = async () => {
@@ -18,4 +18,10 @@ export class NcpChatPresenter {
18
18
  this.chatSessionListManager,
19
19
  this.chatStreamActionsManager
20
20
  );
21
+
22
+ startAgentCreationDraft = (prompt: string) => {
23
+ this.chatSessionListManager.createSession();
24
+ this.chatSessionListManager.setSelectedAgentId('main');
25
+ this.chatInputManager.setDraft(prompt);
26
+ };
21
27
  }