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