@nextclaw/ui 0.14.1 → 0.14.3

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 (224) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{api-DPcvCLfa.js → api-BP_8IaxB.js} +7 -7
  3. package/dist/assets/channels-list-page-DEh5Jjqx.js +8 -0
  4. package/dist/assets/chat-page-BRB0N5WW.js +105 -0
  5. package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
  6. package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-DsivFJ4r.js} +2 -2
  7. package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-B0w9p2ep.js} +1 -1
  8. package/dist/assets/{dist-DPrgcEC0.js → dist-CikkhOlq.js} +1 -1
  9. package/dist/assets/{dist-DsE4OsSS.js → dist-PVA_xL1D.js} +1 -1
  10. package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
  11. package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
  12. package/dist/assets/doc-browser-context-MTw4EvKd.js +1 -0
  13. package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-y6vDtq4M.js} +1 -1
  14. package/dist/assets/{external-link-C_dmG-WJ.js → external-link-DVJIQf_G.js} +1 -1
  15. package/dist/assets/index-BiE-K8-q.css +1 -0
  16. package/dist/assets/index-D9BAE_L_.js +105 -0
  17. package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-DotaTUHb.js +1 -0
  21. package/dist/assets/plus-BbZqquJe.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +1 -0
  23. package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
  24. package/dist/assets/{react-sZz5HSCm.js → react-VNqT5Zp7.js} +1 -1
  25. package/dist/assets/remote-B8TiUfwW.js +1 -0
  26. package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
  27. package/dist/assets/runtime-config-page-S6ErRHR8.js +1 -0
  28. package/dist/assets/{save-MzjoiHri.js → save-BOJ477H-.js} +1 -1
  29. package/dist/assets/{search-B29dE4GN.js → search-BEti5beA.js} +1 -1
  30. package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-DGj5am03.js} +1 -1
  31. package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page--IzrIOid.js} +2 -2
  32. package/dist/assets/{select-BKmh05hd.js → select-hoj8dK4K.js} +2 -2
  33. package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-T8-h-die.js} +1 -1
  34. package/dist/assets/use-config-CjRV2hwL.js +1 -0
  35. package/dist/assets/x-D9PXs_xP.js +1 -0
  36. package/dist/index.html +70 -19
  37. package/index.html +55 -5
  38. package/package.json +9 -9
  39. package/src/app/components/app-presenter-provider.tsx +11 -2
  40. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  41. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  42. package/src/app/components/layout/app-layout.tsx +18 -4
  43. package/src/app/components/layout/header.tsx +3 -3
  44. package/src/app/components/layout/page-layout.tsx +2 -2
  45. package/src/app/components/layout/runtime-status-entry.tsx +7 -7
  46. package/src/app/components/layout/sidebar-items.tsx +194 -71
  47. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  48. package/src/app/components/layout/sidebar.tsx +340 -83
  49. package/src/app/managers/viewport-layout.manager.ts +10 -0
  50. package/src/app/stores/viewport-layout.store.ts +39 -3
  51. package/src/app/styles/design-system.css +388 -26
  52. package/src/app/test/vitest-setup.ts +31 -0
  53. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  54. package/src/features/agents/components/agents-page.tsx +10 -6
  55. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
  56. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  57. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  59. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  60. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  61. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +15 -16
  62. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
  63. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +19 -19
  64. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
  65. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  66. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +18 -16
  68. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  69. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  70. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +93 -20
  71. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +93 -36
  72. package/src/features/chat/components/layout/chat-sidebar.tsx +60 -93
  73. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  74. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
  76. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +308 -0
  77. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +439 -0
  78. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +194 -0
  79. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  80. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +51 -20
  81. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  82. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  83. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  84. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  85. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  86. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  87. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  88. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  89. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  90. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  91. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  92. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  93. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  94. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  95. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  96. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  97. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  98. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  99. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
  100. package/src/features/chat/features/message/components/chat-message-list.container.tsx +99 -18
  101. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  102. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  103. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  104. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
  105. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +6 -6
  106. package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
  107. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
  108. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
  109. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  110. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  111. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  112. package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
  113. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  114. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  115. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  116. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
  117. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
  118. package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
  119. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
  120. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
  121. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  122. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -9
  123. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  124. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
  125. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
  126. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
  127. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +5 -5
  128. package/src/features/chat/features/welcome/components/chat-welcome.tsx +6 -6
  129. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
  130. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
  131. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  132. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  133. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  134. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  135. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  136. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  137. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  138. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  139. package/src/features/chat/index.ts +0 -1
  140. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  141. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  142. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  143. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  144. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  145. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  146. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  147. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  148. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  149. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  150. package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
  151. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  152. package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
  153. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  154. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
  155. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
  156. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
  157. package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
  158. package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
  159. package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
  160. package/src/features/settings/components/config/provider-form.tsx +10 -10
  161. package/src/features/settings/components/config/provider-models-section.tsx +16 -16
  162. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  163. package/src/features/settings/pages/providers-config-page.tsx +17 -17
  164. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  165. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  166. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  167. package/src/shared/components/config-split-page.tsx +11 -11
  168. package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
  169. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
  170. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
  171. package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
  172. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
  173. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  174. package/src/shared/components/ui/button.tsx +4 -4
  175. package/src/shared/components/ui/card.tsx +4 -4
  176. package/src/shared/components/ui/dialog.tsx +5 -5
  177. package/src/shared/components/ui/input.tsx +1 -1
  178. package/src/shared/components/ui/label.tsx +1 -1
  179. package/src/shared/components/ui/popover.tsx +1 -1
  180. package/src/shared/components/ui/select.tsx +3 -3
  181. package/src/shared/components/ui/switch.tsx +3 -3
  182. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  183. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  184. package/src/shared/components/ui/tabs.tsx +4 -4
  185. package/src/shared/components/ui/textarea.tsx +1 -1
  186. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  187. package/src/shared/lib/i18n/locales/en-US/core.json +9 -2
  188. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  189. package/src/shared/lib/i18n/locales/zh-CN/core.json +9 -2
  190. package/src/shared/lib/theme/index.ts +94 -52
  191. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  192. package/src/shared/lib/ui-document-title/index.ts +50 -21
  193. package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
  194. package/dist/assets/chat-page-BBnNJoKG.js +0 -105
  195. package/dist/assets/config-split-page-BCJhqdK9.js +0 -1
  196. package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
  197. package/dist/assets/doc-browser-DgQedeAn.js +0 -1
  198. package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
  199. package/dist/assets/index-7o1WdUbU.js +0 -103
  200. package/dist/assets/index-DQWmEWzk.css +0 -1
  201. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
  202. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
  203. package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
  204. package/dist/assets/plus-Bnt56oqG.js +0 -1
  205. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
  206. package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
  207. package/dist/assets/remote-B0LMs8u7.js +0 -1
  208. package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
  209. package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
  210. package/dist/assets/use-config-DZkBWIdL.js +0 -1
  211. package/dist/assets/x-CW2KO-Hz.js +0 -1
  212. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  213. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  214. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  215. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  216. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  217. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  218. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  219. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  220. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  221. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  222. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  223. package/src/features/chat/stores/chat-input.store.ts +0 -104
  224. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -1,236 +0,0 @@
