@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
|
@@ -3,6 +3,7 @@ import { appQueryClient } from '@/app-query-client';
|
|
|
3
3
|
import { NcpChatThreadManager } from '@/features/chat/managers/ncp-chat-thread.manager';
|
|
4
4
|
import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
|
|
5
5
|
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
6
|
+
import type * as SharedApi from '@/shared/lib/api';
|
|
6
7
|
|
|
7
8
|
const { deleteNcpSessionMock, deleteSummaryMock } = vi.hoisted(() => ({
|
|
8
9
|
deleteNcpSessionMock: vi.fn(async () => ({ deleted: true, sessionId: 'parent-session-1' })),
|
|
@@ -10,7 +11,7 @@ const { deleteNcpSessionMock, deleteSummaryMock } = vi.hoisted(() => ({
|
|
|
10
11
|
}));
|
|
11
12
|
|
|
12
13
|
vi.mock('@/shared/lib/api', async (importOriginal) => {
|
|
13
|
-
const actual = await importOriginal<typeof
|
|
14
|
+
const actual = await importOriginal<typeof SharedApi>();
|
|
14
15
|
return {
|
|
15
16
|
...actual,
|
|
16
17
|
deleteNcpSession: deleteNcpSessionMock,
|
|
@@ -97,6 +98,56 @@ describe('NcpChatThreadManager', () => {
|
|
|
97
98
|
expect(uiManager.goToSession).toHaveBeenCalledWith('parent-session-1');
|
|
98
99
|
});
|
|
99
100
|
|
|
101
|
+
it('opens the session cron panel without changing route when already selected', () => {
|
|
102
|
+
const uiManager = {
|
|
103
|
+
goToSession: vi.fn(),
|
|
104
|
+
goToChatRoot: vi.fn(),
|
|
105
|
+
goToProviders: vi.fn(),
|
|
106
|
+
confirm: vi.fn(),
|
|
107
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
108
|
+
|
|
109
|
+
const manager = new NcpChatThreadManager(
|
|
110
|
+
uiManager,
|
|
111
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
112
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
manager.openSessionCronPanel('parent-session-1');
|
|
116
|
+
|
|
117
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
118
|
+
workspacePanelParentKey: 'parent-session-1',
|
|
119
|
+
activeWorkspacePanelKind: 'cron',
|
|
120
|
+
activeChildSessionKey: null,
|
|
121
|
+
activeWorkspaceFileKey: null,
|
|
122
|
+
});
|
|
123
|
+
expect(uiManager.goToSession).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('routes to the session before opening its cron panel when needed', () => {
|
|
127
|
+
useChatSessionListStore.setState({
|
|
128
|
+
snapshot: {
|
|
129
|
+
...useChatSessionListStore.getState().snapshot,
|
|
130
|
+
selectedSessionKey: 'another-session',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
const uiManager = {
|
|
134
|
+
goToSession: vi.fn(),
|
|
135
|
+
goToChatRoot: vi.fn(),
|
|
136
|
+
goToProviders: vi.fn(),
|
|
137
|
+
confirm: vi.fn(),
|
|
138
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
139
|
+
|
|
140
|
+
const manager = new NcpChatThreadManager(
|
|
141
|
+
uiManager,
|
|
142
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
143
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
manager.openSessionCronPanel('parent-session-1');
|
|
147
|
+
|
|
148
|
+
expect(uiManager.goToSession).toHaveBeenCalledWith('parent-session-1');
|
|
149
|
+
});
|
|
150
|
+
|
|
100
151
|
it('keeps preview and diff for the same file as separate workspace tabs', () => {
|
|
101
152
|
const uiManager = {
|
|
102
153
|
goToSession: vi.fn(),
|
|
@@ -62,6 +62,7 @@ export class NcpChatThreadManager {
|
|
|
62
62
|
parentSessionKey: null,
|
|
63
63
|
parentSessionLabel: null,
|
|
64
64
|
workspacePanelParentKey: null,
|
|
65
|
+
activeWorkspacePanelKind: null,
|
|
65
66
|
childSessionTabs: [],
|
|
66
67
|
activeChildSessionKey: null,
|
|
67
68
|
workspaceFileTabs: [],
|
|
@@ -156,6 +157,7 @@ export class NcpChatThreadManager {
|
|
|
156
157
|
const activeChildSessionKey = params.activeChildSessionKey?.trim() || null;
|
|
157
158
|
useChatThreadStore.getState().setSnapshot({
|
|
158
159
|
workspacePanelParentKey: parentSessionKey,
|
|
160
|
+
activeWorkspacePanelKind: 'child-session',
|
|
159
161
|
activeChildSessionKey,
|
|
160
162
|
activeWorkspaceFileKey: null,
|
|
161
163
|
});
|
|
@@ -170,6 +172,7 @@ export class NcpChatThreadManager {
|
|
|
170
172
|
}
|
|
171
173
|
useChatThreadStore.getState().setSnapshot({
|
|
172
174
|
workspacePanelParentKey: parentSessionKey,
|
|
175
|
+
activeWorkspacePanelKind: 'file',
|
|
173
176
|
workspaceFileTabs: this.upsertWorkspaceFileTab(nextTab),
|
|
174
177
|
activeWorkspaceFileKey: nextTab.key,
|
|
175
178
|
activeChildSessionKey: null,
|
|
@@ -196,6 +199,7 @@ export class NcpChatThreadManager {
|
|
|
196
199
|
}
|
|
197
200
|
useChatThreadStore.getState().setSnapshot({
|
|
198
201
|
workspacePanelParentKey: null,
|
|
202
|
+
activeWorkspacePanelKind: null,
|
|
199
203
|
activeChildSessionKey: null,
|
|
200
204
|
activeWorkspaceFileKey: null,
|
|
201
205
|
});
|
|
@@ -214,6 +218,7 @@ export class NcpChatThreadManager {
|
|
|
214
218
|
useChatThreadStore.getState().setSnapshot({
|
|
215
219
|
activeChildSessionKey: normalizedSessionKey,
|
|
216
220
|
activeWorkspaceFileKey: null,
|
|
221
|
+
activeWorkspacePanelKind: 'child-session',
|
|
217
222
|
});
|
|
218
223
|
};
|
|
219
224
|
|
|
@@ -229,6 +234,7 @@ export class NcpChatThreadManager {
|
|
|
229
234
|
useChatThreadStore.getState().setSnapshot({
|
|
230
235
|
activeWorkspaceFileKey: normalizedFileKey,
|
|
231
236
|
activeChildSessionKey: null,
|
|
237
|
+
activeWorkspacePanelKind: 'file',
|
|
232
238
|
});
|
|
233
239
|
};
|
|
234
240
|
|
|
@@ -254,11 +260,26 @@ export class NcpChatThreadManager {
|
|
|
254
260
|
closeWorkspacePanel = () => {
|
|
255
261
|
useChatThreadStore.getState().setSnapshot({
|
|
256
262
|
workspacePanelParentKey: null,
|
|
263
|
+
activeWorkspacePanelKind: null,
|
|
257
264
|
activeChildSessionKey: null,
|
|
258
265
|
activeWorkspaceFileKey: null,
|
|
259
266
|
});
|
|
260
267
|
};
|
|
261
268
|
|
|
269
|
+
openSessionCronPanel = (sessionKey: string) => {
|
|
270
|
+
const parentSessionKey = sessionKey.trim();
|
|
271
|
+
if (!parentSessionKey) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
useChatThreadStore.getState().setSnapshot({
|
|
275
|
+
workspacePanelParentKey: parentSessionKey,
|
|
276
|
+
activeWorkspacePanelKind: 'cron',
|
|
277
|
+
activeChildSessionKey: null,
|
|
278
|
+
activeWorkspaceFileKey: null,
|
|
279
|
+
});
|
|
280
|
+
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
281
|
+
};
|
|
282
|
+
|
|
262
283
|
closeChildSessionDetail = () => {
|
|
263
284
|
this.closeWorkspacePanel();
|
|
264
285
|
};
|
|
@@ -106,9 +106,6 @@ function useNcpChatPageBaseState(presenter: NcpChatPresenter) {
|
|
|
106
106
|
const selectedSessionKey = useChatSessionListStore(
|
|
107
107
|
(state) => state.snapshot.selectedSessionKey,
|
|
108
108
|
);
|
|
109
|
-
const draftSessionKey = useChatSessionListStore(
|
|
110
|
-
(state) => state.snapshot.draftSessionKey,
|
|
111
|
-
);
|
|
112
109
|
const selectedAgentId = useChatSessionListStore(
|
|
113
110
|
(state) => state.snapshot.selectedAgentId,
|
|
114
111
|
);
|
|
@@ -137,16 +134,16 @@ function useNcpChatPageBaseState(presenter: NcpChatPresenter) {
|
|
|
137
134
|
() => parseSessionKeyFromRoute(routeSessionIdParam),
|
|
138
135
|
[routeSessionIdParam],
|
|
139
136
|
);
|
|
140
|
-
const sessionKey = routeSessionKey ??
|
|
137
|
+
const sessionKey = routeSessionKey ?? undefined;
|
|
141
138
|
const hasSessionProjectRootOverride =
|
|
142
139
|
pendingProjectRoot !== null &&
|
|
143
|
-
pendingProjectRootSessionKey === sessionKey;
|
|
140
|
+
(!sessionKey || pendingProjectRootSessionKey === sessionKey);
|
|
144
141
|
const sessionProjectRootOverride = hasSessionProjectRootOverride
|
|
145
142
|
? pendingProjectRoot
|
|
146
143
|
: undefined;
|
|
147
144
|
const pageData = useNcpChatPageData({
|
|
148
145
|
query,
|
|
149
|
-
sessionKey,
|
|
146
|
+
sessionKey: sessionKey ?? null,
|
|
150
147
|
projectRootOverride: sessionProjectRootOverride,
|
|
151
148
|
currentSelectedModel,
|
|
152
149
|
pendingSessionType,
|
|
@@ -213,7 +210,7 @@ function useNcpChatPageState(presenter: NcpChatPresenter) {
|
|
|
213
210
|
? (agentsQuery.data?.agents ?? [])
|
|
214
211
|
: [{ id: selectedSession?.agentId ?? selectedAgentId }];
|
|
215
212
|
const derivedState = useNcpChatDerivedState({
|
|
216
|
-
sessionKey,
|
|
213
|
+
sessionKey: sessionKey ?? null,
|
|
217
214
|
selectedSession,
|
|
218
215
|
selectedAgentId,
|
|
219
216
|
availableAgents,
|
|
@@ -222,15 +219,16 @@ function useNcpChatPageState(presenter: NcpChatPresenter) {
|
|
|
222
219
|
selectedSessionType,
|
|
223
220
|
sessionTypeOptions,
|
|
224
221
|
});
|
|
222
|
+
const currentSessionRunning = agent.isRunning || selectedSession?.status === "running";
|
|
225
223
|
return {
|
|
226
224
|
...baseState,
|
|
227
225
|
availableAgents,
|
|
228
226
|
effectiveSessionProjectRoot,
|
|
229
227
|
effectiveSessionProjectName,
|
|
230
|
-
isSending: agent.isSending ||
|
|
231
|
-
isAwaitingAssistantOutput:
|
|
232
|
-
canStopCurrentRun:
|
|
233
|
-
stopDisabledReason:
|
|
228
|
+
isSending: agent.isSending || currentSessionRunning,
|
|
229
|
+
isAwaitingAssistantOutput: currentSessionRunning,
|
|
230
|
+
canStopCurrentRun: currentSessionRunning,
|
|
231
|
+
stopDisabledReason: currentSessionRunning ? null : "__preparing__",
|
|
234
232
|
lastSendError:
|
|
235
233
|
isRuntimeBlocked
|
|
236
234
|
? null
|
|
@@ -251,14 +249,13 @@ function useNcpChatStreamBindings(params: ReturnType<typeof useNcpChatPageState>
|
|
|
251
249
|
pendingProjectRootSessionKey,
|
|
252
250
|
presenter,
|
|
253
251
|
selectedSession,
|
|
254
|
-
selectedSessionKey,
|
|
255
252
|
selectedSessionKeyRef,
|
|
256
253
|
sessionKey,
|
|
257
254
|
} = params;
|
|
258
255
|
useEffect(() => {
|
|
259
256
|
presenter.chatStreamActionsManager.bind({
|
|
260
257
|
sendMessage: async (payload) => {
|
|
261
|
-
if (payload.sessionKey !== sessionKey) {
|
|
258
|
+
if ((payload.sessionKey ?? null) !== (sessionKey ?? null)) {
|
|
262
259
|
return;
|
|
263
260
|
}
|
|
264
261
|
const metadata = buildNcpSendMetadata({
|
|
@@ -267,7 +264,7 @@ function useNcpChatStreamBindings(params: ReturnType<typeof useNcpChatPageState>
|
|
|
267
264
|
thinkingLevel: payload.thinkingLevel,
|
|
268
265
|
sessionType: payload.sessionType,
|
|
269
266
|
projectRoot:
|
|
270
|
-
payload.sessionKey === pendingProjectRootSessionKey
|
|
267
|
+
!payload.sessionKey || payload.sessionKey === pendingProjectRootSessionKey
|
|
271
268
|
? pendingProjectRoot
|
|
272
269
|
: (selectedSession?.projectRoot ?? null),
|
|
273
270
|
requestedSkills: payload.requestedSkills,
|
|
@@ -322,7 +319,6 @@ function useNcpChatStreamBindings(params: ReturnType<typeof useNcpChatPageState>
|
|
|
322
319
|
pendingProjectRoot,
|
|
323
320
|
pendingProjectRootSessionKey,
|
|
324
321
|
presenter,
|
|
325
|
-
selectedSessionKey,
|
|
326
322
|
selectedSession?.projectRoot,
|
|
327
323
|
selectedSessionKeyRef,
|
|
328
324
|
sessionKey,
|
|
@@ -336,7 +332,6 @@ function usePendingProjectRootOverrideCleanup(
|
|
|
336
332
|
pendingProjectRoot,
|
|
337
333
|
pendingProjectRootSessionKey,
|
|
338
334
|
selectedSession,
|
|
339
|
-
selectedSessionKey,
|
|
340
335
|
} = params;
|
|
341
336
|
useEffect(() => {
|
|
342
337
|
if (
|
|
@@ -358,7 +353,6 @@ function usePendingProjectRootOverrideCleanup(
|
|
|
358
353
|
pendingProjectRoot,
|
|
359
354
|
pendingProjectRootSessionKey,
|
|
360
355
|
selectedSession,
|
|
361
|
-
selectedSessionKey,
|
|
362
356
|
]);
|
|
363
357
|
}
|
|
364
358
|
|
|
@@ -385,6 +379,22 @@ function useSelectedSessionAgentSync(params: ReturnType<typeof useNcpChatPageSta
|
|
|
385
379
|
}, [presenter, selectedAgentId, selectedSession?.agentId]);
|
|
386
380
|
}
|
|
387
381
|
|
|
382
|
+
function useMaterializedRootSessionRouteSync(
|
|
383
|
+
params: ReturnType<typeof useNcpChatPageState>,
|
|
384
|
+
) {
|
|
385
|
+
const { agent, presenter, routeSessionKey } = params;
|
|
386
|
+
const materializedSessionKey =
|
|
387
|
+
agent.snapshot.activeRun?.sessionId ??
|
|
388
|
+
agent.visibleMessages.find((message) => message.sessionId.trim())?.sessionId ??
|
|
389
|
+
null;
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
if (routeSessionKey || !materializedSessionKey) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
presenter.chatSessionListManager.materializeRootSessionRoute(materializedSessionKey);
|
|
395
|
+
}, [materializedSessionKey, presenter, routeSessionKey]);
|
|
396
|
+
}
|
|
397
|
+
|
|
388
398
|
export function NcpChatPage({ view }: ChatPageProps) {
|
|
389
399
|
const [presenter] = useState(() => new NcpChatPresenter());
|
|
390
400
|
const state = useNcpChatPageState(presenter);
|
|
@@ -392,6 +402,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
392
402
|
usePendingProjectRootOverrideCleanup(state);
|
|
393
403
|
useNcpChatUiBindings(state);
|
|
394
404
|
useSelectedSessionAgentSync(state);
|
|
405
|
+
useMaterializedRootSessionRouteSync(state);
|
|
395
406
|
useChatSessionSync({
|
|
396
407
|
view,
|
|
397
408
|
routeSessionKey: state.routeSessionKey,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { create, type StateCreator } from 'zustand';
|
|
2
|
-
import { createNcpSessionId } from '@/features/chat/utils/ncp-session-adapter.utils';
|
|
3
2
|
import type { SessionRunStatus } from '@/features/chat/types/session-run-status.types';
|
|
4
3
|
export type ChatSessionListMode = 'time-first' | 'project-first';
|
|
5
4
|
export type ChatSessionListSnapshot = {
|
|
6
5
|
selectedSessionKey: string | null;
|
|
7
|
-
draftSessionKey: string;
|
|
8
6
|
selectedAgentId: string;
|
|
9
7
|
query: string;
|
|
10
8
|
listMode: ChatSessionListMode;
|
|
@@ -50,7 +48,6 @@ type ChatSessionListStoreSet = Parameters<StateCreator<ChatSessionListStore>>[0]
|
|
|
50
48
|
|
|
51
49
|
const initialSnapshot: ChatSessionListSnapshot = {
|
|
52
50
|
selectedSessionKey: null,
|
|
53
|
-
draftSessionKey: createNcpSessionId(),
|
|
54
51
|
selectedAgentId: 'main',
|
|
55
52
|
query: '',
|
|
56
53
|
listMode: 'time-first'
|
|
@@ -55,9 +55,11 @@ export type ChatThreadSnapshot = {
|
|
|
55
55
|
messages: readonly NcpMessage[];
|
|
56
56
|
isSending: boolean;
|
|
57
57
|
isAwaitingAssistantOutput: boolean;
|
|
58
|
+
hasSubmittedDraftMessage: boolean;
|
|
58
59
|
parentSessionKey?: string | null;
|
|
59
60
|
parentSessionLabel?: string | null;
|
|
60
61
|
workspacePanelParentKey?: string | null;
|
|
62
|
+
activeWorkspacePanelKind?: "child-session" | "file" | "cron" | null;
|
|
61
63
|
childSessionTabs: ChatChildSessionTab[];
|
|
62
64
|
activeChildSessionKey?: string | null;
|
|
63
65
|
workspaceFileTabs: ChatWorkspaceFileTab[];
|
|
@@ -92,9 +94,11 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
92
94
|
messages: [],
|
|
93
95
|
isSending: false,
|
|
94
96
|
isAwaitingAssistantOutput: false,
|
|
97
|
+
hasSubmittedDraftMessage: false,
|
|
95
98
|
parentSessionKey: null,
|
|
96
99
|
parentSessionLabel: null,
|
|
97
100
|
workspacePanelParentKey: null,
|
|
101
|
+
activeWorkspacePanelKind: null,
|
|
98
102
|
childSessionTabs: [],
|
|
99
103
|
activeChildSessionKey: null,
|
|
100
104
|
workspaceFileTabs: [],
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { SessionEntryView } from '@/shared/lib/api';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
formatSessionListTime,
|
|
5
|
+
sessionActivityPreviewText,
|
|
6
|
+
sessionDisplayName,
|
|
7
|
+
sessionMatchesQuery
|
|
8
|
+
} from './chat-session-display.utils';
|
|
4
9
|
|
|
5
10
|
function createSession(overrides: Partial<SessionEntryView> = {}): SessionEntryView {
|
|
6
11
|
return {
|
|
@@ -52,4 +57,81 @@ describe('chat-session-display', () => {
|
|
|
52
57
|
it('treats an empty query as a match', () => {
|
|
53
58
|
expect(sessionMatchesQuery(createSession({ label: 'Anything' }), ' ')).toBe(true);
|
|
54
59
|
});
|
|
60
|
+
|
|
61
|
+
it('shows running activity before the previous reply preview', () => {
|
|
62
|
+
expect(
|
|
63
|
+
sessionActivityPreviewText(
|
|
64
|
+
createSession({
|
|
65
|
+
activityPreview: {
|
|
66
|
+
state: 'running',
|
|
67
|
+
statusText: '正在调用工具:shell',
|
|
68
|
+
replyText: '之前的回复',
|
|
69
|
+
timestamp: '2026-05-16T01:00:00.000Z'
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
).toBe('正在调用工具:shell');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('shows the final assistant reply after completion', () => {
|
|
77
|
+
expect(
|
|
78
|
+
sessionActivityPreviewText(
|
|
79
|
+
createSession({
|
|
80
|
+
activityPreview: {
|
|
81
|
+
state: 'completed',
|
|
82
|
+
statusText: '工具调用完成',
|
|
83
|
+
replyText: '最终回复内容',
|
|
84
|
+
timestamp: '2026-05-16T01:00:00.000Z'
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
)
|
|
88
|
+
).toBe('最终回复内容');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('formats today activity as time like WeChat session lists', () => {
|
|
92
|
+
expect(
|
|
93
|
+
formatSessionListTime(
|
|
94
|
+
new Date(2026, 4, 16, 9, 5),
|
|
95
|
+
'zh',
|
|
96
|
+
new Date(2026, 4, 16, 12, 0)
|
|
97
|
+
)
|
|
98
|
+
).toBe('09:05');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('formats yesterday activity as yesterday like WeChat session lists', () => {
|
|
102
|
+
expect(
|
|
103
|
+
formatSessionListTime(
|
|
104
|
+
new Date(2026, 4, 15, 23, 30),
|
|
105
|
+
'zh',
|
|
106
|
+
new Date(2026, 4, 16, 12, 0)
|
|
107
|
+
)
|
|
108
|
+
).toBe('昨天');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('formats recent activity within a week as weekday', () => {
|
|
112
|
+
expect(
|
|
113
|
+
formatSessionListTime(
|
|
114
|
+
new Date(2026, 4, 12, 18, 30),
|
|
115
|
+
'zh',
|
|
116
|
+
new Date(2026, 4, 16, 12, 0)
|
|
117
|
+
)
|
|
118
|
+
).toBe('星期二');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('formats older activity as date and keeps year only across years', () => {
|
|
122
|
+
expect(
|
|
123
|
+
formatSessionListTime(
|
|
124
|
+
new Date(2026, 4, 1, 18, 30),
|
|
125
|
+
'zh',
|
|
126
|
+
new Date(2026, 4, 16, 12, 0)
|
|
127
|
+
)
|
|
128
|
+
).toBe('5月1日');
|
|
129
|
+
expect(
|
|
130
|
+
formatSessionListTime(
|
|
131
|
+
new Date(2025, 11, 31, 18, 30),
|
|
132
|
+
'zh',
|
|
133
|
+
new Date(2026, 4, 16, 12, 0)
|
|
134
|
+
)
|
|
135
|
+
).toBe('2025年12月31日');
|
|
136
|
+
});
|
|
55
137
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SessionEntryView } from '@/shared/lib/api';
|
|
2
|
+
import { getLanguage, getLocale, t, type I18nLanguage } from '@/shared/lib/i18n';
|
|
2
3
|
|
|
3
4
|
export function sessionDisplayName(session: SessionEntryView): string {
|
|
4
5
|
const label = session.label?.trim();
|
|
@@ -9,6 +10,78 @@ export function sessionDisplayName(session: SessionEntryView): string {
|
|
|
9
10
|
return chunks[chunks.length - 1] || session.key;
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
export function sessionActivityPreviewText(session: SessionEntryView): string | null {
|
|
14
|
+
const preview = session.activityPreview;
|
|
15
|
+
if (!preview) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
if (preview.state === 'failed' || preview.state === 'running') {
|
|
19
|
+
return preview.statusText ?? preview.replyText ?? null;
|
|
20
|
+
}
|
|
21
|
+
if (preview.state === 'completed') {
|
|
22
|
+
return preview.replyText ?? preview.statusText ?? null;
|
|
23
|
+
}
|
|
24
|
+
return preview.statusText ?? preview.replyText ?? null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function startOfLocalDate(date: Date): number {
|
|
28
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isSameLocalYear(left: Date, right: Date): boolean {
|
|
32
|
+
return left.getFullYear() === right.getFullYear();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatChineseSessionDate(date: Date, now: Date): string {
|
|
36
|
+
const monthDay = `${date.getMonth() + 1}月${date.getDate()}日`;
|
|
37
|
+
return isSameLocalYear(date, now) ? monthDay : `${date.getFullYear()}年${monthDay}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function formatSessionListTime(
|
|
41
|
+
value?: string | Date,
|
|
42
|
+
lang: I18nLanguage = getLanguage(),
|
|
43
|
+
now: Date = new Date()
|
|
44
|
+
): string {
|
|
45
|
+
if (!value) {
|
|
46
|
+
return '-';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
50
|
+
if (Number.isNaN(date.getTime())) {
|
|
51
|
+
return typeof value === 'string' ? value : '-';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const locale = getLocale(lang);
|
|
55
|
+
const dateStart = startOfLocalDate(date);
|
|
56
|
+
const todayStart = startOfLocalDate(now);
|
|
57
|
+
const daysAgo = Math.floor((todayStart - dateStart) / 86_400_000);
|
|
58
|
+
|
|
59
|
+
if (daysAgo === 0) {
|
|
60
|
+
return new Intl.DateTimeFormat(locale, {
|
|
61
|
+
hour: '2-digit',
|
|
62
|
+
minute: '2-digit',
|
|
63
|
+
hour12: false
|
|
64
|
+
}).format(date);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (daysAgo === 1) {
|
|
68
|
+
return t('chatSidebarYesterday', lang);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (daysAgo > 1 && daysAgo < 7) {
|
|
72
|
+
return new Intl.DateTimeFormat(locale, { weekday: 'long' }).format(date);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (lang === 'zh') {
|
|
76
|
+
return formatChineseSessionDate(date, now);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const options: Intl.DateTimeFormatOptions = isSameLocalYear(date, now)
|
|
80
|
+
? { month: 'numeric', day: 'numeric' }
|
|
81
|
+
: { year: 'numeric', month: 'numeric', day: 'numeric' };
|
|
82
|
+
return new Intl.DateTimeFormat(locale, options).format(date);
|
|
83
|
+
}
|
|
84
|
+
|
|
12
85
|
function normalizeSessionSearchValue(value: string): string {
|
|
13
86
|
return value.trim().toLowerCase();
|
|
14
87
|
}
|
|
@@ -70,6 +70,28 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it('maps session activity preview metadata into the session entry', () => {
|
|
74
|
+
const adapted = adaptNcpSessionSummary(
|
|
75
|
+
createSummary({
|
|
76
|
+
metadata: {
|
|
77
|
+
last_activity_preview: {
|
|
78
|
+
state: 'completed',
|
|
79
|
+
replyText: '已经整理好方案',
|
|
80
|
+
statusText: '工具调用完成',
|
|
81
|
+
timestamp: '2026-05-16T01:00:00.000Z',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(adapted.activityPreview).toEqual({
|
|
88
|
+
state: 'completed',
|
|
89
|
+
replyText: '已经整理好方案',
|
|
90
|
+
statusText: '工具调用完成',
|
|
91
|
+
timestamp: '2026-05-16T01:00:00.000Z',
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
73
95
|
it('does not hydrate context window metadata from persisted session summaries', () => {
|
|
74
96
|
const adapted = adaptNcpSessionSummary(
|
|
75
97
|
createSummary({
|
|
@@ -3,6 +3,7 @@ import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
|
3
3
|
import type {
|
|
4
4
|
NcpMessageView,
|
|
5
5
|
NcpSessionSummaryView,
|
|
6
|
+
SessionActivityPreviewView,
|
|
6
7
|
SessionEntryView,
|
|
7
8
|
ThinkingLevel
|
|
8
9
|
} from '@/shared/lib/api';
|
|
@@ -13,6 +14,12 @@ import {
|
|
|
13
14
|
} from '@/shared/lib/session-project';
|
|
14
15
|
|
|
15
16
|
const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
|
|
17
|
+
const SESSION_ACTIVITY_PREVIEW_STATE_SET = new Set<SessionActivityPreviewView['state']>([
|
|
18
|
+
'running',
|
|
19
|
+
'completed',
|
|
20
|
+
'failed',
|
|
21
|
+
'idle'
|
|
22
|
+
]);
|
|
16
23
|
|
|
17
24
|
function stringifyUnknown(value: unknown): string {
|
|
18
25
|
if (typeof value === 'string') {
|
|
@@ -46,6 +53,28 @@ function readMetadata(summary: NcpSessionSummaryView): Record<string, unknown> |
|
|
|
46
53
|
return metadata as Record<string, unknown>;
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
function readNcpSessionActivityPreview(summary: NcpSessionSummaryView): SessionActivityPreviewView | null {
|
|
57
|
+
const metadata = readMetadata(summary);
|
|
58
|
+
const preview = metadata?.last_activity_preview;
|
|
59
|
+
if (!preview || typeof preview !== 'object' || Array.isArray(preview)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const previewRecord = preview as Record<string, unknown>;
|
|
63
|
+
const { state } = previewRecord;
|
|
64
|
+
const timestamp = readOptionalString(previewRecord.timestamp);
|
|
65
|
+
if (!SESSION_ACTIVITY_PREVIEW_STATE_SET.has(state as SessionActivityPreviewView['state']) || !timestamp) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const statusText = readOptionalString(previewRecord.statusText);
|
|
69
|
+
const replyText = readOptionalString(previewRecord.replyText);
|
|
70
|
+
return {
|
|
71
|
+
state: state as SessionActivityPreviewView['state'],
|
|
72
|
+
timestamp,
|
|
73
|
+
...(statusText ? { statusText } : {}),
|
|
74
|
+
...(replyText ? { replyText } : {})
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
49
78
|
export function readNcpSessionPreferredModel(summary: NcpSessionSummaryView): string | null {
|
|
50
79
|
const metadata = readMetadata(summary);
|
|
51
80
|
if (!metadata) {
|
|
@@ -286,9 +315,10 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
|
|
|
286
315
|
const parentSessionId = readNcpParentSessionId(summary);
|
|
287
316
|
const spawnedByRequestId = readNcpSpawnedByRequestId(summary);
|
|
288
317
|
const isPromotedChildSession = readPromotedChildSession(summary);
|
|
318
|
+
const activityPreview = readNcpSessionActivityPreview(summary);
|
|
289
319
|
return {
|
|
290
320
|
key: summary.sessionId,
|
|
291
|
-
createdAt: summary.updatedAt,
|
|
321
|
+
createdAt: summary.createdAt ?? summary.updatedAt,
|
|
292
322
|
updatedAt: summary.updatedAt,
|
|
293
323
|
...(lastMessageAt ? { lastMessageAt } : {}),
|
|
294
324
|
...(readAt ? { readAt } : {}),
|
|
@@ -301,10 +331,12 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
|
|
|
301
331
|
...(projectName ? { projectName } : {}),
|
|
302
332
|
sessionType: readNcpSessionType(summary),
|
|
303
333
|
sessionTypeMutable: false,
|
|
334
|
+
status: summary.status ?? 'idle',
|
|
304
335
|
isChildSession: Boolean(parentSessionId),
|
|
305
336
|
...(isPromotedChildSession ? { isPromotedChildSession } : {}),
|
|
306
337
|
...(parentSessionId ? { parentSessionId } : {}),
|
|
307
338
|
...(spawnedByRequestId ? { spawnedByRequestId } : {}),
|
|
339
|
+
...(activityPreview ? { activityPreview } : {}),
|
|
308
340
|
messageCount: summary.messageCount
|
|
309
341
|
};
|
|
310
342
|
}
|