@nextclaw/ui 0.12.24 → 0.12.26

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 (210) hide show
  1. package/CHANGELOG.md +136 -29
  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-HgLgrEg4.js +8 -0
  6. package/dist/assets/chat-page-DAKMFDrS.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-DVUbOWbR.js +3 -0
  11. package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
  12. package/dist/assets/{dialog-C3D7Be0p.js → dialog-BKo0RItd.js} +1 -1
  13. package/dist/assets/{dist-CPlbUgwU.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-xqN1slyW.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-Cuwst6cc.js +100 -0
  23. package/dist/assets/index-dlcqieQ0.css +1 -0
  24. package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
  25. package/dist/assets/marketplace-page-BeFbwxR-.js +105 -0
  26. package/dist/assets/marketplace-page-CR4xq-TM.js +1 -0
  27. package/dist/assets/mcp-marketplace-page-DlRrSCj3.js +1 -0
  28. package/dist/assets/mcp-marketplace-page-DwnaLNTx.js +40 -0
  29. package/dist/assets/model-config-L2l6YAlQ.js +1 -0
  30. package/dist/assets/{notice-card-BFDbKQDA.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-B86Dbfhf.js → popover-BDFNiLlg.js} +1 -1
  34. package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
  35. package/dist/assets/providers-list-DYAEunOp.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-BdeU8PEK.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-CQUhd5RU.js +1 -0
  43. package/dist/assets/secrets-config-D-NWlW9q.js +3 -0
  44. package/dist/assets/{select-CJ0wbo3D.js → select-BUTwE_lC.js} +1 -1
  45. package/dist/assets/{setting-row-D1Yygqp7.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-FrkmkT8r.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-CFVdPpNv.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 +53 -35
  71. package/src/features/chat/components/chat-sidebar-session-item.tsx +16 -12
  72. package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +74 -0
  73. package/src/features/chat/components/conversation/chat-conversation-header.tsx +8 -2
  74. package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +262 -114
  75. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +210 -174
  76. package/src/features/chat/components/conversation/chat-input-bar.container.tsx +11 -1
  77. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
  78. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +27 -6
  79. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
  80. package/src/features/chat/components/layout/chat-sidebar.test.tsx +45 -8
  81. package/src/features/chat/components/layout/chat-sidebar.tsx +29 -46
  82. package/src/features/chat/components/providers/chat-presenter.provider.tsx +4 -0
  83. package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
  84. package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +153 -80
  85. package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
  86. package/src/features/chat/hooks/use-ncp-chat-page-data.ts +1 -1
  87. package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
  88. package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
  89. package/src/features/chat/managers/chat-session-list.manager.test.ts +7 -9
  90. package/src/features/chat/managers/chat-session-list.manager.ts +5 -10
  91. package/src/features/chat/managers/ncp-chat-input.manager.test.ts +20 -2
  92. package/src/features/chat/managers/ncp-chat-input.manager.ts +18 -0
  93. package/src/features/chat/managers/ncp-chat-presenter.manager.ts +7 -0
  94. package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
  95. package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
  96. package/src/features/chat/pages/ncp-chat-page.tsx +9 -5
  97. package/src/features/chat/stores/chat-input.store.ts +3 -1
  98. package/src/features/chat/stores/chat-session-list.store.ts +0 -2
  99. package/src/features/chat/stores/chat-thread.store.ts +4 -0
  100. package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
  101. package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
  102. package/src/features/chat/utils/ncp-chat-input-availability.utils.test.ts +1 -0
  103. package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
  104. package/src/features/chat/utils/ncp-session-adapter.utils.ts +32 -0
  105. package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
  106. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
  107. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
  108. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
  109. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
  110. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
  111. package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
  112. package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
  113. package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
  114. package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
  115. package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
  116. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  117. package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
  118. package/src/features/marketplace/components/marketplace-page.tsx +154 -132
  119. package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
  120. package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
  121. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
  122. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
  123. package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
  124. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
  125. package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
  126. package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
  127. package/src/features/system-status/components/runtime-control-card.tsx +7 -6
  128. package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
  129. package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
  130. package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
  131. package/src/features/system-status/utils/system-status.utils.ts +31 -6
  132. package/src/index.css +8 -0
  133. package/src/platforms/desktop/components/desktop-app-shell.test.tsx +68 -0
  134. package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
  135. package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
  136. package/src/platforms/desktop/index.ts +6 -0
  137. package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
  138. package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
  139. package/src/shared/components/common/brand-header.tsx +36 -16
  140. package/src/shared/components/config/provider-form-support.ts +2 -22
  141. package/src/shared/components/cron-config.tsx +12 -58
  142. package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
  143. package/src/shared/components/ui/select.tsx +19 -7
  144. package/src/shared/lib/api/channel-auth.types.ts +1 -0
  145. package/src/shared/lib/api/ncp-session.types.ts +9 -0
  146. package/src/shared/lib/api/types.ts +12 -1
  147. package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
  148. package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
  149. package/src/shared/lib/cron/index.ts +1 -0
  150. package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
  151. package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
  152. package/src/shared/lib/i18n/index.ts +20 -59
  153. package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
  154. package/src/shared/lib/provider-models/index.test.ts +39 -0
  155. package/src/shared/lib/provider-models/index.ts +1 -3
  156. package/src/shared/lib/ui-document-title/index.ts +0 -1
  157. package/tsconfig.json +1 -0
  158. package/vite.config.ts +1 -1
  159. package/vitest.config.ts +1 -1
  160. package/dist/assets/api-D2xRKmZd.js +0 -15
  161. package/dist/assets/app-manager-provider-CNaZboG4.js +0 -1
  162. package/dist/assets/app-navigation.config-Ihhrrt--.js +0 -1
  163. package/dist/assets/channels-list-page-p26lgxLk.js +0 -8
  164. package/dist/assets/chat-Dkh2qtuz.js +0 -61
  165. package/dist/assets/chat-page-DoTmE2wx.js +0 -1
  166. package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
  167. package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
  168. package/dist/assets/desktop-update-config-DlpzDfKM.js +0 -1
  169. package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
  170. package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
  171. package/dist/assets/doc-browser-p82AdNO-.js +0 -1
  172. package/dist/assets/folder-CeJKPx5P.js +0 -1
  173. package/dist/assets/hash-BqxRTZW5.js +0 -1
  174. package/dist/assets/i18n-DnTGDIRw.js +0 -1
  175. package/dist/assets/index-D8MKmXtO.css +0 -1
  176. package/dist/assets/index-pBvbJ5Mt.js +0 -2
  177. package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
  178. package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
  179. package/dist/assets/logos-C4sYP1Vl.js +0 -1
  180. package/dist/assets/marketplace-page-Cql0kDi-.js +0 -1
  181. package/dist/assets/marketplace-page-m4P5g_Ht.js +0 -49
  182. package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +0 -1
  183. package/dist/assets/mcp-marketplace-page-ByzBQZcx.js +0 -40
  184. package/dist/assets/message-square-z_osm9c0.js +0 -1
  185. package/dist/assets/model-config-Dbr_0APb.js +0 -1
  186. package/dist/assets/play-Dv6Nr1Ew.js +0 -1
  187. package/dist/assets/plus-D8eKFY7h.js +0 -1
  188. package/dist/assets/provider-scoped-model-input-DFm6N2f7.js +0 -1
  189. package/dist/assets/providers-list-BJcLOjun.js +0 -1
  190. package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
  191. package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
  192. package/dist/assets/remote-BOxo9iwd.js +0 -1
  193. package/dist/assets/runtime-config-page-CjLhnbSl.js +0 -1
  194. package/dist/assets/search-config-J4Htco-P.js +0 -1
  195. package/dist/assets/secrets-config-CUdERjco.js +0 -3
  196. package/dist/assets/sessions-config-page-DpK991fs.js +0 -2
  197. package/dist/assets/settings-drbWqzA4.js +0 -1
  198. package/dist/assets/skeleton-BK1SOSRA.js +0 -1
  199. package/dist/assets/theme-provider-0hxjiPc_.js +0 -2
  200. package/dist/assets/tooltip-Cj4yA0gH.js +0 -1
  201. package/dist/assets/trash-2-CBsHCfqq.js +0 -1
  202. package/dist/assets/use-config-38Ur-89i.js +0 -1
  203. package/dist/assets/use-confirm-dialog-DPQThaeU.js +0 -1
  204. package/dist/assets/use-infinite-scroll-loader-5Gf1xQi7.js +0 -1
  205. package/dist/assets/use-viewport-layout-D1XzKeip.js +0 -1
  206. package/dist/assets/x-CM-XDMpk.js +0 -1
  207. package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
  208. package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
  209. package/src/features/chat/pages/sessions-config-page.tsx +0 -192
  210. /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
@@ -3,6 +3,7 @@ import { appQueryClient } from '@/app-query-client';
3
3
  import { NcpChatThreadManager } from '@/features/chat/managers/ncp-chat-thread.manager';
4
4
  import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
5
5
  import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
6
+ import type * as SharedApi from '@/shared/lib/api';
6
7
 
7
8
  const { deleteNcpSessionMock, deleteSummaryMock } = vi.hoisted(() => ({
8
9
  deleteNcpSessionMock: vi.fn(async () => ({ deleted: true, sessionId: 'parent-session-1' })),
@@ -10,7 +11,7 @@ const { deleteNcpSessionMock, deleteSummaryMock } = vi.hoisted(() => ({
10
11
  }));
11
12
 
12
13
  vi.mock('@/shared/lib/api', async (importOriginal) => {
13
- const actual = await importOriginal<typeof import('@/shared/lib/api')>();
14
+ const actual = await importOriginal<typeof SharedApi>();
14
15
  return {
15
16
  ...actual,
16
17
  deleteNcpSession: deleteNcpSessionMock,
@@ -97,6 +98,56 @@ describe('NcpChatThreadManager', () => {
97
98
  expect(uiManager.goToSession).toHaveBeenCalledWith('parent-session-1');
98
99
  });
99
100
 
101
+ it('opens the session cron panel without changing route when already selected', () => {
102
+ const uiManager = {
103
+ goToSession: vi.fn(),
104
+ goToChatRoot: vi.fn(),
105
+ goToProviders: vi.fn(),
106
+ confirm: vi.fn(),
107
+ } as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
108
+
109
+ const manager = new NcpChatThreadManager(
110
+ uiManager,
111
+ {} as ConstructorParameters<typeof NcpChatThreadManager>[1],
112
+ {} as ConstructorParameters<typeof NcpChatThreadManager>[2],
113
+ );
114
+
115
+ manager.openSessionCronPanel('parent-session-1');
116
+
117
+ expect(useChatThreadStore.getState().snapshot).toMatchObject({
118
+ workspacePanelParentKey: 'parent-session-1',
119
+ activeWorkspacePanelKind: 'cron',
120
+ activeChildSessionKey: null,
121
+ activeWorkspaceFileKey: null,
122
+ });
123
+ expect(uiManager.goToSession).not.toHaveBeenCalled();
124
+ });
125
+
126
+ it('routes to the session before opening its cron panel when needed', () => {
127
+ useChatSessionListStore.setState({
128
+ snapshot: {
129
+ ...useChatSessionListStore.getState().snapshot,
130
+ selectedSessionKey: 'another-session',
131
+ },
132
+ });
133
+ const uiManager = {
134
+ goToSession: vi.fn(),
135
+ goToChatRoot: vi.fn(),
136
+ goToProviders: vi.fn(),
137
+ confirm: vi.fn(),
138
+ } as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
139
+
140
+ const manager = new NcpChatThreadManager(
141
+ uiManager,
142
+ {} as ConstructorParameters<typeof NcpChatThreadManager>[1],
143
+ {} as ConstructorParameters<typeof NcpChatThreadManager>[2],
144
+ );
145
+
146
+ manager.openSessionCronPanel('parent-session-1');
147
+
148
+ expect(uiManager.goToSession).toHaveBeenCalledWith('parent-session-1');
149
+ });
150
+
100
151
  it('keeps preview and diff for the same file as separate workspace tabs', () => {
101
152
  const uiManager = {
102
153
  goToSession: vi.fn(),
@@ -62,6 +62,7 @@ export class NcpChatThreadManager {
62
62
  parentSessionKey: null,
63
63
  parentSessionLabel: null,
64
64
  workspacePanelParentKey: null,
65
+ activeWorkspacePanelKind: null,
65
66
  childSessionTabs: [],
66
67
  activeChildSessionKey: null,
67
68
  workspaceFileTabs: [],
@@ -156,6 +157,7 @@ export class NcpChatThreadManager {
156
157
  const activeChildSessionKey = params.activeChildSessionKey?.trim() || null;
157
158
  useChatThreadStore.getState().setSnapshot({
158
159
  workspacePanelParentKey: parentSessionKey,
160
+ activeWorkspacePanelKind: 'child-session',
159
161
  activeChildSessionKey,
160
162
  activeWorkspaceFileKey: null,
161
163
  });
@@ -170,6 +172,7 @@ export class NcpChatThreadManager {
170
172
  }
171
173
  useChatThreadStore.getState().setSnapshot({
172
174
  workspacePanelParentKey: parentSessionKey,
175
+ activeWorkspacePanelKind: 'file',
173
176
  workspaceFileTabs: this.upsertWorkspaceFileTab(nextTab),
174
177
  activeWorkspaceFileKey: nextTab.key,
175
178
  activeChildSessionKey: null,
@@ -196,6 +199,7 @@ export class NcpChatThreadManager {
196
199
  }
197
200
  useChatThreadStore.getState().setSnapshot({
198
201
  workspacePanelParentKey: null,
202
+ activeWorkspacePanelKind: null,
199
203
  activeChildSessionKey: null,
200
204
  activeWorkspaceFileKey: null,
201
205
  });
@@ -214,6 +218,7 @@ export class NcpChatThreadManager {
214
218
  useChatThreadStore.getState().setSnapshot({
215
219
  activeChildSessionKey: normalizedSessionKey,
216
220
  activeWorkspaceFileKey: null,
221
+ activeWorkspacePanelKind: 'child-session',
217
222
  });
218
223
  };
219
224
 
@@ -229,6 +234,7 @@ export class NcpChatThreadManager {
229
234
  useChatThreadStore.getState().setSnapshot({
230
235
  activeWorkspaceFileKey: normalizedFileKey,
231
236
  activeChildSessionKey: null,
237
+ activeWorkspacePanelKind: 'file',
232
238
  });
233
239
  };
234
240
 
@@ -254,11 +260,26 @@ export class NcpChatThreadManager {
254
260
  closeWorkspacePanel = () => {
255
261
  useChatThreadStore.getState().setSnapshot({
256
262
  workspacePanelParentKey: null,
263
+ activeWorkspacePanelKind: null,
257
264
  activeChildSessionKey: null,
258
265
  activeWorkspaceFileKey: null,
259
266
  });
260
267
  };
261
268
 
269
+ openSessionCronPanel = (sessionKey: string) => {
270
+ const parentSessionKey = sessionKey.trim();
271
+ if (!parentSessionKey) {
272
+ return;
273
+ }
274
+ useChatThreadStore.getState().setSnapshot({
275
+ workspacePanelParentKey: parentSessionKey,
276
+ activeWorkspacePanelKind: 'cron',
277
+ activeChildSessionKey: null,
278
+ activeWorkspaceFileKey: null,
279
+ });
280
+ this.ensureWorkspaceParentRoute(parentSessionKey);
281
+ };
282
+
262
283
  closeChildSessionDetail = () => {
263
284
  this.closeWorkspacePanel();
264
285
  };
@@ -219,15 +219,16 @@ function useNcpChatPageState(presenter: NcpChatPresenter) {
219
219
  selectedSessionType,
220
220
  sessionTypeOptions,
221
221
  });
222
+ const currentSessionRunning = agent.isRunning || selectedSession?.status === "running";
222
223
  return {
223
224
  ...baseState,
224
225
  availableAgents,
225
226
  effectiveSessionProjectRoot,
226
227
  effectiveSessionProjectName,
227
- isSending: agent.isSending || agent.isRunning,
228
- isAwaitingAssistantOutput: agent.isRunning,
229
- canStopCurrentRun: agent.isRunning,
230
- stopDisabledReason: agent.isRunning ? null : "__preparing__",
228
+ isSending: agent.isSending || currentSessionRunning,
229
+ isAwaitingAssistantOutput: currentSessionRunning,
230
+ canStopCurrentRun: currentSessionRunning,
231
+ stopDisabledReason: currentSessionRunning ? null : "__preparing__",
231
232
  lastSendError:
232
233
  isRuntimeBlocked
233
234
  ? null
@@ -280,7 +281,10 @@ function useNcpChatStreamBindings(params: ReturnType<typeof useNcpChatPageState>
280
281
  return;
281
282
  }
282
283
  try {
283
- await agent.send(envelope);
284
+ const handle = await agent.send(envelope);
285
+ if (!payload.sessionKey && handle?.sessionId) {
286
+ presenter.chatSessionListManager.materializeRootSessionRoute(handle.sessionId);
287
+ }
284
288
  } catch (error) {
285
289
  if (payload.restoreDraftOnError) {
286
290
  if (payload.composerNodes && payload.composerNodes.length > 0) {
@@ -44,6 +44,7 @@ export type ChatInputSnapshot = {
44
44
  skillRecords: SessionSkillEntryView[];
45
45
  isSkillsLoading: boolean;
46
46
  selectedSkills: string[];
47
+ composerFocusRequest: { id: number; placement: 'end' } | null;
47
48
  };
48
49
 
49
50
  type ChatInputStore = {
@@ -75,7 +76,8 @@ const initialSnapshot: ChatInputSnapshot = {
75
76
  sessionTypeUnavailable: false,
76
77
  skillRecords: [],
77
78
  isSkillsLoading: false,
78
- selectedSkills: []
79
+ selectedSkills: [],
80
+ composerFocusRequest: null
79
81
  };
80
82
 
81
83
  export const useChatInputStore = create<ChatInputStore>((set) => ({
@@ -3,7 +3,6 @@ import type { SessionRunStatus } from '@/features/chat/types/session-run-status.
3
3
  export type ChatSessionListMode = 'time-first' | 'project-first';
4
4
  export type ChatSessionListSnapshot = {
5
5
  selectedSessionKey: string | null;
6
- draftSessionKey: string | null;
7
6
  selectedAgentId: string;
8
7
  query: string;
9
8
  listMode: ChatSessionListMode;
@@ -49,7 +48,6 @@ type ChatSessionListStoreSet = Parameters<StateCreator<ChatSessionListStore>>[0]
49
48
 
50
49
  const initialSnapshot: ChatSessionListSnapshot = {
51
50
  selectedSessionKey: null,
52
- draftSessionKey: null,
53
51
  selectedAgentId: 'main',
54
52
  query: '',
55
53
  listMode: 'time-first'
@@ -55,9 +55,11 @@ export type ChatThreadSnapshot = {
55
55
  messages: readonly NcpMessage[];
56
56
  isSending: boolean;
57
57
  isAwaitingAssistantOutput: boolean;
58
+ hasSubmittedDraftMessage: boolean;
58
59
  parentSessionKey?: string | null;
59
60
  parentSessionLabel?: string | null;
60
61
  workspacePanelParentKey?: string | null;
62
+ activeWorkspacePanelKind?: "child-session" | "file" | "cron" | null;
61
63
  childSessionTabs: ChatChildSessionTab[];
62
64
  activeChildSessionKey?: string | null;
63
65
  workspaceFileTabs: ChatWorkspaceFileTab[];
@@ -92,9 +94,11 @@ const initialSnapshot: ChatThreadSnapshot = {
92
94
  messages: [],
93
95
  isSending: false,
94
96
  isAwaitingAssistantOutput: false,
97
+ hasSubmittedDraftMessage: false,
95
98
  parentSessionKey: null,
96
99
  parentSessionLabel: null,
97
100
  workspacePanelParentKey: null,
101
+ activeWorkspacePanelKind: null,
98
102
  childSessionTabs: [],
99
103
  activeChildSessionKey: null,
100
104
  workspaceFileTabs: [],
@@ -1,6 +1,11 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import type { SessionEntryView } from '@/shared/lib/api';
3
- import { sessionDisplayName, sessionMatchesQuery } from './chat-session-display.utils';
3
+ import {
4
+ formatSessionListTime,
5
+ sessionActivityPreviewText,
6
+ sessionDisplayName,
7
+ sessionMatchesQuery
8
+ } from './chat-session-display.utils';
4
9
 
5
10
  function createSession(overrides: Partial<SessionEntryView> = {}): SessionEntryView {
6
11
  return {
@@ -52,4 +57,81 @@ describe('chat-session-display', () => {
52
57
  it('treats an empty query as a match', () => {
53
58
  expect(sessionMatchesQuery(createSession({ label: 'Anything' }), ' ')).toBe(true);
54
59
  });
60
+
61
+ it('shows running activity before the previous reply preview', () => {
62
+ expect(
63
+ sessionActivityPreviewText(
64
+ createSession({
65
+ activityPreview: {
66
+ state: 'running',
67
+ statusText: '正在调用工具:shell',
68
+ replyText: '之前的回复',
69
+ timestamp: '2026-05-16T01:00:00.000Z'
70
+ }
71
+ })
72
+ )
73
+ ).toBe('正在调用工具:shell');
74
+ });
75
+
76
+ it('shows the final assistant reply after completion', () => {
77
+ expect(
78
+ sessionActivityPreviewText(
79
+ createSession({
80
+ activityPreview: {
81
+ state: 'completed',
82
+ statusText: '工具调用完成',
83
+ replyText: '最终回复内容',
84
+ timestamp: '2026-05-16T01:00:00.000Z'
85
+ }
86
+ })
87
+ )
88
+ ).toBe('最终回复内容');
89
+ });
90
+
91
+ it('formats today activity as time like WeChat session lists', () => {
92
+ expect(
93
+ formatSessionListTime(
94
+ new Date(2026, 4, 16, 9, 5),
95
+ 'zh',
96
+ new Date(2026, 4, 16, 12, 0)
97
+ )
98
+ ).toBe('09:05');
99
+ });
100
+
101
+ it('formats yesterday activity as yesterday like WeChat session lists', () => {
102
+ expect(
103
+ formatSessionListTime(
104
+ new Date(2026, 4, 15, 23, 30),
105
+ 'zh',
106
+ new Date(2026, 4, 16, 12, 0)
107
+ )
108
+ ).toBe('昨天');
109
+ });
110
+
111
+ it('formats recent activity within a week as weekday', () => {
112
+ expect(
113
+ formatSessionListTime(
114
+ new Date(2026, 4, 12, 18, 30),
115
+ 'zh',
116
+ new Date(2026, 4, 16, 12, 0)
117
+ )
118
+ ).toBe('星期二');
119
+ });
120
+
121
+ it('formats older activity as date and keeps year only across years', () => {
122
+ expect(
123
+ formatSessionListTime(
124
+ new Date(2026, 4, 1, 18, 30),
125
+ 'zh',
126
+ new Date(2026, 4, 16, 12, 0)
127
+ )
128
+ ).toBe('5月1日');
129
+ expect(
130
+ formatSessionListTime(
131
+ new Date(2025, 11, 31, 18, 30),
132
+ 'zh',
133
+ new Date(2026, 4, 16, 12, 0)
134
+ )
135
+ ).toBe('2025年12月31日');
136
+ });
55
137
  });
@@ -1,4 +1,5 @@
1
1
  import type { SessionEntryView } from '@/shared/lib/api';
2
+ import { getLanguage, getLocale, t, type I18nLanguage } from '@/shared/lib/i18n';
2
3
 
3
4
  export function sessionDisplayName(session: SessionEntryView): string {
4
5
  const label = session.label?.trim();
@@ -9,6 +10,78 @@ export function sessionDisplayName(session: SessionEntryView): string {
9
10
  return chunks[chunks.length - 1] || session.key;
10
11
  }
11
12
 
13
+ export function sessionActivityPreviewText(session: SessionEntryView): string | null {
14
+ const preview = session.activityPreview;
15
+ if (!preview) {
16
+ return null;
17
+ }
18
+ if (preview.state === 'failed' || preview.state === 'running') {
19
+ return preview.statusText ?? preview.replyText ?? null;
20
+ }
21
+ if (preview.state === 'completed') {
22
+ return preview.replyText ?? preview.statusText ?? null;
23
+ }
24
+ return preview.statusText ?? preview.replyText ?? null;
25
+ }
26
+
27
+ function startOfLocalDate(date: Date): number {
28
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
29
+ }
30
+
31
+ function isSameLocalYear(left: Date, right: Date): boolean {
32
+ return left.getFullYear() === right.getFullYear();
33
+ }
34
+
35
+ function formatChineseSessionDate(date: Date, now: Date): string {
36
+ const monthDay = `${date.getMonth() + 1}月${date.getDate()}日`;
37
+ return isSameLocalYear(date, now) ? monthDay : `${date.getFullYear()}年${monthDay}`;
38
+ }
39
+
40
+ export function formatSessionListTime(
41
+ value?: string | Date,
42
+ lang: I18nLanguage = getLanguage(),
43
+ now: Date = new Date()
44
+ ): string {
45
+ if (!value) {
46
+ return '-';
47
+ }
48
+
49
+ const date = value instanceof Date ? value : new Date(value);
50
+ if (Number.isNaN(date.getTime())) {
51
+ return typeof value === 'string' ? value : '-';
52
+ }
53
+
54
+ const locale = getLocale(lang);
55
+ const dateStart = startOfLocalDate(date);
56
+ const todayStart = startOfLocalDate(now);
57
+ const daysAgo = Math.floor((todayStart - dateStart) / 86_400_000);
58
+
59
+ if (daysAgo === 0) {
60
+ return new Intl.DateTimeFormat(locale, {
61
+ hour: '2-digit',
62
+ minute: '2-digit',
63
+ hour12: false
64
+ }).format(date);
65
+ }
66
+
67
+ if (daysAgo === 1) {
68
+ return t('chatSidebarYesterday', lang);
69
+ }
70
+
71
+ if (daysAgo > 1 && daysAgo < 7) {
72
+ return new Intl.DateTimeFormat(locale, { weekday: 'long' }).format(date);
73
+ }
74
+
75
+ if (lang === 'zh') {
76
+ return formatChineseSessionDate(date, now);
77
+ }
78
+
79
+ const options: Intl.DateTimeFormatOptions = isSameLocalYear(date, now)
80
+ ? { month: 'numeric', day: 'numeric' }
81
+ : { year: 'numeric', month: 'numeric', day: 'numeric' };
82
+ return new Intl.DateTimeFormat(locale, options).format(date);
83
+ }
84
+
12
85
  function normalizeSessionSearchValue(value: string): string {
13
86
  return value.trim().toLowerCase();
14
87
  }
@@ -36,6 +36,7 @@ function createSnapshot(
36
36
  skillRecords: [],
37
37
  isSkillsLoading: false,
38
38
  selectedSkills: [],
39
+ composerFocusRequest: null,
39
40
  ...overrides,
40
41
  };
41
42
  }
@@ -70,6 +70,28 @@ describe('adaptNcpSessionSummary', () => {
70
70
  });
71
71
  });
72
72
 
73
+ it('maps session activity preview metadata into the session entry', () => {
74
+ const adapted = adaptNcpSessionSummary(
75
+ createSummary({
76
+ metadata: {
77
+ last_activity_preview: {
78
+ state: 'completed',
79
+ replyText: '已经整理好方案',
80
+ statusText: '工具调用完成',
81
+ timestamp: '2026-05-16T01:00:00.000Z',
82
+ },
83
+ },
84
+ }),
85
+ );
86
+
87
+ expect(adapted.activityPreview).toEqual({
88
+ state: 'completed',
89
+ replyText: '已经整理好方案',
90
+ statusText: '工具调用完成',
91
+ timestamp: '2026-05-16T01:00:00.000Z',
92
+ });
93
+ });
94
+
73
95
  it('does not hydrate context window metadata from persisted session summaries', () => {
74
96
  const adapted = adaptNcpSessionSummary(
75
97
  createSummary({
@@ -3,6 +3,7 @@ import type { NcpMessagePart } from '@nextclaw/ncp';
3
3
  import type {
4
4
  NcpMessageView,
5
5
  NcpSessionSummaryView,
6
+ SessionActivityPreviewView,
6
7
  SessionEntryView,
7
8
  ThinkingLevel
8
9
  } from '@/shared/lib/api';
@@ -13,6 +14,12 @@ import {
13
14
  } from '@/shared/lib/session-project';
14
15
 
15
16
  const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
17
+ const SESSION_ACTIVITY_PREVIEW_STATE_SET = new Set<SessionActivityPreviewView['state']>([
18
+ 'running',
19
+ 'completed',
20
+ 'failed',
21
+ 'idle'
22
+ ]);
16
23
 
17
24
  function stringifyUnknown(value: unknown): string {
18
25
  if (typeof value === 'string') {
@@ -46,6 +53,28 @@ function readMetadata(summary: NcpSessionSummaryView): Record<string, unknown> |
46
53
  return metadata as Record<string, unknown>;
47
54
  }
48
55
 
56
+ function readNcpSessionActivityPreview(summary: NcpSessionSummaryView): SessionActivityPreviewView | null {
57
+ const metadata = readMetadata(summary);
58
+ const preview = metadata?.last_activity_preview;
59
+ if (!preview || typeof preview !== 'object' || Array.isArray(preview)) {
60
+ return null;
61
+ }
62
+ const previewRecord = preview as Record<string, unknown>;
63
+ const { state } = previewRecord;
64
+ const timestamp = readOptionalString(previewRecord.timestamp);
65
+ if (!SESSION_ACTIVITY_PREVIEW_STATE_SET.has(state as SessionActivityPreviewView['state']) || !timestamp) {
66
+ return null;
67
+ }
68
+ const statusText = readOptionalString(previewRecord.statusText);
69
+ const replyText = readOptionalString(previewRecord.replyText);
70
+ return {
71
+ state: state as SessionActivityPreviewView['state'],
72
+ timestamp,
73
+ ...(statusText ? { statusText } : {}),
74
+ ...(replyText ? { replyText } : {})
75
+ };
76
+ }
77
+
49
78
  export function readNcpSessionPreferredModel(summary: NcpSessionSummaryView): string | null {
50
79
  const metadata = readMetadata(summary);
51
80
  if (!metadata) {
@@ -286,6 +315,7 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
286
315
  const parentSessionId = readNcpParentSessionId(summary);
287
316
  const spawnedByRequestId = readNcpSpawnedByRequestId(summary);
288
317
  const isPromotedChildSession = readPromotedChildSession(summary);
318
+ const activityPreview = readNcpSessionActivityPreview(summary);
289
319
  return {
290
320
  key: summary.sessionId,
291
321
  createdAt: summary.createdAt ?? summary.updatedAt,
@@ -301,10 +331,12 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
301
331
  ...(projectName ? { projectName } : {}),
302
332
  sessionType: readNcpSessionType(summary),
303
333
  sessionTypeMutable: false,
334
+ status: summary.status ?? 'idle',
304
335
  isChildSession: Boolean(parentSessionId),
305
336
  ...(isPromotedChildSession ? { isPromotedChildSession } : {}),
306
337
  ...(parentSessionId ? { parentSessionId } : {}),
307
338
  ...(spawnedByRequestId ? { spawnedByRequestId } : {}),
339
+ ...(activityPreview ? { activityPreview } : {}),
308
340
  messageCount: summary.messageCount
309
341
  };
310
342
  }