1
- import type { NcpMessage } from '@nextclaw/ncp';
2
- import { RUNTIME_DEFAULT_MODEL_VALUE } from '@nextclaw/shared';
3
- import { beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
5
- import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
6
- import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
7
-
8
- function createChatRunManager() {
9
- const uiManager = {
10
- isAtChatRoot: vi.fn(() => true),
11
- goToSession: vi.fn(),
12
- };
13
- return {
14
- manager: new ChatRunManager(
15
- uiManager as unknown as ConstructorParameters<typeof ChatRunManager>[0],
16
- ),
17
- uiManager,
18
- };
19
- }
20
-
21
- describe('ChatRunManager', () => {
22
- beforeEach(() => {
23
- useChatInputStore.setState({
24
- snapshot: {
25
- ...useChatInputStore.getState().snapshot,
26
- canStopGeneration: false,
27
- sendError: null,
28
- isSending: false,
29
- },
30
- });
31
- useChatThreadStore.setState({
32
- snapshot: {
33
- ...useChatThreadStore.getState().snapshot,
34
- isHistoryLoading: false,
35
- messages: [],
36
- isSending: false,
37
- isAwaitingAssistantOutput: false,
38
- contextWindow: null,
39
- },
40
- });
41
- });
42
-
43
- it('sends an envelope through the active runtime and materializes a root session', async () => {
44
- const { manager, uiManager } = createChatRunManager();
45
- const sendEnvelope = vi.fn(async () => ({
46
- sessionId: 'materialized-session',
47
- userMessageId: 'user-message-1',
48
- assistantMessageId: null,
49
- runId: 'run-1',
50
- }));
51
- manager.setActiveRuntime({
52
- sessionKey: null,
53
- sendEnvelope,
54
- abortCurrentRun: vi.fn(),
55
- resumeCurrentSessionRun: vi.fn(),
56
- });
57
-
58
- await manager.sendMessage({
59
- message: 'hello',
60
- agentId: 'engineer',
61
- sessionType: 'codex',
62
- projectRoot: '/tmp/project-alpha',
63
- requestedSkills: ['project:/tmp/project-alpha/.agents/skills/review'],
64
- });
65
-
66
- expect(sendEnvelope).toHaveBeenCalledWith(
67
- expect.objectContaining({
68
- metadata: expect.objectContaining({
69
- agentId: 'engineer',
70
- session_type: 'codex',
71
- projectRoot: '/tmp/project-alpha',
72
- requested_skill_refs: ['project:/tmp/project-alpha/.agents/skills/review'],
73
- }),
74
- }),
75
- );
76
- expect(uiManager.goToSession).toHaveBeenCalledWith('materialized-session', { replace: true });
77
- });
78
-
79
- it('keeps the runtime-default sentinel in request metadata to override stale session model metadata', async () => {
80
- const { manager } = createChatRunManager();
81
- const sendEnvelope = vi.fn(async () => ({
82
- sessionId: 'session-1',
83
- userMessageId: 'user-message-1',
84
- assistantMessageId: null,
85
- runId: 'run-1',
86
- }));
87
- manager.setActiveRuntime({
88
- sessionKey: 'session-1',
89
- sendEnvelope,
90
- abortCurrentRun: vi.fn(),
91
- resumeCurrentSessionRun: vi.fn(),
92
- });
93
-
94
- await manager.sendMessage({
95
- message: 'hello',
96
- sessionKey: 'session-1',
97
- agentId: 'main',
98
- sessionType: 'codex',
99
- model: RUNTIME_DEFAULT_MODEL_VALUE,
100
- });
101
-
102
- expect(sendEnvelope).toHaveBeenCalledWith(
103
- expect.objectContaining({
104
- metadata: expect.objectContaining({
105
- model: RUNTIME_DEFAULT_MODEL_VALUE,
106
- preferred_model: RUNTIME_DEFAULT_MODEL_VALUE,
107
- }),
108
- }),
109
- );
110
- });
111
-
112
- it('does not send through a runtime for another session', async () => {
113
- const { manager } = createChatRunManager();
114
- const sendEnvelope = vi.fn();
115
- manager.setActiveRuntime({
116
- sessionKey: 'session-1',
117
- sendEnvelope,
118
- abortCurrentRun: vi.fn(),
119
- resumeCurrentSessionRun: vi.fn(),
120
- });
121
-
122
- await manager.sendMessage({
123
- message: 'hello',
124
- sessionKey: 'session-2',
125
- agentId: 'main',
126
- });
127
-
128
- expect(sendEnvelope).not.toHaveBeenCalled();
129
- });
130
-
131
- it('routes stop and resume commands through the active runtime', async () => {
132
- const { manager } = createChatRunManager();
133
- const abortCurrentRun = vi.fn(async () => undefined);
134
- const resumeCurrentSessionRun = vi.fn(async () => undefined);
135
- manager.setActiveRuntime({
136
- sessionKey: 'session-1',
137
- sendEnvelope: vi.fn(),
138
- abortCurrentRun,
139
- resumeCurrentSessionRun,
140
- });
141
-
142
- await manager.stopCurrentRun();
143
- await manager.resumeRun({ sessionKey: 'session-1' });
144
- await manager.resumeRun({ sessionKey: 'session-2' });
145
-
146
- expect(abortCurrentRun).toHaveBeenCalledTimes(1);
147
- expect(resumeCurrentSessionRun).toHaveBeenCalledTimes(1);
148
- });
149
-
150
- it('syncs the run snapshot into chat input and thread state', () => {
151
- const { manager } = createChatRunManager();
152
- const message: NcpMessage = {
153
- id: 'message-1',
154
- sessionId: 'session-1',
155
- role: 'assistant',
156
- status: 'final',
157
- parts: [{ type: 'text', text: 'done' }],
158
- timestamp: '2026-06-10T00:00:00.000Z',
159
- };
160
-
161
- manager.applyRunSnapshot({
162
- routeSessionKey: 'session-1',
163
- isHydrating: true,
164
- isSending: false,
165
- isRunning: true,
166
- visibleMessages: [message],
167
- contextWindow: {
168
- usedContextTokens: 10,
169
- totalContextTokens: 100,
170
- availableContextTokens: 90,
171
- prunedUsedContextTokens: 10,
172
- droppedHistoryCount: 0,
173
- truncatedToolResultCount: 0,
174
- truncatedSystemPrompt: false,
175
- truncatedUserMessage: false,
176
- compacted: false,
177
- compactedUsedContextTokens: 10,
178
- compactedMessageCount: 0,
179
- updatedAt: '2026-06-10T00:00:00.000Z',
180
- },
181
- sendErrorMessage: 'failed',
182
- materializedSessionKey: null,
183
- });
184
-
185
- expect(useChatInputStore.getState().snapshot).toMatchObject({
186
- canStopGeneration: true,
187
- sendError: 'failed',
188
- isSending: true,
189
- });
190
- expect(useChatThreadStore.getState().snapshot).toMatchObject({
191
- isHistoryLoading: true,
192
- messages: [message],
193
- isSending: true,
194
- isAwaitingAssistantOutput: true,
195
- });
196
- });
197
-
198
- it('clears run-owned input and thread state', () => {
199
- const { manager } = createChatRunManager();
200
- useChatInputStore.getState().setSnapshot({
201
- canStopGeneration: true,
202
- sendError: 'failed',
203
- isSending: true,
204
- });
205
- useChatThreadStore.getState().setSnapshot({
206
- isHistoryLoading: true,
207
- messages: [
208
- {
209
- id: 'message-1',
210
- sessionId: 'session-1',
211
- role: 'assistant',
212
- status: 'final',
213
- parts: [{ type: 'text', text: 'done' }],
214
- timestamp: '2026-06-10T00:00:00.000Z',
215
- },
216
- ],
217
- isSending: true,
218
- isAwaitingAssistantOutput: true,
219
- });
220
-
221
- manager.clearRunState();
222
-
223
- expect(useChatInputStore.getState().snapshot).toMatchObject({
224
- canStopGeneration: false,
225
- sendError: null,
226
- isSending: false,
227
- });
228
- expect(useChatThreadStore.getState().snapshot).toMatchObject({
229
- isHistoryLoading: false,
230
- messages: [],
231
- isSending: false,
232
- isAwaitingAssistantOutput: false,
233
- contextWindow: null,
234
- });
235
- });
236
- });
@@ -1,219 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { RUNTIME_DEFAULT_MODEL_VALUE } from '@nextclaw/shared';
3
- import { updateNcpSession } from '@/shared/lib/api';
4
- import { ChatSessionPreferenceSync } from '@/features/chat/managers/chat-session-preference-sync.manager';
5
- import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
6
- import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
7
- import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
8
- import type * as SharedApiModule from '@/shared/lib/api';
9
-
10
- vi.mock('@/shared/lib/api', async (importOriginal) => {
11
- const actual = await importOriginal<typeof SharedApiModule>();
12
- return {
13
- ...actual,
14
- updateNcpSession: vi.fn(async () => ({
15
- sessionId: 'session-1',
16
- messageCount: 0,
17
- updatedAt: new Date().toISOString(),
18
- status: 'idle',
19
- metadata: {}
20
- }))
21
- };
22
- });
23
-
24
- describe('ChatSessionPreferenceSync', () => {
25
- afterEach(() => {
26
- useChatInputStore.setState((state) => ({
27
- snapshot: {
28
- ...state.snapshot,
29
- modelOptions: [],
30
- selectedModel: '',
31
- selectedThinkingLevel: null
32
- }
33
- }));
34
- useChatSessionListStore.setState((state) => ({
35
- snapshot: {
36
- ...state.snapshot,
37
- selectedSessionKey: null
38
- }
39
- }));
40
- useChatThreadStore.setState((state) => ({
41
- snapshot: {
42
- ...state.snapshot,
43
- canDeleteSession: false
44
- }
45
- }));
46
- vi.clearAllMocks();
47
- });
48
-
49
- it('persists the selected model and thinking to the current session metadata', async () => {
50
- useChatInputStore.setState((state) => ({
51
- snapshot: {
52
- ...state.snapshot,
53
- selectedModel: 'openai/gpt-5',
54
- selectedThinkingLevel: 'high'
55
- }
56
- }));
57
- useChatSessionListStore.setState((state) => ({
58
- snapshot: {
59
- ...state.snapshot,
60
- selectedSessionKey: 'session-1'
61
- }
62
- }));
63
- useChatThreadStore.setState((state) => ({
64
- snapshot: {
65
- ...state.snapshot,
66
- canDeleteSession: true
67
- }
68
- }));
69
-
70
- const sync = new ChatSessionPreferenceSync(updateNcpSession);
71
- sync.syncSelectedSessionPreferences();
72
- await vi.waitFor(() => {
73
- expect(updateNcpSession).toHaveBeenCalledWith('session-1', {
74
- preferredModel: 'openai/gpt-5',
75
- preferredThinking: 'high'
76
- });
77
- });
78
- });
79
-
80
- it('clears the persisted model preference when runtime default is selected', async () => {
81
- useChatInputStore.setState((state) => ({
82
- snapshot: {
83
- ...state.snapshot,
84
- selectedModel: RUNTIME_DEFAULT_MODEL_VALUE,
85
- selectedThinkingLevel: null
86
- }
87
- }));
88
- useChatSessionListStore.setState((state) => ({
89
- snapshot: {
90
- ...state.snapshot,
91
- selectedSessionKey: 'session-1'
92
- }
93
- }));
94
- useChatThreadStore.setState((state) => ({
95
- snapshot: {
96
- ...state.snapshot,
97
- canDeleteSession: true
98
- }
99
- }));
100
-
101
- const sync = new ChatSessionPreferenceSync(updateNcpSession);
102
- sync.syncSelectedSessionPreferences();
103
- await vi.waitFor(() => {
104
- expect(updateNcpSession).toHaveBeenCalledWith('session-1', {
105
- preferredModel: null,
106
- preferredThinking: null
107
- });
108
- });
109
- });
110
-
111
- it('uses the runtime-default thinking capability default when no session thinking is persisted', () => {
112
- useChatInputStore.setState((state) => ({
113
- snapshot: {
114
- ...state.snapshot,
115
- modelOptions: [
116
- {
117
- value: RUNTIME_DEFAULT_MODEL_VALUE,
118
- modelLabel: 'Runtime default',
119
- providerLabel: '',
120
- isRuntimeDefault: true,
121
- thinkingCapability: {
122
- supported: ['off', 'minimal', 'low', 'medium', 'high', 'xhigh'],
123
- default: 'high',
124
- },
125
- },
126
- ],
127
- selectedModel: '',
128
- selectedThinkingLevel: null,
129
- }
130
- }));
131
-
132
- const sync = new ChatSessionPreferenceSync(updateNcpSession);
133
- sync.syncInputSelection({
134
- selectedSessionExists: true,
135
- defaultModel: RUNTIME_DEFAULT_MODEL_VALUE,
136
- selectedSessionPreferredThinking: null,
137
- fallbackPreferredThinking: null,
138
- });
139
-
140
- expect(useChatInputStore.getState().snapshot).toMatchObject({
141
- selectedModel: RUNTIME_DEFAULT_MODEL_VALUE,
142
- selectedThinkingLevel: 'high',
143
- });
144
- });
145
-
146
- it('does not rewrite the input snapshot when the resolved selection is unchanged', () => {
147
- useChatInputStore.setState((state) => ({
148
- snapshot: {
149
- ...state.snapshot,
150
- modelOptions: [
151
- {
152
- value: 'openai/gpt-5',
153
- modelLabel: 'GPT-5',
154
- providerLabel: 'OpenAI',
155
- thinkingCapability: null,
156
- },
157
- ],
158
- selectedModel: 'openai/gpt-5',
159
- selectedThinkingLevel: null,
160
- }
161
- }));
162
- const sync = new ChatSessionPreferenceSync(updateNcpSession);
163
- const listener = vi.fn();
164
- const unsubscribe = useChatInputStore.subscribe(listener);
165
-
166
- sync.syncInputSelection({
167
- selectedSessionExists: true,
168
- selectedSessionPreferredModel: 'openai/gpt-5',
169
- selectedSessionPreferredThinking: null,
170
- });
171
-
172
- unsubscribe();
173
- expect(listener).not.toHaveBeenCalled();
174
- });
175
-
176
- it('applies a historical session preferred model after its summary arrives', () => {
177
- useChatInputStore.setState((state) => ({
178
- snapshot: {
179
- ...state.snapshot,
180
- modelOptions: [
181
- {
182
- value: 'anthropic/claude-sonnet-4',
183
- modelLabel: 'Claude Sonnet 4',
184
- providerLabel: 'Anthropic',
185
- thinkingCapability: null,
186
- },
187
- {
188
- value: 'openai/gpt-5',
189
- modelLabel: 'GPT-5',
190
- providerLabel: 'OpenAI',
191
- thinkingCapability: null,
192
- },
193
- ],
194
- selectedModel: 'anthropic/claude-sonnet-4',
195
- selectedThinkingLevel: null,
196
- }
197
- }));
198
- const sync = new ChatSessionPreferenceSync(updateNcpSession);
199
-
200
- sync.syncInputSelection({
201
- selectedSessionKey: 'session-2',
202
- selectedSessionExists: false,
203
- fallbackPreferredModel: 'anthropic/claude-sonnet-4',
204
- });
205
- expect(useChatInputStore.getState().snapshot.selectedModel).toBe(
206
- 'anthropic/claude-sonnet-4',
207
- );
208
-
209
- sync.syncInputSelection({
210
- selectedSessionKey: 'session-2',
211
- selectedSessionExists: true,
212
- selectedSessionPreferredModel: 'openai/gpt-5',
213
- selectedSessionPreferredThinking: null,
214
- fallbackPreferredModel: 'anthropic/claude-sonnet-4',
215
- });
216
-
217
- expect(useChatInputStore.getState().snapshot.selectedModel).toBe('openai/gpt-5');
218
- });
219
- });