@nextclaw/ui 0.12.34 → 0.12.35-beta.0
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 +63 -0
- package/dist/assets/{api-rASx2-N4.js → api-De1PAch0.js} +3 -3
- package/dist/assets/{book-open-BcnAiKde.js → book-open-825Pcd0z.js} +1 -1
- package/dist/assets/channels-list-page-CGjXDSD-.js +8 -0
- package/dist/assets/chat-page-RsUTK_bY.js +105 -0
- package/dist/assets/{config-split-page-CcrEUtwu.js → config-split-page-BvfxFQSb.js} +1 -1
- package/dist/assets/{createLucideIcon-DzY6wN61.js → createLucideIcon-CeNHY6BW.js} +1 -1
- package/dist/assets/desktop-update-config-B_YIv5ZJ.js +1 -0
- package/dist/assets/{dist-NBjcwlsw.js → dist-uH7tiW6E.js} +1 -1
- package/dist/assets/{doc-browser-D2pq4IHR.js → doc-browser-CDe9yz9n.js} +1 -1
- package/dist/assets/doc-browser-DwT64GuF.js +1 -0
- package/dist/assets/{doc-browser-context-BPeeiNJ_.js → doc-browser-context-CQAx_92e.js} +1 -1
- package/dist/assets/{es2015-CVAuMqlM.js → es2015-c9XYpli5.js} +1 -1
- package/dist/assets/{external-link-CpEvG65F.js → external-link-BE1deZ_c.js} +1 -1
- package/dist/assets/i18n-DnubSHoK.js +1 -0
- package/dist/assets/index-6WZ6JjaU.css +1 -0
- package/dist/assets/index-BjH_p3R6.js +103 -0
- package/dist/assets/{key-round-DUq47t0P.js → key-round-CHo3gqpO.js} +1 -1
- package/dist/assets/mcp-marketplace-page-0C214hGi.js +40 -0
- package/dist/assets/mcp-marketplace-page-amWcMncu.js +1 -0
- package/dist/assets/model-config-B8zNrkag.js +1 -0
- package/dist/assets/notice-card-DO33_LzD.js +5 -0
- package/dist/assets/play-CqlWbVNo.js +1 -0
- package/dist/assets/plus-p61tQh2-.js +1 -0
- package/dist/assets/provider-scoped-model-input-B0_r_4EY.js +1 -0
- package/dist/assets/providers-list-B6v3foi7.js +1 -0
- package/dist/assets/remote-k671rLmY.js +1 -0
- package/dist/assets/{rotate-cw-BN9yjccP.js → rotate-cw-CYUByuqo.js} +1 -1
- package/dist/assets/runtime-config-page-DgH-XJwK.js +1 -0
- package/dist/assets/{save-CO_4qf6b.js → save-B6Ga8JIq.js} +1 -1
- package/dist/assets/{search-CRtQwr-h.js → search-5kKpgNoP.js} +1 -1
- package/dist/assets/search-config-DPZ6d9y6.js +1 -0
- package/dist/assets/{secrets-config-DFbHCb9n.js → secrets-config-CrXA11bo.js} +2 -2
- package/dist/assets/{select-HI5kiwX_.js → select-C0hGOCOK.js} +1 -1
- package/dist/assets/{setting-row-B4QnghjV.js → setting-row-xsM_0nTd.js} +1 -1
- package/dist/assets/{tabs-custom-D48zdZoc.js → tabs-custom-Bnqfhe4p.js} +1 -1
- package/dist/assets/{tag-chip-BPgMfp9l.js → tag-chip-cktJJMzh.js} +1 -1
- package/dist/assets/x-DDQw-C2x.js +1 -0
- package/dist/index.html +17 -29
- package/package.json +9 -9
- package/src/app/components/layout/app-layout.test.tsx +49 -4
- package/src/app/components/layout/app-layout.tsx +4 -7
- package/src/app/components/layout/settings-entry-page.test.tsx +9 -0
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -2
- package/src/app/components/layout/sidebar.tsx +3 -3
- package/src/app/configs/app-navigation.config.ts +0 -12
- package/src/app/hooks/use-app-event-consumers.ts +1 -6
- package/src/app/hooks/use-viewport-layout.ts +0 -1
- package/src/app/index.tsx +2 -10
- package/src/features/channels/components/config/channel-form.tsx +85 -47
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +45 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +119 -1
- package/src/features/channels/hooks/use-channel-auth.ts +8 -1
- package/src/features/channels/index.ts +1 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +2 -2
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +2 -7
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +27 -3
- package/src/features/chat/components/layout/chat-sidebar.tsx +2 -3
- package/src/features/chat/managers/chat-session-list.manager.test.ts +12 -11
- package/src/features/chat/managers/chat-session-list.manager.ts +2 -1
- package/src/features/chat/managers/chat-ui.manager.ts +2 -2
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +3 -3
- package/src/features/chat/pages/ncp-chat-page.test.ts +3 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +19 -19
- package/src/features/chat/utils/chat-session-route.utils.test.ts +14 -0
- package/src/features/chat/utils/chat-session-route.utils.ts +3 -1
- package/src/features/chat/utils/ncp-chat-runtime-availability.utils.test.ts +6 -6
- package/src/features/marketplace/components/marketplace-list-card.tsx +4 -36
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +0 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +9 -128
- package/src/features/marketplace/components/marketplace-page.tsx +16 -72
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +1 -1
- package/src/features/marketplace/hooks/use-marketplace.ts +1 -10
- package/src/features/marketplace/utils/marketplace-installed-cache.utils.test.ts +23 -21
- package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +12 -12
- package/src/features/system-status/managers/system-status.manager.test.ts +6 -6
- package/src/index.css +1 -1
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +29 -13
- package/src/platforms/desktop/components/desktop-app-shell.tsx +14 -16
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +2 -3
- package/src/platforms/mobile/components/mobile-app-shell.tsx +3 -1
- package/src/platforms/mobile/components/mobile-topbar.tsx +6 -5
- package/src/platforms/mobile/index.ts +1 -0
- package/src/shared/components/config/provider-form-support.ts +51 -33
- package/src/shared/components/config/provider-form.tsx +68 -35
- package/src/shared/components/config/provider-models-section.tsx +19 -4
- package/src/shared/components/model-config.test.tsx +2 -0
- package/src/shared/components/ui/button.tsx +1 -1
- package/src/shared/lib/api/channel-auth.types.ts +8 -0
- package/src/shared/lib/api/types.ts +17 -20
- package/src/shared/lib/api/utils/channel-auth.utils.ts +10 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +0 -5
- package/src/shared/lib/i18n/channel-auth.constants.ts +24 -10
- package/src/shared/lib/i18n/chat-labels.utils.ts +2 -2
- package/src/shared/lib/i18n/index.ts +2 -0
- package/src/shared/lib/i18n/marketplace.ts +0 -16
- package/src/shared/lib/provider-models/index.ts +41 -13
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/dist/assets/app-manager-provider-CUkcbdWk.js +0 -1
- package/dist/assets/channels-list-page-ZWYqfcCe.js +0 -8
- package/dist/assets/chat-page-ACm58eUO.js +0 -1
- package/dist/assets/cpu-DKc3bQic.js +0 -3
- package/dist/assets/desktop-C-cDRCBY.js +0 -3
- package/dist/assets/desktop-update-config-DAD9JN9Q.js +0 -1
- package/dist/assets/dialog-CR80q3Rm.js +0 -5
- package/dist/assets/doc-browser-DhIkcy0-.js +0 -1
- package/dist/assets/doc-browser-fLs0TcdS.js +0 -1
- package/dist/assets/download-CMM8po31.js +0 -1
- package/dist/assets/i18n-D1144VAA.js +0 -1
- package/dist/assets/index-DV57tBmg.css +0 -1
- package/dist/assets/index-Db9Cufsy.js +0 -100
- package/dist/assets/marketplace-page-CAp_gpNe.js +0 -105
- package/dist/assets/marketplace-page-PPY5uMHK.js +0 -1
- package/dist/assets/mcp-marketplace-page-87__92b1.js +0 -1
- package/dist/assets/mcp-marketplace-page-C6HENB6V.js +0 -40
- package/dist/assets/model-config-CK3a8DWH.js +0 -1
- package/dist/assets/notice-card-CF4AJPM3.js +0 -1
- package/dist/assets/play-AqrNslHI.js +0 -1
- package/dist/assets/plus-B-YHtTNC.js +0 -1
- package/dist/assets/popover-DeAliE_4.js +0 -1
- package/dist/assets/provider-scoped-model-input-B8LJ6RCt.js +0 -1
- package/dist/assets/providers-list-BTPjqNdK.js +0 -1
- package/dist/assets/refresh-cw-CrbD8EkT.js +0 -1
- package/dist/assets/remote-DiPWGRZc.js +0 -1
- package/dist/assets/runtime-config-page-Bb9he7t8.js +0 -1
- package/dist/assets/search-config-BkbSXa9d.js +0 -1
- package/dist/assets/settings-MWL2SMyk.js +0 -1
- package/dist/assets/sparkles-BmgOD4nY.js +0 -1
- package/dist/assets/status-dot-l3kPFdq_.js +0 -1
- package/dist/assets/use-config-BFqzVF8Y.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-DXhw9W4l.js +0 -1
- package/dist/assets/x-BeyYA_h6.js +0 -1
- /package/dist/assets/{config-hints-BNfpOL4J.js → config-hints-DGfwbumO.js} +0 -0
|
@@ -52,6 +52,7 @@ describe('ChatSessionListManager', () => {
|
|
|
52
52
|
it('applies the requested session type when creating a session', () => {
|
|
53
53
|
const uiManager = {
|
|
54
54
|
goToChatRoot: vi.fn(),
|
|
55
|
+
navigateTo: vi.fn(),
|
|
55
56
|
goToSession: vi.fn(),
|
|
56
57
|
isAtChatRoot: vi.fn(() => true),
|
|
57
58
|
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
@@ -63,7 +64,7 @@ describe('ChatSessionListManager', () => {
|
|
|
63
64
|
manager.createSession('codex');
|
|
64
65
|
|
|
65
66
|
expect(streamActionsManager.resetStreamState).toHaveBeenCalledTimes(1);
|
|
66
|
-
expect(uiManager.
|
|
67
|
+
expect(uiManager.navigateTo).toHaveBeenCalledWith('/chat/draft');
|
|
67
68
|
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
|
|
68
69
|
expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
|
|
69
70
|
expect(useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage).toBe(false);
|
|
@@ -75,6 +76,7 @@ describe('ChatSessionListManager', () => {
|
|
|
75
76
|
it('starts an agent draft chat through one owner state transition', () => {
|
|
76
77
|
const uiManager = {
|
|
77
78
|
goToChatRoot: vi.fn(),
|
|
79
|
+
navigateTo: vi.fn(),
|
|
78
80
|
goToSession: vi.fn(),
|
|
79
81
|
isAtChatRoot: vi.fn(() => true),
|
|
80
82
|
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
@@ -86,7 +88,7 @@ describe('ChatSessionListManager', () => {
|
|
|
86
88
|
manager.startAgentDraftChat('researcher', 'codex');
|
|
87
89
|
|
|
88
90
|
expect(streamActionsManager.resetStreamState).toHaveBeenCalledTimes(1);
|
|
89
|
-
expect(uiManager.
|
|
91
|
+
expect(uiManager.navigateTo).toHaveBeenCalledWith('/chat/draft');
|
|
90
92
|
expect(useChatSessionListStore.getState().snapshot.selectedAgentId).toBe('researcher');
|
|
91
93
|
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
|
|
92
94
|
expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
|
|
@@ -99,6 +101,7 @@ describe('ChatSessionListManager', () => {
|
|
|
99
101
|
it('hydrates the draft project root when creating a session inside a project group', () => {
|
|
100
102
|
const uiManager = {
|
|
101
103
|
goToChatRoot: vi.fn(),
|
|
104
|
+
navigateTo: vi.fn(),
|
|
102
105
|
goToSession: vi.fn(),
|
|
103
106
|
isAtChatRoot: vi.fn(() => true),
|
|
104
107
|
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
@@ -122,6 +125,7 @@ describe('ChatSessionListManager', () => {
|
|
|
122
125
|
});
|
|
123
126
|
const uiManager = {
|
|
124
127
|
goToChatRoot: vi.fn(),
|
|
128
|
+
navigateTo: vi.fn(),
|
|
125
129
|
goToSession: vi.fn(),
|
|
126
130
|
isAtChatRoot: vi.fn(() => true),
|
|
127
131
|
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
@@ -134,6 +138,7 @@ describe('ChatSessionListManager', () => {
|
|
|
134
138
|
|
|
135
139
|
expect(sessionKey).toBeNull();
|
|
136
140
|
expect(uiManager.goToChatRoot).not.toHaveBeenCalled();
|
|
141
|
+
expect(uiManager.navigateTo).not.toHaveBeenCalled();
|
|
137
142
|
expect(uiManager.goToSession).not.toHaveBeenCalled();
|
|
138
143
|
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
|
|
139
144
|
expect(useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage).toBe(true);
|
|
@@ -142,6 +147,7 @@ describe('ChatSessionListManager', () => {
|
|
|
142
147
|
it('does not eagerly replace the old selected session before the route finishes switching', () => {
|
|
143
148
|
const uiManager = {
|
|
144
149
|
goToChatRoot: vi.fn(),
|
|
150
|
+
navigateTo: vi.fn(),
|
|
145
151
|
goToSession: vi.fn(),
|
|
146
152
|
isAtChatRoot: vi.fn(() => true),
|
|
147
153
|
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
@@ -159,6 +165,7 @@ describe('ChatSessionListManager', () => {
|
|
|
159
165
|
it('delegates existing-session selection to routing while preserving workspace panel state', () => {
|
|
160
166
|
const uiManager = {
|
|
161
167
|
goToChatRoot: vi.fn(),
|
|
168
|
+
navigateTo: vi.fn(),
|
|
162
169
|
goToSession: vi.fn(),
|
|
163
170
|
isAtChatRoot: vi.fn(() => true),
|
|
164
171
|
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
@@ -178,9 +185,7 @@ describe('ChatSessionListManager', () => {
|
|
|
178
185
|
});
|
|
179
186
|
|
|
180
187
|
it('updates the sidebar list mode without touching other session list state', () => {
|
|
181
|
-
const uiManager = {
|
|
182
|
-
isAtChatRoot: vi.fn(() => true),
|
|
183
|
-
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
188
|
+
const uiManager = {} as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
184
189
|
const streamActionsManager = {} as ConstructorParameters<typeof ChatSessionListManager>[1];
|
|
185
190
|
|
|
186
191
|
const manager = new ChatSessionListManager(uiManager, streamActionsManager);
|
|
@@ -192,9 +197,7 @@ describe('ChatSessionListManager', () => {
|
|
|
192
197
|
|
|
193
198
|
it('marks a session as read through the session list owner boundary', () => {
|
|
194
199
|
const manager = new ChatSessionListManager(
|
|
195
|
-
{
|
|
196
|
-
isAtChatRoot: vi.fn(() => true),
|
|
197
|
-
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0],
|
|
200
|
+
{} as ConstructorParameters<typeof ChatSessionListManager>[0],
|
|
198
201
|
{} as ConstructorParameters<typeof ChatSessionListManager>[1]
|
|
199
202
|
);
|
|
200
203
|
|
|
@@ -210,9 +213,7 @@ describe('ChatSessionListManager', () => {
|
|
|
210
213
|
|
|
211
214
|
it('skips persisting read state when the backend already has the same watermark', () => {
|
|
212
215
|
const manager = new ChatSessionListManager(
|
|
213
|
-
{
|
|
214
|
-
isAtChatRoot: vi.fn(() => true),
|
|
215
|
-
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0],
|
|
216
|
+
{} as ConstructorParameters<typeof ChatSessionListManager>[0],
|
|
216
217
|
{} as ConstructorParameters<typeof ChatSessionListManager>[1]
|
|
217
218
|
);
|
|
218
219
|
|
|
@@ -6,6 +6,7 @@ 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
8
|
import { updateNcpSession } from '@/shared/lib/api';
|
|
9
|
+
import { CHAT_DRAFT_SESSION_PATH } from '@/features/chat/utils/chat-session-route.utils';
|
|
9
10
|
export class ChatSessionListManager {
|
|
10
11
|
constructor(
|
|
11
12
|
private uiManager: ChatUiManager,
|
|
@@ -117,7 +118,7 @@ export class ChatSessionListManager {
|
|
|
117
118
|
pendingProjectRoot: normalizedProjectRoot,
|
|
118
119
|
pendingProjectRootSessionKey: null
|
|
119
120
|
});
|
|
120
|
-
this.uiManager.
|
|
121
|
+
this.uiManager.navigateTo(CHAT_DRAFT_SESSION_PATH);
|
|
121
122
|
};
|
|
122
123
|
|
|
123
124
|
startAgentDraftChat = (agentId: string, sessionType: string): void => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NavigateFunction, NavigateOptions } from 'react-router-dom';
|
|
2
|
-
import { buildSessionPath } from '@/features/chat/utils/chat-session-route.utils';
|
|
2
|
+
import { CHAT_DRAFT_SESSION_PATH, buildSessionPath } from '@/features/chat/utils/chat-session-route.utils';
|
|
3
3
|
|
|
4
4
|
type ChatUiState = {
|
|
5
5
|
pathname: string;
|
|
@@ -67,7 +67,7 @@ export class ChatUiManager {
|
|
|
67
67
|
this.navigateTo('/chat', options);
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
isAtChatRoot = () => this.state.pathname === '/chat';
|
|
70
|
+
isAtChatRoot = () => this.state.pathname === '/chat' || this.state.pathname === CHAT_DRAFT_SESSION_PATH;
|
|
71
71
|
|
|
72
72
|
goToSession = (sessionKey: string, options?: NavigateOptions) => {
|
|
73
73
|
this.navigateTo(buildSessionPath(sessionKey), options);
|
|
@@ -39,10 +39,10 @@ describe('NcpChatInputManager', () => {
|
|
|
39
39
|
ncpAgent: {
|
|
40
40
|
state: 'ready',
|
|
41
41
|
},
|
|
42
|
-
|
|
42
|
+
extensionLoading: {
|
|
43
43
|
state: 'ready',
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
loadedExtensionCount: 1,
|
|
45
|
+
totalExtensionCount: 1,
|
|
46
46
|
},
|
|
47
47
|
channels: {
|
|
48
48
|
state: 'ready',
|
|
@@ -76,8 +76,11 @@ describe('buildNcpSendMetadata', () => {
|
|
|
76
76
|
projectRoot: ' /tmp/project-alpha ',
|
|
77
77
|
}),
|
|
78
78
|
).toMatchObject({
|
|
79
|
+
agentId: 'engineer',
|
|
79
80
|
agent_id: 'engineer',
|
|
81
|
+
agentRuntimeId: 'codex',
|
|
80
82
|
session_type: 'codex',
|
|
83
|
+
projectRoot: '/tmp/project-alpha',
|
|
81
84
|
project_root: '/tmp/project-alpha',
|
|
82
85
|
});
|
|
83
86
|
});
|
|
@@ -39,6 +39,10 @@ import {
|
|
|
39
39
|
normalizeSessionProjectRootValue,
|
|
40
40
|
} from "@/shared/lib/session-project";
|
|
41
41
|
|
|
42
|
+
function createMetadataFields(value: string | undefined, fields: readonly string[]): Record<string, string> {
|
|
43
|
+
return value ? Object.fromEntries(fields.map((field) => [field, value])) : {};
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
export function buildNcpSendMetadata(payload: {
|
|
43
47
|
agentId?: string;
|
|
44
48
|
model?: string;
|
|
@@ -48,26 +52,22 @@ export function buildNcpSendMetadata(payload: {
|
|
|
48
52
|
requestedSkills?: string[];
|
|
49
53
|
composerNodes?: Parameters<typeof buildInlineSkillTokensFromComposer>[0];
|
|
50
54
|
}): Record<string, unknown> {
|
|
51
|
-
const metadata: Record<string, unknown> = {};
|
|
52
|
-
if (payload.model?.trim()) {
|
|
53
|
-
metadata.model = payload.model.trim();
|
|
54
|
-
metadata.preferred_model = payload.model.trim();
|
|
55
|
-
}
|
|
56
|
-
if (payload.thinkingLevel?.trim()) {
|
|
57
|
-
metadata.thinking = payload.thinkingLevel.trim();
|
|
58
|
-
metadata.preferred_thinking = payload.thinkingLevel.trim();
|
|
59
|
-
}
|
|
60
|
-
if (payload.sessionType?.trim()) {
|
|
61
|
-
metadata.session_type = payload.sessionType.trim();
|
|
62
|
-
metadata.runtime = payload.sessionType.trim();
|
|
63
|
-
}
|
|
64
|
-
if (payload.agentId?.trim()) {
|
|
65
|
-
metadata.agent_id = payload.agentId.trim();
|
|
66
|
-
}
|
|
67
55
|
const projectRoot = normalizeSessionProjectRootValue(payload.projectRoot);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
const metadata: Record<string, unknown> = {
|
|
57
|
+
...createMetadataFields(payload.model?.trim(), ["model", "preferred_model"]),
|
|
58
|
+
...createMetadataFields(payload.thinkingLevel?.trim(), [
|
|
59
|
+
"thinkingEffort",
|
|
60
|
+
"thinking",
|
|
61
|
+
"preferred_thinking",
|
|
62
|
+
]),
|
|
63
|
+
...createMetadataFields(payload.sessionType?.trim(), [
|
|
64
|
+
"agentRuntimeId",
|
|
65
|
+
"session_type",
|
|
66
|
+
"runtime",
|
|
67
|
+
]),
|
|
68
|
+
...createMetadataFields(payload.agentId?.trim(), ["agentId", "agent_id"]),
|
|
69
|
+
...createMetadataFields(projectRoot ?? undefined, ["projectRoot", "project_root"]),
|
|
70
|
+
};
|
|
71
71
|
const requestedSkills = normalizeRequestedSkills(payload.requestedSkills);
|
|
72
72
|
if (requestedSkills.length > 0) {
|
|
73
73
|
metadata.requested_skill_refs = requestedSkills;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
CHAT_DRAFT_SESSION_PATH,
|
|
4
|
+
buildSessionPath,
|
|
5
|
+
parseSessionKeyFromRoute,
|
|
6
|
+
} from '@/features/chat/utils/chat-session-route.utils';
|
|
7
|
+
|
|
8
|
+
describe('chat session route utils', () => {
|
|
9
|
+
it('keeps the draft route separate from real session keys', () => {
|
|
10
|
+
expect(CHAT_DRAFT_SESSION_PATH).toBe('/chat/draft');
|
|
11
|
+
expect(parseSessionKeyFromRoute('draft')).toBeNull();
|
|
12
|
+
expect(parseSessionKeyFromRoute(buildSessionPath('draft').slice('/chat/'.length))).toBe('draft');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const SESSION_ROUTE_PREFIX = 'sid_';
|
|
2
|
+
export const CHAT_DRAFT_SESSION_PATH = '/chat/draft';
|
|
3
|
+
const DRAFT_SESSION_ROUTE_ID = CHAT_DRAFT_SESSION_PATH.slice('/chat/'.length);
|
|
2
4
|
|
|
3
5
|
export function encodeSessionRouteId(sessionKey: string): string {
|
|
4
6
|
const bytes = new TextEncoder().encode(sessionKey);
|
|
@@ -26,7 +28,7 @@ export function decodeSessionRouteId(routeValue: string): string | null {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export function parseSessionKeyFromRoute(routeValue?: string): string | null {
|
|
29
|
-
if (!routeValue) {
|
|
31
|
+
if (!routeValue || routeValue === DRAFT_SESSION_ROUTE_ID) {
|
|
30
32
|
return null;
|
|
31
33
|
}
|
|
32
34
|
const decodedToken = decodeSessionRouteId(routeValue);
|
|
@@ -20,10 +20,10 @@ function createStatus(overrides: Partial<SystemStatusView> = {}): SystemStatusVi
|
|
|
20
20
|
ncpAgent: {
|
|
21
21
|
state: 'ready',
|
|
22
22
|
},
|
|
23
|
-
|
|
23
|
+
extensionLoading: {
|
|
24
24
|
state: 'ready',
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
loadedExtensionCount: 1,
|
|
26
|
+
totalExtensionCount: 1,
|
|
27
27
|
},
|
|
28
28
|
channels: {
|
|
29
29
|
state: 'ready',
|
|
@@ -97,10 +97,10 @@ describe('ncp-chat-runtime-availability.utils', () => {
|
|
|
97
97
|
state: 'error',
|
|
98
98
|
error: 'boom',
|
|
99
99
|
},
|
|
100
|
-
|
|
100
|
+
extensionLoading: {
|
|
101
101
|
state: 'pending',
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
loadedExtensionCount: 0,
|
|
103
|
+
totalExtensionCount: 0,
|
|
104
104
|
},
|
|
105
105
|
channels: {
|
|
106
106
|
state: 'pending',
|
|
@@ -19,8 +19,6 @@ import { cn } from "@/shared/lib/utils";
|
|
|
19
19
|
import {
|
|
20
20
|
CheckCircle2,
|
|
21
21
|
Download,
|
|
22
|
-
Power,
|
|
23
|
-
PowerOff,
|
|
24
22
|
Trash2,
|
|
25
23
|
} from "lucide-react";
|
|
26
24
|
|
|
@@ -35,7 +33,6 @@ export type ManageState = {
|
|
|
35
33
|
type MarketplaceListCardActionProps = {
|
|
36
34
|
item?: MarketplaceItemSummary;
|
|
37
35
|
record?: MarketplaceInstalledRecord;
|
|
38
|
-
pluginRecord?: MarketplaceInstalledRecord;
|
|
39
36
|
isInstalling: boolean;
|
|
40
37
|
isDisabled: boolean;
|
|
41
38
|
canUninstall: boolean;
|
|
@@ -97,7 +94,6 @@ function MarketplaceListCardActions(props: MarketplaceListCardActionProps) {
|
|
|
97
94
|
const {
|
|
98
95
|
item,
|
|
99
96
|
record,
|
|
100
|
-
pluginRecord,
|
|
101
97
|
isInstalling,
|
|
102
98
|
isDisabled,
|
|
103
99
|
canUninstall,
|
|
@@ -107,7 +103,7 @@ function MarketplaceListCardActions(props: MarketplaceListCardActionProps) {
|
|
|
107
103
|
onInstall,
|
|
108
104
|
onManage,
|
|
109
105
|
} = props;
|
|
110
|
-
const hasActions = Boolean((item && !record) ||
|
|
106
|
+
const hasActions = Boolean((item && !record) || (record && canUninstall));
|
|
111
107
|
|
|
112
108
|
return (
|
|
113
109
|
<div
|
|
@@ -152,30 +148,6 @@ function MarketplaceListCardActions(props: MarketplaceListCardActionProps) {
|
|
|
152
148
|
</button>
|
|
153
149
|
)}
|
|
154
150
|
|
|
155
|
-
{pluginRecord && (
|
|
156
|
-
<button
|
|
157
|
-
disabled={busyForRecord}
|
|
158
|
-
onClick={(event) => {
|
|
159
|
-
event.stopPropagation();
|
|
160
|
-
onManage(isDisabled ? "enable" : "disable", pluginRecord);
|
|
161
|
-
}}
|
|
162
|
-
className="inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-xl border border-gray-200/80 bg-white px-3 text-xs font-medium text-gray-600 transition-colors hover:border-gray-300 hover:bg-gray-50 disabled:opacity-50"
|
|
163
|
-
>
|
|
164
|
-
{isDisabled ? (
|
|
165
|
-
<Power className="h-3.5 w-3.5" />
|
|
166
|
-
) : (
|
|
167
|
-
<PowerOff className="h-3.5 w-3.5" />
|
|
168
|
-
)}
|
|
169
|
-
{busyAction && busyAction !== "uninstall"
|
|
170
|
-
? busyAction === "enable"
|
|
171
|
-
? t("marketplaceEnabling")
|
|
172
|
-
: t("marketplaceDisabling")
|
|
173
|
-
: isDisabled
|
|
174
|
-
? t("marketplaceEnable")
|
|
175
|
-
: t("marketplaceDisable")}
|
|
176
|
-
</button>
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
151
|
{record && canUninstall && (
|
|
180
152
|
<button
|
|
181
153
|
disabled={busyForRecord}
|
|
@@ -217,7 +189,7 @@ function MarketplaceInstalledStatusIcon(props: {
|
|
|
217
189
|
)}
|
|
218
190
|
>
|
|
219
191
|
{disabled ? (
|
|
220
|
-
<
|
|
192
|
+
<CheckCircle2 className="h-4 w-4 opacity-40" />
|
|
221
193
|
) : (
|
|
222
194
|
<CheckCircle2 className="h-4 w-4" />
|
|
223
195
|
)}
|
|
@@ -253,7 +225,6 @@ export function MarketplaceListCard(props: {
|
|
|
253
225
|
onManage,
|
|
254
226
|
} = props;
|
|
255
227
|
const localeFallbacks = buildLocaleFallbacks(language);
|
|
256
|
-
const pluginRecord = record?.type === "plugin" ? record : undefined;
|
|
257
228
|
const title =
|
|
258
229
|
item?.name ??
|
|
259
230
|
record?.label ??
|
|
@@ -269,9 +240,7 @@ export function MarketplaceListCard(props: {
|
|
|
269
240
|
? manageState.actionsByTarget.get(targetId)
|
|
270
241
|
: undefined;
|
|
271
242
|
const busyForRecord = Boolean(busyAction);
|
|
272
|
-
const canUninstall =
|
|
273
|
-
(record?.type === "plugin" && record.origin !== "bundled") ||
|
|
274
|
-
(record?.type === "skill" && record.source === "workspace");
|
|
243
|
+
const canUninstall = record?.type === "skill" && record.source === "workspace";
|
|
275
244
|
const isDisabled = record
|
|
276
245
|
? record.enabled === false || record.runtimeStatus === "disabled"
|
|
277
246
|
: false;
|
|
@@ -288,7 +257,7 @@ export function MarketplaceListCard(props: {
|
|
|
288
257
|
<div className="flex min-w-0 flex-1 gap-3">
|
|
289
258
|
<MarketplaceItemIcon
|
|
290
259
|
name={title}
|
|
291
|
-
fallback={spec || t("
|
|
260
|
+
fallback={spec || t("marketplaceTypeSkill")}
|
|
292
261
|
/>
|
|
293
262
|
<div className="flex min-w-0 flex-1 flex-col justify-center">
|
|
294
263
|
<MarketplaceListCardMeta title={title} spec={spec} summary={summary} />
|
|
@@ -298,7 +267,6 @@ export function MarketplaceListCard(props: {
|
|
|
298
267
|
<MarketplaceListCardActions
|
|
299
268
|
item={item}
|
|
300
269
|
record={record}
|
|
301
|
-
pluginRecord={pluginRecord}
|
|
302
270
|
isInstalling={isInstalling}
|
|
303
271
|
isDisabled={isDisabled}
|
|
304
272
|
canUninstall={canUninstall}
|
|
@@ -44,7 +44,6 @@ vi.mock("@/shared/components/doc-browser", () => ({
|
|
|
44
44
|
|
|
45
45
|
vi.mock("@/shared/lib/api", () => ({
|
|
46
46
|
fetchMarketplaceSkillContent: mocks.fetchMarketplaceSkillContent,
|
|
47
|
-
fetchMarketplacePluginContent: vi.fn(),
|
|
48
47
|
}));
|
|
49
48
|
|
|
50
49
|
vi.mock("@/app/components/i18n-provider", () => ({
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
|
-
import userEvent from "@testing-library/user-event";
|
|
3
2
|
import { MarketplacePage } from "@/features/marketplace";
|
|
4
3
|
import type {
|
|
5
|
-
MarketplaceInstalledRecord,
|
|
6
4
|
MarketplaceInstalledView,
|
|
7
5
|
MarketplaceItemSummary,
|
|
8
6
|
MarketplaceListView,
|
|
@@ -28,7 +26,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
28
26
|
navigate: vi.fn(),
|
|
29
27
|
docOpen: vi.fn(),
|
|
30
28
|
confirm: vi.fn(),
|
|
31
|
-
routeParams: {} as {
|
|
29
|
+
routeParams: {} as { scene?: string },
|
|
32
30
|
itemsQuery: null as unknown as ItemsQueryState,
|
|
33
31
|
installedQuery: null as unknown as InstalledQueryState,
|
|
34
32
|
installMutation: {
|
|
@@ -118,41 +116,6 @@ function createMarketplaceItem(
|
|
|
118
116
|
};
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
function createPluginMarketplaceItem(
|
|
122
|
-
overrides: Partial<MarketplaceItemSummary> = {},
|
|
123
|
-
): MarketplaceItemSummary {
|
|
124
|
-
return createMarketplaceItem({
|
|
125
|
-
id: "plugin-channel-slack",
|
|
126
|
-
slug: "channel-slack",
|
|
127
|
-
type: "plugin",
|
|
128
|
-
name: "Slack Channel",
|
|
129
|
-
summary: "Optional Slack channel for NextClaw",
|
|
130
|
-
summaryI18n: { en: "Optional Slack channel for NextClaw" },
|
|
131
|
-
install: {
|
|
132
|
-
kind: "npm",
|
|
133
|
-
spec: "@nextclaw/channel-plugin-slack",
|
|
134
|
-
command: "npm install @nextclaw/channel-plugin-slack",
|
|
135
|
-
},
|
|
136
|
-
...overrides,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function createInstalledRecord(
|
|
141
|
-
overrides: Partial<MarketplaceInstalledRecord> = {},
|
|
142
|
-
): MarketplaceInstalledRecord {
|
|
143
|
-
return {
|
|
144
|
-
type: "plugin",
|
|
145
|
-
id: "@nextclaw/channel-plugin-slack",
|
|
146
|
-
spec: "@nextclaw/channel-plugin-slack",
|
|
147
|
-
label: "Slack Channel",
|
|
148
|
-
enabled: true,
|
|
149
|
-
origin: "marketplace",
|
|
150
|
-
source: "marketplace",
|
|
151
|
-
installedAt: "2026-03-19T00:00:00.000Z",
|
|
152
|
-
...overrides,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
119
|
function createItemsQuery(overrides: Partial<Record<string, unknown>> = {}) {
|
|
157
120
|
return {
|
|
158
121
|
data: undefined as MarketplaceListView | undefined,
|
|
@@ -238,7 +201,7 @@ describe("MarketplacePage", () => {
|
|
|
238
201
|
expect(screen.getByText("Web Search")).toBeTruthy();
|
|
239
202
|
});
|
|
240
203
|
|
|
241
|
-
it("does not render
|
|
204
|
+
it("does not render a redundant type label in skill cards", () => {
|
|
242
205
|
mocks.itemsQuery = createItemsQuery({
|
|
243
206
|
data: {
|
|
244
207
|
total: 1,
|
|
@@ -247,31 +210,16 @@ describe("MarketplacePage", () => {
|
|
|
247
210
|
totalPages: 1,
|
|
248
211
|
sort: "relevance",
|
|
249
212
|
items: [
|
|
250
|
-
createMarketplaceItem(
|
|
251
|
-
id: "plugin-channel-slack",
|
|
252
|
-
slug: "channel-slack",
|
|
253
|
-
type: "plugin",
|
|
254
|
-
name: "Slack Channel",
|
|
255
|
-
summary: "Optional Slack channel for NextClaw",
|
|
256
|
-
summaryI18n: { en: "Optional Slack channel for NextClaw" },
|
|
257
|
-
install: {
|
|
258
|
-
kind: "npm",
|
|
259
|
-
spec: "@nextclaw/channel-plugin-slack",
|
|
260
|
-
command:
|
|
261
|
-
"npm install @nextclaw/channel-plugin-slack",
|
|
262
|
-
},
|
|
263
|
-
}),
|
|
213
|
+
createMarketplaceItem(),
|
|
264
214
|
],
|
|
265
215
|
} satisfies MarketplaceListView,
|
|
266
216
|
});
|
|
267
217
|
|
|
268
|
-
const { container } = render(<MarketplacePage forcedType="
|
|
218
|
+
const { container } = render(<MarketplacePage forcedType="skills" />);
|
|
269
219
|
const card = container.querySelector("article");
|
|
270
220
|
|
|
271
|
-
expect(card?.textContent).toContain(
|
|
272
|
-
|
|
273
|
-
);
|
|
274
|
-
expect(card?.textContent).not.toContain("Plugin");
|
|
221
|
+
expect(card?.textContent).toContain("@nextclaw/web-search");
|
|
222
|
+
expect(card?.textContent).not.toContain("Skill");
|
|
275
223
|
});
|
|
276
224
|
|
|
277
225
|
it("does not dim the loaded list during background refresh", () => {
|
|
@@ -282,81 +230,14 @@ describe("MarketplacePage", () => {
|
|
|
282
230
|
pageSize: 20,
|
|
283
231
|
totalPages: 1,
|
|
284
232
|
sort: "relevance",
|
|
285
|
-
items: [
|
|
233
|
+
items: [createMarketplaceItem()],
|
|
286
234
|
} satisfies MarketplaceListView,
|
|
287
235
|
isFetching: true,
|
|
288
236
|
});
|
|
289
237
|
|
|
290
|
-
const { container } = render(<MarketplacePage forcedType="
|
|
238
|
+
const { container } = render(<MarketplacePage forcedType="skills" />);
|
|
291
239
|
|
|
292
|
-
expect(screen.getByText("
|
|
240
|
+
expect(screen.getByText("Web Search")).toBeTruthy();
|
|
293
241
|
expect(container.querySelector(".opacity-70")).toBeNull();
|
|
294
242
|
});
|
|
295
|
-
|
|
296
|
-
it("only disables the targeted plugin action while a manage request is pending", async () => {
|
|
297
|
-
const user = userEvent.setup();
|
|
298
|
-
let resolveMutation: (() => void) | undefined;
|
|
299
|
-
mocks.itemsQuery = createItemsQuery({
|
|
300
|
-
data: {
|
|
301
|
-
total: 2,
|
|
302
|
-
page: 1,
|
|
303
|
-
pageSize: 20,
|
|
304
|
-
totalPages: 1,
|
|
305
|
-
sort: "relevance",
|
|
306
|
-
items: [
|
|
307
|
-
createPluginMarketplaceItem(),
|
|
308
|
-
createPluginMarketplaceItem({
|
|
309
|
-
id: "plugin-channel-discord",
|
|
310
|
-
slug: "channel-discord",
|
|
311
|
-
name: "Discord Channel",
|
|
312
|
-
install: {
|
|
313
|
-
kind: "npm",
|
|
314
|
-
spec: "@nextclaw/channel-plugin-discord",
|
|
315
|
-
command:
|
|
316
|
-
"npm install @nextclaw/channel-plugin-discord",
|
|
317
|
-
},
|
|
318
|
-
}),
|
|
319
|
-
],
|
|
320
|
-
} satisfies MarketplaceListView,
|
|
321
|
-
});
|
|
322
|
-
mocks.installedQuery = createInstalledQuery({
|
|
323
|
-
data: {
|
|
324
|
-
type: "plugin",
|
|
325
|
-
total: 2,
|
|
326
|
-
specs: [
|
|
327
|
-
"@nextclaw/channel-plugin-slack",
|
|
328
|
-
"@nextclaw/channel-plugin-discord",
|
|
329
|
-
],
|
|
330
|
-
records: [
|
|
331
|
-
createInstalledRecord(),
|
|
332
|
-
createInstalledRecord({
|
|
333
|
-
id: "@nextclaw/channel-plugin-discord",
|
|
334
|
-
spec: "@nextclaw/channel-plugin-discord",
|
|
335
|
-
label: "Discord Channel",
|
|
336
|
-
}),
|
|
337
|
-
],
|
|
338
|
-
} satisfies MarketplaceInstalledView,
|
|
339
|
-
});
|
|
340
|
-
mocks.manageMutation.mutateAsync.mockImplementation(
|
|
341
|
-
() =>
|
|
342
|
-
new Promise<void>((resolve) => {
|
|
343
|
-
resolveMutation = resolve;
|
|
344
|
-
}),
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
render(<MarketplacePage forcedType="plugins" />);
|
|
348
|
-
|
|
349
|
-
const disableButtons = screen.getAllByRole("button", { name: "Disable" });
|
|
350
|
-
const firstDisableButton = disableButtons[0];
|
|
351
|
-
const secondDisableButton = disableButtons[1];
|
|
352
|
-
|
|
353
|
-
await user.click(firstDisableButton);
|
|
354
|
-
|
|
355
|
-
expect(mocks.manageMutation.mutateAsync).toHaveBeenCalledTimes(1);
|
|
356
|
-
expect(firstDisableButton.hasAttribute("disabled")).toBe(true);
|
|
357
|
-
expect(firstDisableButton.textContent).toContain("Disabling");
|
|
358
|
-
expect(secondDisableButton.hasAttribute("disabled")).toBe(false);
|
|
359
|
-
|
|
360
|
-
resolveMutation?.();
|
|
361
|
-
});
|
|
362
243
|
});
|