@nextclaw/ui 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/assets/{api-DPcvCLfa.js → api-a14tR_z1.js} +7 -7
- package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
- package/dist/assets/chat-page-CEdavJXc.js +105 -0
- package/dist/assets/{config-split-page-BCJhqdK9.js → config-split-page-B5b9B0uE.js} +1 -1
- package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-ClH_Ec7n.js} +1 -1
- package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-DPNDnVpk.js} +1 -1
- package/dist/assets/{dist-DPrgcEC0.js → dist-BtCS2Ucq.js} +1 -1
- package/dist/assets/{dist-DsE4OsSS.js → dist-CLGGLm8b.js} +1 -1
- package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
- package/dist/assets/doc-browser-BJRLhztv.js +1 -0
- package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
- package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-CtJXjIol.js} +1 -1
- package/dist/assets/{external-link-C_dmG-WJ.js → external-link-CdJiG13p.js} +1 -1
- package/dist/assets/index-BDN1zzMQ.js +105 -0
- package/dist/assets/index-g3uSZXoc.css +1 -0
- package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
- package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
- package/dist/assets/middleware-CkFT3R4v.js +1 -0
- package/dist/assets/model-config-page-D6hYXROd.js +1 -0
- package/dist/assets/plus-DPjb0piO.js +1 -0
- package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
- package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
- package/dist/assets/{react-sZz5HSCm.js → react-C6cZgP44.js} +1 -1
- package/dist/assets/remote-B0G80uPr.js +1 -0
- package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
- package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
- package/dist/assets/{save-MzjoiHri.js → save-DKA0x4zk.js} +1 -1
- package/dist/assets/{search-B29dE4GN.js → search-BNlcoj5c.js} +1 -1
- package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-Cq9uJ-N8.js} +1 -1
- package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page-CxSbZLBo.js} +2 -2
- package/dist/assets/{select-BKmh05hd.js → select-CvTlDZTQ.js} +1 -1
- package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-8ffF2y2C.js} +1 -1
- package/dist/assets/use-config-DmT0o1gv.js +1 -0
- package/dist/assets/x-BiSSpgqB.js +1 -0
- package/dist/index.html +15 -14
- package/package.json +9 -9
- package/src/app/components/app-presenter-provider.tsx +11 -2
- package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
- package/src/app/components/layout/app-layout.tsx +18 -4
- package/src/app/components/layout/sidebar-items.tsx +194 -71
- package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
- package/src/app/components/layout/sidebar.tsx +340 -83
- package/src/app/managers/viewport-layout.manager.ts +10 -0
- package/src/app/stores/viewport-layout.store.ts +39 -3
- package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
- package/src/features/agents/components/agents-page.tsx +10 -6
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +16 -14
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +80 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
- package/src/features/chat/components/layout/chat-sidebar.tsx +59 -92
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
- package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
- package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
- package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
- package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
- package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
- package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
- package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
- package/src/features/chat/index.ts +0 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
- package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
- package/src/features/chat/managers/chat-thread.manager.ts +108 -34
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
- package/src/features/chat/presenters/chat.presenter.ts +2 -19
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
- package/src/features/chat/stores/chat-thread.store.ts +42 -26
- package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
- package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
- package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
- package/src/shared/lib/ui-document-title/index.ts +50 -21
- package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
- package/dist/assets/chat-page-BBnNJoKG.js +0 -105
- package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
- package/dist/assets/doc-browser-DgQedeAn.js +0 -1
- package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
- package/dist/assets/index-7o1WdUbU.js +0 -103
- package/dist/assets/index-DQWmEWzk.css +0 -1
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
- package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
- package/dist/assets/plus-Bnt56oqG.js +0 -1
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
- package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
- package/dist/assets/remote-B0LMs8u7.js +0 -1
- package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
- package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
- package/dist/assets/use-config-DZkBWIdL.js +0 -1
- package/dist/assets/x-CW2KO-Hz.js +0 -1
- package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
- package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
- package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
- package/src/features/chat/managers/chat-input.manager.ts +0 -430
- package/src/features/chat/managers/chat-run.manager.ts +0 -110
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
- package/src/features/chat/stores/chat-input.store.ts +0 -104
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
import { createChatComposerTextNode } from '@nextclaw/agent-chat-ui';
|
|
2
|
-
import { RUNTIME_DEFAULT_MODEL_VALUE } from '@nextclaw/shared';
|
|
3
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
-
import { ChatInputManager } from '@/features/chat/managers/chat-input.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 { useSystemStatusStore } from '@/features/system-status';
|
|
9
|
-
|
|
10
|
-
function resetChatInputManagerStoreState() {
|
|
11
|
-
useChatInputStore.setState({
|
|
12
|
-
snapshot: {
|
|
13
|
-
...useChatInputStore.getState().snapshot,
|
|
14
|
-
draft: 'hello from current thread',
|
|
15
|
-
composerNodes: [createChatComposerTextNode('hello from current thread')],
|
|
16
|
-
attachments: [],
|
|
17
|
-
selectedSkills: [],
|
|
18
|
-
composerFocusRequest: null,
|
|
19
|
-
selectedSessionType: 'native',
|
|
20
|
-
selectedModel: 'gpt-5',
|
|
21
|
-
selectedThinkingLevel: null,
|
|
22
|
-
isProviderStateResolved: true,
|
|
23
|
-
modelOptions: [
|
|
24
|
-
{
|
|
25
|
-
value: 'gpt-5',
|
|
26
|
-
modelLabel: 'GPT-5',
|
|
27
|
-
providerLabel: 'OpenAI',
|
|
28
|
-
thinkingCapability: null,
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
useSystemStatusStore.setState({
|
|
34
|
-
state: {
|
|
35
|
-
...useSystemStatusStore.getState().state,
|
|
36
|
-
lifecyclePhase: 'ready',
|
|
37
|
-
bootstrapStatus: {
|
|
38
|
-
phase: 'ready',
|
|
39
|
-
ncpAgent: {
|
|
40
|
-
state: 'ready',
|
|
41
|
-
},
|
|
42
|
-
extensionLoading: {
|
|
43
|
-
state: 'ready',
|
|
44
|
-
loadedExtensionCount: 1,
|
|
45
|
-
totalExtensionCount: 1,
|
|
46
|
-
},
|
|
47
|
-
channels: {
|
|
48
|
-
state: 'ready',
|
|
49
|
-
enabled: [],
|
|
50
|
-
},
|
|
51
|
-
remote: {
|
|
52
|
-
state: 'pending',
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
useChatSessionListStore.setState({
|
|
58
|
-
optimisticReadAtBySessionKey: {},
|
|
59
|
-
snapshot: {
|
|
60
|
-
...useChatSessionListStore.getState().snapshot,
|
|
61
|
-
selectedSessionKey: 'stale-selected-session',
|
|
62
|
-
selectedAgentId: 'main',
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
useChatThreadStore.setState({
|
|
66
|
-
snapshot: {
|
|
67
|
-
...useChatThreadStore.getState().snapshot,
|
|
68
|
-
sessionKey: 'current-route-session',
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
describe('ChatInputManager', () => {
|
|
74
|
-
beforeEach(resetChatInputManagerStoreState);
|
|
75
|
-
|
|
76
|
-
it('sends through the current thread session when selected session state is stale', async () => {
|
|
77
|
-
const chatRunManager = {
|
|
78
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
79
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
80
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
81
|
-
const sessionListManager = {
|
|
82
|
-
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
83
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
84
|
-
const manager = new ChatInputManager(
|
|
85
|
-
chatRunManager,
|
|
86
|
-
sessionListManager,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
await manager.send();
|
|
90
|
-
|
|
91
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledTimes(1);
|
|
92
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledWith(
|
|
93
|
-
expect.objectContaining({
|
|
94
|
-
sessionKey: 'current-route-session',
|
|
95
|
-
message: 'hello from current thread',
|
|
96
|
-
}),
|
|
97
|
-
);
|
|
98
|
-
expect(sessionListManager.ensureDraftSession).not.toHaveBeenCalled();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('sends without a session key while /chat is still in blank-draft mode', async () => {
|
|
102
|
-
useChatThreadStore.setState({
|
|
103
|
-
snapshot: {
|
|
104
|
-
...useChatThreadStore.getState().snapshot,
|
|
105
|
-
sessionKey: null,
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
useChatSessionListStore.setState({
|
|
109
|
-
snapshot: {
|
|
110
|
-
...useChatSessionListStore.getState().snapshot,
|
|
111
|
-
selectedSessionKey: null,
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
const chatRunManager = {
|
|
115
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
116
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
117
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
118
|
-
const sessionListManager = {
|
|
119
|
-
ensureDraftSession: vi.fn(() => 'materialized-draft-session'),
|
|
120
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
121
|
-
const manager = new ChatInputManager(
|
|
122
|
-
chatRunManager,
|
|
123
|
-
sessionListManager,
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
await manager.send();
|
|
127
|
-
|
|
128
|
-
expect(sessionListManager.ensureDraftSession).toHaveBeenCalledWith('native');
|
|
129
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledWith(
|
|
130
|
-
expect.not.objectContaining({
|
|
131
|
-
sessionKey: expect.any(String),
|
|
132
|
-
}),
|
|
133
|
-
);
|
|
134
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledWith(
|
|
135
|
-
expect.objectContaining({
|
|
136
|
-
message: 'hello from current thread',
|
|
137
|
-
}),
|
|
138
|
-
);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('uses the default workspace as the project root for a blank draft send', async () => {
|
|
142
|
-
useChatInputStore.setState({
|
|
143
|
-
snapshot: {
|
|
144
|
-
...useChatInputStore.getState().snapshot,
|
|
145
|
-
defaultProjectRoot: '/Users/demo/.nextclaw/workspace',
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
useChatThreadStore.setState({
|
|
149
|
-
snapshot: {
|
|
150
|
-
...useChatThreadStore.getState().snapshot,
|
|
151
|
-
sessionKey: null,
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
useChatSessionListStore.setState({
|
|
155
|
-
snapshot: {
|
|
156
|
-
...useChatSessionListStore.getState().snapshot,
|
|
157
|
-
selectedSessionKey: null,
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
const chatRunManager = {
|
|
161
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
162
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
163
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
164
|
-
const sessionListManager = {
|
|
165
|
-
ensureDraftSession: vi.fn(() => 'materialized-draft-session'),
|
|
166
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
167
|
-
const manager = new ChatInputManager(
|
|
168
|
-
chatRunManager,
|
|
169
|
-
sessionListManager,
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
await manager.send();
|
|
173
|
-
|
|
174
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledWith(
|
|
175
|
-
expect.objectContaining({
|
|
176
|
-
projectRoot: '/Users/demo/.nextclaw/workspace',
|
|
177
|
-
}),
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('does not send while the runtime is still blocked during startup', async () => {
|
|
182
|
-
useChatInputStore.setState({
|
|
183
|
-
snapshot: {
|
|
184
|
-
...useChatInputStore.getState().snapshot,
|
|
185
|
-
isProviderStateResolved: false,
|
|
186
|
-
modelOptions: [],
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
useSystemStatusStore.setState({
|
|
190
|
-
state: {
|
|
191
|
-
...useSystemStatusStore.getState().state,
|
|
192
|
-
lifecyclePhase: 'cold-starting',
|
|
193
|
-
bootstrapStatus: {
|
|
194
|
-
...useSystemStatusStore.getState().state.bootstrapStatus!,
|
|
195
|
-
phase: 'kernel-starting',
|
|
196
|
-
ncpAgent: {
|
|
197
|
-
state: 'pending',
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
const chatRunManager = {
|
|
203
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
204
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
205
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
206
|
-
const sessionListManager = {
|
|
207
|
-
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
208
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
209
|
-
const manager = new ChatInputManager(
|
|
210
|
-
chatRunManager,
|
|
211
|
-
sessionListManager,
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
await manager.send();
|
|
215
|
-
|
|
216
|
-
expect(chatRunManager.sendMessage).not.toHaveBeenCalled();
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('still attempts to send when provider metadata is stale or the session type is marked unavailable', async () => {
|
|
220
|
-
useChatInputStore.setState({
|
|
221
|
-
snapshot: {
|
|
222
|
-
...useChatInputStore.getState().snapshot,
|
|
223
|
-
isProviderStateResolved: true,
|
|
224
|
-
modelOptions: [],
|
|
225
|
-
sessionTypeUnavailable: true,
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
const chatRunManager = {
|
|
229
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
230
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
231
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
232
|
-
const sessionListManager = {
|
|
233
|
-
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
234
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
235
|
-
const manager = new ChatInputManager(
|
|
236
|
-
chatRunManager,
|
|
237
|
-
sessionListManager,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
await manager.send();
|
|
241
|
-
|
|
242
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledTimes(1);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('includes the current thread project root in the run payload', async () => {
|
|
246
|
-
useChatThreadStore.setState({
|
|
247
|
-
snapshot: {
|
|
248
|
-
...useChatThreadStore.getState().snapshot,
|
|
249
|
-
sessionKey: 'current-route-session',
|
|
250
|
-
sessionProjectRoot: '/tmp/project-alpha',
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
const chatRunManager = {
|
|
254
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
255
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
256
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
257
|
-
const sessionListManager = {
|
|
258
|
-
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
259
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
260
|
-
const manager = new ChatInputManager(
|
|
261
|
-
chatRunManager,
|
|
262
|
-
sessionListManager,
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
await manager.send();
|
|
266
|
-
|
|
267
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledWith(
|
|
268
|
-
expect.objectContaining({
|
|
269
|
-
projectRoot: '/tmp/project-alpha',
|
|
270
|
-
}),
|
|
271
|
-
);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('restores the composer when sending through the run manager fails', async () => {
|
|
275
|
-
const chatRunManager = {
|
|
276
|
-
sendMessage: vi.fn().mockRejectedValue(new Error('send failed')),
|
|
277
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
278
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
279
|
-
const sessionListManager = {
|
|
280
|
-
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
281
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
282
|
-
const manager = new ChatInputManager(
|
|
283
|
-
chatRunManager,
|
|
284
|
-
sessionListManager,
|
|
285
|
-
);
|
|
286
|
-
const originalComposerNodes = useChatInputStore.getState().snapshot.composerNodes;
|
|
287
|
-
|
|
288
|
-
await expect(manager.send()).rejects.toThrow('send failed');
|
|
289
|
-
|
|
290
|
-
expect(useChatInputStore.getState().snapshot.draft).toBe('hello from current thread');
|
|
291
|
-
expect(useChatInputStore.getState().snapshot.composerNodes).toEqual(originalComposerNodes);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
describe('ChatInputManager composer focus', () => {
|
|
297
|
-
beforeEach(resetChatInputManagerStoreState);
|
|
298
|
-
|
|
299
|
-
it('creates and consumes one-shot composer focus requests', () => {
|
|
300
|
-
const manager = new ChatInputManager(
|
|
301
|
-
{} as ConstructorParameters<typeof ChatInputManager>[0],
|
|
302
|
-
{} as ConstructorParameters<typeof ChatInputManager>[1],
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
manager.requestComposerFocusAtEnd();
|
|
306
|
-
|
|
307
|
-
const request = useChatInputStore.getState().snapshot.composerFocusRequest;
|
|
308
|
-
expect(request).toEqual({ id: 1, placement: 'end' });
|
|
309
|
-
|
|
310
|
-
manager.consumeComposerFocusRequest(999);
|
|
311
|
-
expect(useChatInputStore.getState().snapshot.composerFocusRequest).toEqual(request);
|
|
312
|
-
|
|
313
|
-
manager.consumeComposerFocusRequest(request!.id);
|
|
314
|
-
expect(useChatInputStore.getState().snapshot.composerFocusRequest).toBeNull();
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('applies prompt suggestions to the composer and requests focus', () => {
|
|
318
|
-
const manager = new ChatInputManager(
|
|
319
|
-
{} as ConstructorParameters<typeof ChatInputManager>[0],
|
|
320
|
-
{} as ConstructorParameters<typeof ChatInputManager>[1],
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
manager.applyPromptSuggestion(' Build a release plan ');
|
|
324
|
-
|
|
325
|
-
expect(useChatInputStore.getState().snapshot).toMatchObject({
|
|
326
|
-
draft: 'Build a release plan',
|
|
327
|
-
composerNodes: [
|
|
328
|
-
expect.objectContaining({
|
|
329
|
-
type: 'text',
|
|
330
|
-
text: 'Build a release plan',
|
|
331
|
-
}),
|
|
332
|
-
],
|
|
333
|
-
composerFocusRequest: { id: 1, placement: 'end' },
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
describe('ChatInputManager configuration sync', () => {
|
|
339
|
-
beforeEach(resetChatInputManagerStoreState);
|
|
340
|
-
|
|
341
|
-
it('syncs session model and thinking preferences inside the input manager', () => {
|
|
342
|
-
useChatInputStore.setState({
|
|
343
|
-
snapshot: {
|
|
344
|
-
...useChatInputStore.getState().snapshot,
|
|
345
|
-
modelOptions: [
|
|
346
|
-
{
|
|
347
|
-
value: 'anthropic/claude-sonnet-4',
|
|
348
|
-
modelLabel: 'claude-sonnet-4',
|
|
349
|
-
providerLabel: 'Anthropic',
|
|
350
|
-
thinkingCapability: {
|
|
351
|
-
supported: ['off', 'medium'],
|
|
352
|
-
default: 'medium',
|
|
353
|
-
},
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
value: 'openai/gpt-5',
|
|
357
|
-
modelLabel: 'gpt-5',
|
|
358
|
-
providerLabel: 'OpenAI',
|
|
359
|
-
thinkingCapability: {
|
|
360
|
-
supported: ['off', 'high'],
|
|
361
|
-
default: 'high',
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
],
|
|
365
|
-
},
|
|
366
|
-
});
|
|
367
|
-
useChatThreadStore.setState({
|
|
368
|
-
snapshot: {
|
|
369
|
-
...useChatThreadStore.getState().snapshot,
|
|
370
|
-
sessionKey: 'session-1',
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
const manager = new ChatInputManager(
|
|
374
|
-
{} as ConstructorParameters<typeof ChatInputManager>[0],
|
|
375
|
-
{} as ConstructorParameters<typeof ChatInputManager>[1],
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
manager.syncSessionPreferences({
|
|
379
|
-
selectedSessionExists: true,
|
|
380
|
-
selectedSessionPreferredModel: 'openai/gpt-5',
|
|
381
|
-
selectedSessionPreferredThinking: 'high',
|
|
382
|
-
fallbackPreferredModel: 'anthropic/claude-sonnet-4',
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
expect(useChatInputStore.getState().snapshot).toMatchObject({
|
|
386
|
-
selectedModel: 'openai/gpt-5',
|
|
387
|
-
selectedThinkingLevel: 'high',
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it('sends the runtime-default sentinel so the backend can ignore stale session model metadata', async () => {
|
|
392
|
-
useChatInputStore.setState({
|
|
393
|
-
snapshot: {
|
|
394
|
-
...useChatInputStore.getState().snapshot,
|
|
395
|
-
selectedSessionType: 'codex',
|
|
396
|
-
selectedModel: RUNTIME_DEFAULT_MODEL_VALUE,
|
|
397
|
-
},
|
|
398
|
-
});
|
|
399
|
-
const chatRunManager = {
|
|
400
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
401
|
-
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
402
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
403
|
-
const sessionListManager = {
|
|
404
|
-
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
405
|
-
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
406
|
-
const manager = new ChatInputManager(chatRunManager, sessionListManager);
|
|
407
|
-
|
|
408
|
-
await manager.send();
|
|
409
|
-
|
|
410
|
-
expect(chatRunManager.sendMessage).toHaveBeenCalledWith(
|
|
411
|
-
expect.objectContaining({
|
|
412
|
-
sessionType: 'codex',
|
|
413
|
-
model: RUNTIME_DEFAULT_MODEL_VALUE,
|
|
414
|
-
}),
|
|
415
|
-
);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('resolves and clears pending project root overrides inside the input manager', () => {
|
|
419
|
-
useChatInputStore.setState({
|
|
420
|
-
snapshot: {
|
|
421
|
-
...useChatInputStore.getState().snapshot,
|
|
422
|
-
pendingProjectRoot: '/tmp/project-alpha',
|
|
423
|
-
pendingProjectRootSessionKey: 'session-1',
|
|
424
|
-
},
|
|
425
|
-
});
|
|
426
|
-
const manager = new ChatInputManager(
|
|
427
|
-
{} as ConstructorParameters<typeof ChatInputManager>[0],
|
|
428
|
-
{} as ConstructorParameters<typeof ChatInputManager>[1],
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
expect(
|
|
432
|
-
manager.resolveProjectRootForSend({
|
|
433
|
-
sessionKey: 'session-1',
|
|
434
|
-
selectedSessionProjectRoot: '/tmp/server-project',
|
|
435
|
-
}),
|
|
436
|
-
).toBe('/tmp/project-alpha');
|
|
437
|
-
|
|
438
|
-
manager.clearPendingProjectRootOverrideForSession({
|
|
439
|
-
sessionKey: 'session-1',
|
|
440
|
-
selectedSessionProjectRoot: '/tmp/project-alpha',
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
expect(useChatInputStore.getState().snapshot).toMatchObject({
|
|
444
|
-
pendingProjectRoot: null,
|
|
445
|
-
pendingProjectRootSessionKey: null,
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { NcpMessage } from '@nextclaw/ncp';
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
|
|
4
|
-
import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
|
|
5
|
-
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
6
|
-
|
|
7
|
-
function createChatRunManager() {
|
|
8
|
-
const uiManager = {
|
|
9
|
-
isAtChatRoot: vi.fn(() => true),
|
|
10
|
-
goToSession: vi.fn(),
|
|
11
|
-
};
|
|
12
|
-
return new ChatRunManager(
|
|
13
|
-
uiManager as unknown as ConstructorParameters<typeof ChatRunManager>[0],
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('ChatRunManager run snapshots', () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
useChatInputStore.setState({
|
|
20
|
-
snapshot: {
|
|
21
|
-
...useChatInputStore.getState().snapshot,
|
|
22
|
-
canStopGeneration: false,
|
|
23
|
-
sendError: null,
|
|
24
|
-
isSending: false,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
useChatThreadStore.setState({
|
|
28
|
-
snapshot: {
|
|
29
|
-
...useChatThreadStore.getState().snapshot,
|
|
30
|
-
sessionKey: null,
|
|
31
|
-
isHistoryLoading: false,
|
|
32
|
-
messages: [],
|
|
33
|
-
isSending: false,
|
|
34
|
-
isAwaitingAssistantOutput: false,
|
|
35
|
-
contextWindow: null,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('syncs the run snapshot into chat input and thread state', () => {
|
|
41
|
-
const manager = createChatRunManager();
|
|
42
|
-
const message: NcpMessage = {
|
|
43
|
-
id: 'message-1',
|
|
44
|
-
sessionId: 'session-1',
|
|
45
|
-
role: 'assistant',
|
|
46
|
-
status: 'final',
|
|
47
|
-
parts: [{ type: 'text', text: 'done' }],
|
|
48
|
-
timestamp: '2026-06-10T00:00:00.000Z',
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
manager.applyRunSnapshot({
|
|
52
|
-
routeSessionKey: 'session-1',
|
|
53
|
-
isHydrating: true,
|
|
54
|
-
isSending: false,
|
|
55
|
-
isRunning: true,
|
|
56
|
-
visibleMessages: [message],
|
|
57
|
-
contextWindow: {
|
|
58
|
-
usedContextTokens: 10,
|
|
59
|
-
totalContextTokens: 100,
|
|
60
|
-
availableContextTokens: 90,
|
|
61
|
-
prunedUsedContextTokens: 10,
|
|
62
|
-
droppedHistoryCount: 0,
|
|
63
|
-
truncatedToolResultCount: 0,
|
|
64
|
-
truncatedSystemPrompt: false,
|
|
65
|
-
truncatedUserMessage: false,
|
|
66
|
-
compacted: false,
|
|
67
|
-
compactedUsedContextTokens: 10,
|
|
68
|
-
compactedMessageCount: 0,
|
|
69
|
-
updatedAt: '2026-06-10T00:00:00.000Z',
|
|
70
|
-
},
|
|
71
|
-
sendErrorMessage: 'failed',
|
|
72
|
-
materializedSessionKey: null,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
expect(useChatInputStore.getState().snapshot).toMatchObject({
|
|
76
|
-
canStopGeneration: true,
|
|
77
|
-
sendError: 'failed',
|
|
78
|
-
isSending: true,
|
|
79
|
-
});
|
|
80
|
-
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
81
|
-
sessionKey: 'session-1',
|
|
82
|
-
isHistoryLoading: true,
|
|
83
|
-
messages: [message],
|
|
84
|
-
isSending: true,
|
|
85
|
-
isAwaitingAssistantOutput: true,
|
|
86
|
-
contextWindow: {
|
|
87
|
-
usedContextTokens: 10,
|
|
88
|
-
totalContextTokens: 100,
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('resets stale context window state when switching sessions', () => {
|
|
94
|
-
const manager = createChatRunManager();
|
|
95
|
-
useChatThreadStore.getState().setSnapshot({
|
|
96
|
-
sessionKey: 'session-previous',
|
|
97
|
-
contextWindow: {
|
|
98
|
-
usedContextTokens: 80,
|
|
99
|
-
totalContextTokens: 100,
|
|
100
|
-
availableContextTokens: 20,
|
|
101
|
-
prunedUsedContextTokens: 80,
|
|
102
|
-
droppedHistoryCount: 0,
|
|
103
|
-
truncatedToolResultCount: 0,
|
|
104
|
-
truncatedSystemPrompt: false,
|
|
105
|
-
truncatedUserMessage: false,
|
|
106
|
-
compacted: false,
|
|
107
|
-
compactedUsedContextTokens: 80,
|
|
108
|
-
compactedMessageCount: 0,
|
|
109
|
-
updatedAt: '2026-06-10T00:00:00.000Z',
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
manager.applyRunSnapshot({
|
|
114
|
-
routeSessionKey: 'session-next',
|
|
115
|
-
isHydrating: true,
|
|
116
|
-
isSending: false,
|
|
117
|
-
isRunning: false,
|
|
118
|
-
visibleMessages: [],
|
|
119
|
-
contextWindow: null,
|
|
120
|
-
sendErrorMessage: null,
|
|
121
|
-
materializedSessionKey: null,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
125
|
-
sessionKey: 'session-next',
|
|
126
|
-
contextWindow: null,
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
});
|