@nextclaw/ui 0.11.21 → 0.11.23
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 +24 -0
- package/dist/assets/{ChannelsList-ByHWHkQS.js → ChannelsList-DVDu1xvz.js} +6 -6
- package/dist/assets/ChatPage-Z9tRzm_n.js +43 -0
- package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
- package/dist/assets/{DocBrowser-3y_NHZ71.js → DocBrowser-BmtBLFU0.js} +1 -1
- package/dist/assets/{DocBrowserContext-CVJuwCcw.js → DocBrowserContext-YIKkPb76.js} +1 -1
- package/dist/assets/{LogoBadge-D8fyilO-.js → LogoBadge-F7ZWdxLT.js} +1 -1
- package/dist/assets/{MarketplacePage-CmhsZXr1.js → MarketplacePage-Buo9HrOz.js} +2 -2
- package/dist/assets/MarketplacePage-D6rVQEQR.js +1 -0
- package/dist/assets/{McpMarketplacePage-C7PkCYbp.js → McpMarketplacePage-JnkYwK7p.js} +2 -2
- package/dist/assets/ModelConfig-BYRhgp0c.js +1 -0
- package/dist/assets/ProvidersList-DmLyyHvX.js +1 -0
- package/dist/assets/RemoteAccessPage-CDSSvH7Z.js +1 -0
- package/dist/assets/RuntimeConfig-v7a7Fe3x.js +1 -0
- package/dist/assets/{SearchConfig-Dm7r2yfp.js → SearchConfig-D5f1EkLE.js} +1 -1
- package/dist/assets/{SecretsConfig-BBP_mbQh.js → SecretsConfig-D61IKcYt.js} +2 -2
- package/dist/assets/{SessionsConfig-6wNJloZN.js → SessionsConfig-BRIxVTEv.js} +2 -2
- package/dist/assets/{book-open-B26jGBjY.js → book-open-CXoF5nQC.js} +1 -1
- package/dist/assets/chat-session-display-D0WpnuRZ.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-B-4B29RN.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
- package/dist/assets/{config-BaC29Qf-.js → config-DJswxxE8.js} +1 -1
- package/dist/assets/{createLucideIcon-DiFAvXmK.js → createLucideIcon-CjGHOWb6.js} +1 -1
- package/dist/assets/{dist-pCfWPG1A.js → dist-Cl2QB-2y.js} +1 -1
- package/dist/assets/{dist-kW_O3kyZ.js → dist-nqTTbVdA.js} +1 -1
- package/dist/assets/{external-link-D5-p-Gmm.js → external-link-tIO7zING.js} +1 -1
- package/dist/assets/{hash-BlwrSV0q.js → hash-JWUyl1pT.js} +1 -1
- package/dist/assets/i18n-CDHMXlRZ.js +1 -0
- package/dist/assets/{index-DvKS3L9j.js → index-BuwbBgmT.js} +3 -3
- package/dist/assets/index-bZ8cqQIS.css +1 -0
- package/dist/assets/{label-RyXfZqkP.js → label-BIpeNu4r.js} +1 -1
- package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
- package/dist/assets/{logos-Bpl8QTgI.js → logos-DThdM9lk.js} +1 -1
- package/dist/assets/{page-layout--S0YBU0W.js → page-layout-D3Xo605Z.js} +1 -1
- package/dist/assets/plus-PHf8q-Ct.js +1 -0
- package/dist/assets/{popover-BEjfbEwy.js → popover-BJRUGA_H.js} +1 -1
- package/dist/assets/provider-models-bz5y28rq.js +1 -0
- package/dist/assets/{react-BuSP2-8B.js → react-7ZHqQtEV.js} +1 -1
- package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
- package/dist/assets/{save-DPPPpD_c.js → save-DJM5RRWW.js} +1 -1
- package/dist/assets/search-C91yH_6y.js +1 -0
- package/dist/assets/{security-config-6t78Ph-I.js → security-config-DbUyWcQz.js} +1 -1
- package/dist/assets/{select-CT50pzod.js → select-DSkTc61S.js} +1 -1
- package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
- package/dist/assets/{status-dot-BbBqRHfh.js → status-dot-LNBlDu3q.js} +1 -1
- package/dist/assets/{switch-D3l6AcCk.js → switch-Bo-Y46HZ.js} +1 -1
- package/dist/assets/tabs-custom-DXv507_2.js +1 -0
- package/dist/assets/{trash-2-B2_AGVE3.js → trash-2-DFZmW6Gg.js} +1 -1
- package/dist/assets/useConfirmDialog-COwYXDKm.js +1 -0
- package/dist/assets/{useMutation-BzCrO8j-.js → useMutation-DrZrOgVL.js} +1 -1
- package/dist/assets/x-D7Q1yqSF.js +1 -0
- package/dist/index.html +18 -18
- package/package.json +6 -6
- package/src/api/ncp-session.test.ts +37 -0
- package/src/api/ncp-session.ts +29 -1
- package/src/api/server-path.ts +23 -0
- package/src/api/types.ts +45 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +53 -9
- package/src/components/chat/ChatConversationPanel.tsx +122 -79
- package/src/components/chat/ChatSidebar.test.tsx +2 -2
- package/src/components/chat/ChatSidebar.tsx +2 -2
- package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
- package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
- package/src/components/chat/adapters/chat-message-part.adapter.ts +26 -14
- package/src/components/chat/adapters/chat-message.adapter.test.ts +159 -13
- package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +191 -0
- package/src/components/chat/adapters/{chat-message.file-operation-card.ts → file-operation/card.ts} +74 -181
- package/src/components/chat/adapters/{chat-message.file-operation-diff.ts → file-operation/diff.ts} +178 -188
- package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
- package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
- package/src/components/chat/chat-child-session-panel.tsx +100 -0
- package/src/components/chat/chat-composer-state.ts +3 -3
- package/src/components/chat/chat-page-runtime.test.ts +1 -0
- package/src/components/chat/chat-session-display.test.ts +22 -0
- package/src/components/chat/chat-session-display.ts +6 -1
- package/src/components/chat/containers/chat-input-bar.container.tsx +21 -24
- package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
- package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
- package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
- package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
- package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
- package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
- package/src/components/chat/ncp/NcpChatPage.tsx +219 -116
- package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
- package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +49 -0
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +24 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +47 -0
- package/src/components/chat/ncp/use-ncp-session-list-view.ts +10 -1
- package/src/components/chat/presenter/chat-presenter-context.tsx +4 -1
- package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
- package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
- package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
- package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
- package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
- package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
- package/src/components/chat/stores/chat-input.store.ts +6 -3
- package/src/components/chat/stores/chat-thread.store.ts +17 -3
- package/src/components/chat/useHydratedNcpAgent.test.tsx +30 -23
- package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
- package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
- package/src/hooks/server-path/use-server-path-browse.ts +19 -0
- package/src/hooks/useConfig.ts +26 -1
- package/src/lib/i18n/i18n-language-owner.ts +94 -0
- package/src/lib/i18n/i18n.path-picker.ts +12 -0
- package/src/lib/i18n.chat.ts +23 -0
- package/src/lib/i18n.ts +21 -84
- package/src/lib/session-project/session-project.utils.ts +30 -0
- package/dist/assets/ChatPage-FdT3pDnw.js +0 -42
- package/dist/assets/DocBrowser-CMdPdbZj.js +0 -1
- package/dist/assets/MarketplacePage-9oKmxN2n.js +0 -1
- package/dist/assets/ModelConfig-DmCY6jWM.js +0 -1
- package/dist/assets/ProvidersList-ClT-34aX.js +0 -1
- package/dist/assets/RemoteAccessPage-B6hUZl1O.js +0 -1
- package/dist/assets/RuntimeConfig-C5aqliGk.js +0 -1
- package/dist/assets/chat-session-display-Bjmn4aIZ.js +0 -1
- package/dist/assets/i18n-CSytxMFI.js +0 -1
- package/dist/assets/index-CUy6doWo.css +0 -1
- package/dist/assets/loader-circle-B2J777gj.js +0 -1
- package/dist/assets/plus-CM9XJ0Tf.js +0 -1
- package/dist/assets/provider-models-C8JQUd1E.js +0 -1
- package/dist/assets/search-Ctaw34Kp.js +0 -1
- package/dist/assets/skeleton-Bycyb0zU.js +0 -1
- package/dist/assets/tabs-custom-TZQ5WPWP.js +0 -1
- package/dist/assets/useConfirmDialog-BDpdjfIO.js +0 -1
- package/dist/assets/x-CHOBE-63.js +0 -1
- package/src/components/chat/adapters/chat-message.subagent-tool-card.ts +0 -154
- /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
- /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
- /package/dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
|
@@ -1,34 +1,59 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type MutableRefObject,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { NcpHttpAgentClientEndpoint } from "@nextclaw/ncp-http-agent-client";
|
|
3
10
|
import {
|
|
4
11
|
buildNcpRequestEnvelope,
|
|
5
12
|
useHydratedNcpAgent,
|
|
6
|
-
type NcpConversationSeed
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
type NcpConversationSeed,
|
|
14
|
+
type NcpConversationSeedLoader,
|
|
15
|
+
} from "@nextclaw/ncp-react";
|
|
16
|
+
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|
17
|
+
import { API_BASE } from "@/api/api-base";
|
|
18
|
+
import { fetchNcpSessionMessages } from "@/api/ncp-session";
|
|
19
|
+
import {
|
|
20
|
+
ChatPageLayout,
|
|
21
|
+
type ChatPageProps,
|
|
22
|
+
useChatSessionSync,
|
|
23
|
+
} from "@/components/chat/chat-page-shell";
|
|
24
|
+
import { sessionDisplayName } from "@/components/chat/chat-session-display";
|
|
25
|
+
import {
|
|
26
|
+
buildInlineSkillTokensFromComposer,
|
|
27
|
+
CHAT_UI_INLINE_TOKENS_METADATA_KEY,
|
|
28
|
+
} from "@/components/chat/chat-inline-token.utils";
|
|
29
|
+
import { createNcpAppClientFetch } from "@/components/chat/ncp/ncp-app-client-fetch";
|
|
30
|
+
import {
|
|
31
|
+
parseSessionKeyFromRoute,
|
|
32
|
+
resolveAgentIdFromSessionKey,
|
|
33
|
+
} from "@/components/chat/chat-session-route";
|
|
34
|
+
import { useNcpChatPageData } from "@/components/chat/ncp/ncp-chat-page-data";
|
|
35
|
+
import { NcpChatPresenter } from "@/components/chat/ncp/ncp-chat.presenter";
|
|
36
|
+
import {
|
|
37
|
+
adaptNcpSessionSummary,
|
|
38
|
+
createNcpSessionId,
|
|
39
|
+
} from "@/components/chat/ncp/ncp-session-adapter";
|
|
40
|
+
import { ChatPresenterProvider } from "@/components/chat/presenter/chat-presenter-context";
|
|
41
|
+
import type { ResumeRunParams } from "@/components/chat/chat-stream/types";
|
|
42
|
+
import { useChatInputStore } from "@/components/chat/stores/chat-input.store";
|
|
43
|
+
import { useChatSessionListStore } from "@/components/chat/stores/chat-session-list.store";
|
|
44
|
+
import { resolveSessionTypeLabel } from "@/components/chat/useChatSessionTypeState";
|
|
45
|
+
import { useConfirmDialog } from "@/hooks/useConfirmDialog";
|
|
46
|
+
import { normalizeRequestedSkills } from "@/lib/chat-runtime-utils";
|
|
47
|
+
import {
|
|
48
|
+
getSessionProjectName,
|
|
49
|
+
normalizeSessionProjectRootValue,
|
|
50
|
+
} from "@/lib/session-project/session-project.utils";
|
|
27
51
|
|
|
28
|
-
function buildNcpSendMetadata(payload: {
|
|
52
|
+
export function buildNcpSendMetadata(payload: {
|
|
29
53
|
model?: string;
|
|
30
54
|
thinkingLevel?: string;
|
|
31
55
|
sessionType?: string;
|
|
56
|
+
projectRoot?: string | null;
|
|
32
57
|
requestedSkills?: string[];
|
|
33
58
|
composerNodes?: Parameters<typeof buildInlineSkillTokensFromComposer>[0];
|
|
34
59
|
}): Record<string, unknown> {
|
|
@@ -44,9 +69,13 @@ function buildNcpSendMetadata(payload: {
|
|
|
44
69
|
if (payload.sessionType?.trim()) {
|
|
45
70
|
metadata.session_type = payload.sessionType.trim();
|
|
46
71
|
}
|
|
72
|
+
const projectRoot = normalizeSessionProjectRootValue(payload.projectRoot);
|
|
73
|
+
if (projectRoot) {
|
|
74
|
+
metadata.project_root = projectRoot;
|
|
75
|
+
}
|
|
47
76
|
const requestedSkills = normalizeRequestedSkills(payload.requestedSkills);
|
|
48
77
|
if (requestedSkills.length > 0) {
|
|
49
|
-
metadata.
|
|
78
|
+
metadata.requested_skill_refs = requestedSkills;
|
|
50
79
|
}
|
|
51
80
|
const inlineSkillTokens = payload.composerNodes
|
|
52
81
|
? buildInlineSkillTokensFromComposer(payload.composerNodes)
|
|
@@ -61,30 +90,91 @@ function isMissingNcpSessionError(error: unknown): boolean {
|
|
|
61
90
|
if (!(error instanceof Error)) {
|
|
62
91
|
return false;
|
|
63
92
|
}
|
|
64
|
-
return error.message.includes(
|
|
93
|
+
return error.message.includes("ncp session not found:");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type NcpSeedSessionSummary = {
|
|
97
|
+
sessionId: string;
|
|
98
|
+
status?: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function useNcpConversationSeedLoader<T extends NcpSeedSessionSummary>(
|
|
102
|
+
sessionSummariesRef: MutableRefObject<readonly T[]>,
|
|
103
|
+
): NcpConversationSeedLoader {
|
|
104
|
+
return useCallback(
|
|
105
|
+
async (
|
|
106
|
+
sessionId: string,
|
|
107
|
+
signal: AbortSignal,
|
|
108
|
+
): Promise<NcpConversationSeed> => {
|
|
109
|
+
signal.throwIfAborted();
|
|
110
|
+
let history: Awaited<ReturnType<typeof fetchNcpSessionMessages>> | null =
|
|
111
|
+
null;
|
|
112
|
+
try {
|
|
113
|
+
history = await fetchNcpSessionMessages(sessionId, 300);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (!isMissingNcpSessionError(error)) {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
signal.throwIfAborted();
|
|
120
|
+
|
|
121
|
+
const sessionSummary =
|
|
122
|
+
sessionSummariesRef.current.find(
|
|
123
|
+
(item) => item.sessionId === sessionId,
|
|
124
|
+
) ?? null;
|
|
125
|
+
return {
|
|
126
|
+
messages: history?.messages ?? [],
|
|
127
|
+
status: sessionSummary?.status === "running" ? "running" : "idle",
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
[sessionSummariesRef],
|
|
131
|
+
);
|
|
65
132
|
}
|
|
66
133
|
|
|
67
134
|
export function NcpChatPage({ view }: ChatPageProps) {
|
|
68
135
|
const [presenter] = useState(() => new NcpChatPresenter());
|
|
69
|
-
const [draftSessionId, setDraftSessionId] = useState(() =>
|
|
136
|
+
const [draftSessionId, setDraftSessionId] = useState(() =>
|
|
137
|
+
createNcpSessionId(),
|
|
138
|
+
);
|
|
70
139
|
const query = useChatSessionListStore((state) => state.snapshot.query);
|
|
71
|
-
const selectedSessionKey = useChatSessionListStore(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
140
|
+
const selectedSessionKey = useChatSessionListStore(
|
|
141
|
+
(state) => state.snapshot.selectedSessionKey,
|
|
142
|
+
);
|
|
143
|
+
const selectedAgentId = useChatSessionListStore(
|
|
144
|
+
(state) => state.snapshot.selectedAgentId,
|
|
145
|
+
);
|
|
146
|
+
const pendingSessionType = useChatInputStore(
|
|
147
|
+
(state) => state.snapshot.pendingSessionType,
|
|
148
|
+
);
|
|
149
|
+
const pendingProjectRoot = useChatInputStore(
|
|
150
|
+
(state) => state.snapshot.pendingProjectRoot,
|
|
151
|
+
);
|
|
152
|
+
const pendingProjectRootSessionKey = useChatInputStore(
|
|
153
|
+
(state) => state.snapshot.pendingProjectRootSessionKey,
|
|
154
|
+
);
|
|
155
|
+
const currentSelectedModel = useChatInputStore(
|
|
156
|
+
(state) => state.snapshot.selectedModel,
|
|
157
|
+
);
|
|
75
158
|
const { confirm, ConfirmDialog } = useConfirmDialog();
|
|
76
159
|
const location = useLocation();
|
|
77
160
|
const navigate = useNavigate();
|
|
78
|
-
const { sessionId: routeSessionIdParam } = useParams<{
|
|
161
|
+
const { sessionId: routeSessionIdParam } = useParams<{
|
|
162
|
+
sessionId?: string;
|
|
163
|
+
}>();
|
|
79
164
|
const threadRef = useRef<HTMLDivElement | null>(null);
|
|
80
165
|
const selectedSessionKeyRef = useRef<string | null>(selectedSessionKey);
|
|
81
|
-
const sessionStreamAttachInFlightRef = useRef(false);
|
|
82
166
|
const routeSessionKey = useMemo(
|
|
83
167
|
() => parseSessionKeyFromRoute(routeSessionIdParam),
|
|
84
|
-
[routeSessionIdParam]
|
|
168
|
+
[routeSessionIdParam],
|
|
85
169
|
);
|
|
170
|
+
const sessionKey = selectedSessionKey ?? draftSessionId;
|
|
171
|
+
const hasSessionProjectRootOverride =
|
|
172
|
+
pendingProjectRootSessionKey === sessionKey;
|
|
173
|
+
const sessionProjectRootOverride = hasSessionProjectRootOverride
|
|
174
|
+
? pendingProjectRoot
|
|
175
|
+
: undefined;
|
|
86
176
|
const {
|
|
87
|
-
|
|
177
|
+
sessionSkillsQuery,
|
|
88
178
|
isProviderStateResolved,
|
|
89
179
|
modelOptions,
|
|
90
180
|
sessionSummaries,
|
|
@@ -95,18 +185,19 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
95
185
|
selectedSessionType,
|
|
96
186
|
canEditSessionType,
|
|
97
187
|
sessionTypeUnavailable,
|
|
98
|
-
sessionTypeUnavailableMessage
|
|
188
|
+
sessionTypeUnavailableMessage,
|
|
99
189
|
} = useNcpChatPageData({
|
|
100
190
|
query,
|
|
101
|
-
|
|
191
|
+
sessionKey,
|
|
192
|
+
projectRootOverride: sessionProjectRootOverride,
|
|
102
193
|
currentSelectedModel,
|
|
103
194
|
pendingSessionType,
|
|
104
195
|
setPendingSessionType: presenter.chatInputManager.setPendingSessionType,
|
|
105
196
|
setSelectedModel: presenter.chatInputManager.setSelectedModel,
|
|
106
|
-
setSelectedThinkingLevel:
|
|
197
|
+
setSelectedThinkingLevel:
|
|
198
|
+
presenter.chatInputManager.setSelectedThinkingLevel,
|
|
107
199
|
});
|
|
108
200
|
|
|
109
|
-
const activeSessionId = selectedSessionKey ?? draftSessionId;
|
|
110
201
|
const sessionSummariesRef = useRef(sessionSummaries);
|
|
111
202
|
useEffect(() => {
|
|
112
203
|
sessionSummariesRef.current = sessionSummaries;
|
|
@@ -115,35 +206,18 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
115
206
|
const [ncpClient] = useState(
|
|
116
207
|
() =>
|
|
117
208
|
new NcpHttpAgentClientEndpoint({
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
})
|
|
209
|
+
baseUrl: API_BASE,
|
|
210
|
+
basePath: "/api/ncp/agent",
|
|
211
|
+
fetchImpl: createNcpAppClientFetch(),
|
|
212
|
+
}),
|
|
122
213
|
);
|
|
123
214
|
|
|
124
|
-
const loadSeed =
|
|
125
|
-
signal.throwIfAborted();
|
|
126
|
-
let history: Awaited<ReturnType<typeof fetchNcpSessionMessages>> | null = null;
|
|
127
|
-
try {
|
|
128
|
-
history = await fetchNcpSessionMessages(sessionId, 300);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
if (!isMissingNcpSessionError(error)) {
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
signal.throwIfAborted();
|
|
135
|
-
|
|
136
|
-
const sessionSummary = sessionSummariesRef.current.find((item) => item.sessionId === sessionId) ?? null;
|
|
137
|
-
return {
|
|
138
|
-
messages: history?.messages ?? [],
|
|
139
|
-
status: sessionSummary?.status === 'running' ? 'running' : 'idle'
|
|
140
|
-
};
|
|
141
|
-
}, []);
|
|
215
|
+
const loadSeed = useNcpConversationSeedLoader(sessionSummariesRef);
|
|
142
216
|
|
|
143
217
|
const agent = useHydratedNcpAgent({
|
|
144
|
-
sessionId:
|
|
218
|
+
sessionId: sessionKey,
|
|
145
219
|
client: ncpClient,
|
|
146
|
-
loadSeed
|
|
220
|
+
loadSeed,
|
|
147
221
|
});
|
|
148
222
|
|
|
149
223
|
useEffect(() => {
|
|
@@ -158,60 +232,45 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
158
232
|
}
|
|
159
233
|
}, [presenter, selectedSessionKey]);
|
|
160
234
|
|
|
235
|
+
const effectiveSessionProjectRoot = hasSessionProjectRootOverride
|
|
236
|
+
? pendingProjectRoot
|
|
237
|
+
: (selectedSession?.projectRoot ?? null);
|
|
238
|
+
const effectiveSessionProjectName = hasSessionProjectRootOverride
|
|
239
|
+
? getSessionProjectName(effectiveSessionProjectRoot)
|
|
240
|
+
: (selectedSession?.projectName ??
|
|
241
|
+
getSessionProjectName(effectiveSessionProjectRoot));
|
|
242
|
+
const parentSessionId = selectedSession?.parentSessionId ?? null;
|
|
243
|
+
|
|
161
244
|
const isSending = agent.isSending || agent.isRunning;
|
|
162
245
|
const isAwaitingAssistantOutput = agent.isRunning;
|
|
163
246
|
const canStopCurrentRun = agent.isRunning;
|
|
164
|
-
const stopDisabledReason = agent.isRunning ? null :
|
|
165
|
-
const lastSendError =
|
|
166
|
-
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
const attachRealtimeSessionStream = () => {
|
|
169
|
-
if (sessionStreamAttachInFlightRef.current) {
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (agent.isHydrating || agent.isRunning || agent.isSending) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
sessionStreamAttachInFlightRef.current = true;
|
|
177
|
-
void ncpClient
|
|
178
|
-
.stream({ sessionId: activeSessionId })
|
|
179
|
-
.catch(() => undefined)
|
|
180
|
-
.finally(() => {
|
|
181
|
-
sessionStreamAttachInFlightRef.current = false;
|
|
182
|
-
});
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
return appClient.subscribe((event) => {
|
|
186
|
-
if (
|
|
187
|
-
event.type === 'session.run-status' &&
|
|
188
|
-
event.payload.sessionKey === activeSessionId &&
|
|
189
|
-
event.payload.status === 'running'
|
|
190
|
-
) {
|
|
191
|
-
attachRealtimeSessionStream();
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
}, [activeSessionId, agent.isHydrating, agent.isRunning, agent.isSending, ncpClient]);
|
|
247
|
+
const stopDisabledReason = agent.isRunning ? null : "__preparing__";
|
|
248
|
+
const lastSendError =
|
|
249
|
+
agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
|
|
195
250
|
|
|
196
251
|
useEffect(() => {
|
|
197
252
|
presenter.chatStreamActionsManager.bind({
|
|
198
253
|
sendMessage: async (payload) => {
|
|
199
|
-
if (payload.sessionKey !==
|
|
254
|
+
if (payload.sessionKey !== sessionKey) {
|
|
200
255
|
return;
|
|
201
256
|
}
|
|
202
257
|
const metadata = buildNcpSendMetadata({
|
|
203
258
|
model: payload.model,
|
|
204
259
|
thinkingLevel: payload.thinkingLevel,
|
|
205
260
|
sessionType: payload.sessionType,
|
|
261
|
+
projectRoot:
|
|
262
|
+
payload.sessionKey === pendingProjectRootSessionKey
|
|
263
|
+
? pendingProjectRoot
|
|
264
|
+
: (selectedSession?.projectRoot ?? null),
|
|
206
265
|
requestedSkills: payload.requestedSkills,
|
|
207
|
-
composerNodes: payload.composerNodes
|
|
266
|
+
composerNodes: payload.composerNodes,
|
|
208
267
|
});
|
|
209
268
|
const envelope = buildNcpRequestEnvelope({
|
|
210
269
|
sessionId: payload.sessionKey,
|
|
211
270
|
text: payload.message,
|
|
212
271
|
attachments: payload.attachments,
|
|
213
272
|
parts: payload.parts,
|
|
214
|
-
metadata
|
|
273
|
+
metadata,
|
|
215
274
|
});
|
|
216
275
|
if (!envelope) {
|
|
217
276
|
return;
|
|
@@ -223,11 +282,13 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
223
282
|
if (payload.composerNodes && payload.composerNodes.length > 0) {
|
|
224
283
|
presenter.chatInputManager.restoreComposerState?.(
|
|
225
284
|
payload.composerNodes,
|
|
226
|
-
payload.attachments ?? []
|
|
285
|
+
payload.attachments ?? [],
|
|
227
286
|
);
|
|
228
287
|
} else {
|
|
229
288
|
presenter.chatInputManager.setDraft((currentDraft) =>
|
|
230
|
-
currentDraft.trim().length === 0
|
|
289
|
+
currentDraft.trim().length === 0
|
|
290
|
+
? payload.message
|
|
291
|
+
: currentDraft,
|
|
231
292
|
);
|
|
232
293
|
}
|
|
233
294
|
}
|
|
@@ -238,7 +299,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
238
299
|
await agent.abort();
|
|
239
300
|
},
|
|
240
301
|
resumeRun: async (run: ResumeRunParams) => {
|
|
241
|
-
if (run.sessionKey !==
|
|
302
|
+
if (run.sessionKey !== sessionKey) {
|
|
242
303
|
return;
|
|
243
304
|
}
|
|
244
305
|
await agent.streamRun();
|
|
@@ -246,36 +307,70 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
246
307
|
resetStreamState: () => {
|
|
247
308
|
selectedSessionKeyRef.current = null;
|
|
248
309
|
},
|
|
249
|
-
applyHistoryMessages: () => {}
|
|
310
|
+
applyHistoryMessages: () => {},
|
|
250
311
|
});
|
|
251
|
-
}, [
|
|
312
|
+
}, [
|
|
313
|
+
agent,
|
|
314
|
+
pendingProjectRoot,
|
|
315
|
+
pendingProjectRootSessionKey,
|
|
316
|
+
presenter,
|
|
317
|
+
selectedSession?.projectRoot,
|
|
318
|
+
sessionKey,
|
|
319
|
+
]);
|
|
320
|
+
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
if (
|
|
323
|
+
!selectedSession ||
|
|
324
|
+
pendingProjectRootSessionKey !== selectedSession.key ||
|
|
325
|
+
(selectedSession.projectRoot ?? null) !== pendingProjectRoot
|
|
326
|
+
) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
useChatInputStore.getState().setSnapshot({
|
|
330
|
+
pendingProjectRoot: null,
|
|
331
|
+
pendingProjectRootSessionKey: null,
|
|
332
|
+
});
|
|
333
|
+
}, [pendingProjectRoot, pendingProjectRootSessionKey, selectedSession]);
|
|
252
334
|
|
|
253
335
|
useChatSessionSync({
|
|
254
336
|
view,
|
|
255
337
|
routeSessionKey,
|
|
256
338
|
selectedSessionKey,
|
|
257
339
|
selectedAgentId,
|
|
258
|
-
setSelectedSessionKey:
|
|
340
|
+
setSelectedSessionKey:
|
|
341
|
+
presenter.chatSessionListManager.setSelectedSessionKey,
|
|
259
342
|
setSelectedAgentId: presenter.chatSessionListManager.setSelectedAgentId,
|
|
260
343
|
selectedSessionKeyRef,
|
|
261
344
|
resetStreamState: presenter.chatStreamActionsManager.resetStreamState,
|
|
262
|
-
resolveAgentIdFromSessionKey
|
|
345
|
+
resolveAgentIdFromSessionKey,
|
|
263
346
|
});
|
|
264
347
|
|
|
265
348
|
useEffect(() => {
|
|
266
349
|
presenter.chatUiManager.syncState({
|
|
267
|
-
pathname: location.pathname
|
|
350
|
+
pathname: location.pathname,
|
|
268
351
|
});
|
|
269
352
|
presenter.chatUiManager.bindActions({
|
|
270
353
|
navigate,
|
|
271
|
-
confirm
|
|
354
|
+
confirm,
|
|
272
355
|
});
|
|
273
356
|
}, [confirm, location.pathname, navigate, presenter]);
|
|
274
357
|
|
|
275
|
-
const currentSessionDisplayName = selectedSession
|
|
358
|
+
const currentSessionDisplayName = selectedSession
|
|
359
|
+
? sessionDisplayName(selectedSession)
|
|
360
|
+
: undefined;
|
|
361
|
+
const parentSession = useMemo(() => {
|
|
362
|
+
if (!parentSessionId) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
const parentSummary =
|
|
366
|
+
sessionSummaries.find(
|
|
367
|
+
(summary) => summary.sessionId === parentSessionId,
|
|
368
|
+
) ?? null;
|
|
369
|
+
return parentSummary ? adaptNcpSessionSummary(parentSummary) : null;
|
|
370
|
+
}, [parentSessionId, sessionSummaries]);
|
|
276
371
|
const currentSessionTypeLabel =
|
|
277
|
-
sessionTypeOptions.find((option) => option.value === selectedSessionType)
|
|
278
|
-
|
|
372
|
+
sessionTypeOptions.find((option) => option.value === selectedSessionType)
|
|
373
|
+
?.label ?? resolveSessionTypeLabel(selectedSessionType);
|
|
279
374
|
|
|
280
375
|
useEffect(() => {
|
|
281
376
|
presenter.chatInputManager.syncSnapshot({
|
|
@@ -293,7 +388,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
293
388
|
canEditSessionType,
|
|
294
389
|
sessionTypeUnavailable,
|
|
295
390
|
skillRecords,
|
|
296
|
-
isSkillsLoading:
|
|
391
|
+
isSkillsLoading: sessionSkillsQuery.isLoading,
|
|
297
392
|
});
|
|
298
393
|
presenter.chatThreadManager.syncSnapshot({
|
|
299
394
|
isProviderStateResolved,
|
|
@@ -301,14 +396,20 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
301
396
|
sessionTypeUnavailable,
|
|
302
397
|
sessionTypeUnavailableMessage,
|
|
303
398
|
sessionTypeLabel: currentSessionTypeLabel,
|
|
304
|
-
|
|
399
|
+
sessionKey,
|
|
305
400
|
sessionDisplayName: currentSessionDisplayName,
|
|
401
|
+
sessionProjectRoot: effectiveSessionProjectRoot,
|
|
402
|
+
sessionProjectName: effectiveSessionProjectName,
|
|
306
403
|
canDeleteSession: Boolean(selectedSession),
|
|
307
404
|
threadRef,
|
|
308
405
|
isHistoryLoading: agent.isHydrating,
|
|
309
406
|
messages: agent.visibleMessages,
|
|
310
407
|
isSending,
|
|
311
|
-
isAwaitingAssistantOutput
|
|
408
|
+
isAwaitingAssistantOutput,
|
|
409
|
+
parentSessionKey: parentSession?.key ?? null,
|
|
410
|
+
parentSessionLabel: parentSession
|
|
411
|
+
? sessionDisplayName(parentSession)
|
|
412
|
+
: null,
|
|
312
413
|
});
|
|
313
414
|
}, [
|
|
314
415
|
agent.isHydrating,
|
|
@@ -317,16 +418,18 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
317
418
|
currentSessionDisplayName,
|
|
318
419
|
currentSessionTypeLabel,
|
|
319
420
|
defaultSessionType,
|
|
320
|
-
|
|
421
|
+
sessionSkillsQuery.isLoading,
|
|
321
422
|
isAwaitingAssistantOutput,
|
|
322
423
|
isProviderStateResolved,
|
|
323
424
|
isSending,
|
|
324
425
|
lastSendError,
|
|
325
|
-
modelOptions.length,
|
|
326
426
|
modelOptions,
|
|
427
|
+
parentSession,
|
|
327
428
|
presenter,
|
|
429
|
+
effectiveSessionProjectName,
|
|
430
|
+
effectiveSessionProjectRoot,
|
|
328
431
|
selectedSession,
|
|
329
|
-
|
|
432
|
+
sessionKey,
|
|
330
433
|
selectedSessionType,
|
|
331
434
|
sessionTypeOptions,
|
|
332
435
|
sessionTypeUnavailable,
|
|
@@ -334,7 +437,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
334
437
|
skillRecords,
|
|
335
438
|
stopDisabledReason,
|
|
336
439
|
threadRef,
|
|
337
|
-
agent.visibleMessages
|
|
440
|
+
agent.visibleMessages,
|
|
338
441
|
]);
|
|
339
442
|
|
|
340
443
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildNcpSendMetadata } from '@/components/chat/ncp/NcpChatPage';
|
|
2
3
|
import { filterModelOptionsBySessionType } from '@/components/chat/ncp/ncp-chat-page-data';
|
|
3
4
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
4
5
|
|
|
@@ -42,3 +43,35 @@ describe('filterModelOptionsBySessionType', () => {
|
|
|
42
43
|
).toEqual(modelOptions);
|
|
43
44
|
});
|
|
44
45
|
});
|
|
46
|
+
|
|
47
|
+
describe('buildNcpSendMetadata', () => {
|
|
48
|
+
it('includes the project root in the first-message metadata when present', () => {
|
|
49
|
+
expect(
|
|
50
|
+
buildNcpSendMetadata({
|
|
51
|
+
sessionType: 'codex',
|
|
52
|
+
projectRoot: ' /tmp/project-alpha ',
|
|
53
|
+
}),
|
|
54
|
+
).toMatchObject({
|
|
55
|
+
session_type: 'codex',
|
|
56
|
+
project_root: '/tmp/project-alpha',
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('omits project_root when the input is blank', () => {
|
|
61
|
+
expect(
|
|
62
|
+
buildNcpSendMetadata({
|
|
63
|
+
projectRoot: ' ',
|
|
64
|
+
}),
|
|
65
|
+
).not.toHaveProperty('project_root');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('sends requested skill refs instead of legacy requested skill names', () => {
|
|
69
|
+
expect(
|
|
70
|
+
buildNcpSendMetadata({
|
|
71
|
+
requestedSkills: ['project:/tmp/project-alpha/.agents/skills/review'],
|
|
72
|
+
}),
|
|
73
|
+
).toMatchObject({
|
|
74
|
+
requested_skill_refs: ['project:/tmp/project-alpha/.agents/skills/review'],
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
useConfig,
|
|
16
16
|
useConfigMeta,
|
|
17
|
+
useNcpSessionSkills,
|
|
17
18
|
useNcpSessions
|
|
18
19
|
} from '@/hooks/useConfig';
|
|
19
20
|
import { useNcpChatSessionTypes } from '@/hooks/use-ncp-chat-session-types';
|
|
20
|
-
import { useMarketplaceInstalled } from '@/hooks/useMarketplace';
|
|
21
21
|
import { buildProviderModelCatalog, composeProviderModel, resolveModelThinkingCapability } from '@/lib/provider-models';
|
|
22
22
|
|
|
23
23
|
type UseNcpChatPageDataParams = {
|
|
24
24
|
query: string;
|
|
25
|
-
|
|
25
|
+
sessionKey: string;
|
|
26
|
+
projectRootOverride?: string | null;
|
|
26
27
|
currentSelectedModel: string;
|
|
27
28
|
pendingSessionType: string;
|
|
28
29
|
setPendingSessionType: Dispatch<SetStateAction<string>>;
|
|
@@ -51,7 +52,12 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
51
52
|
const configMetaQuery = useConfigMeta();
|
|
52
53
|
const sessionsQuery = useNcpSessions({ limit: 200 });
|
|
53
54
|
const sessionTypesQuery = useNcpChatSessionTypes();
|
|
54
|
-
const
|
|
55
|
+
const sessionSkillsQuery = useNcpSessionSkills({
|
|
56
|
+
sessionId: params.sessionKey,
|
|
57
|
+
...(Object.prototype.hasOwnProperty.call(params, 'projectRootOverride')
|
|
58
|
+
? { projectRoot: params.projectRootOverride ?? null }
|
|
59
|
+
: {})
|
|
60
|
+
});
|
|
55
61
|
const isProviderStateResolved =
|
|
56
62
|
(configQuery.isFetched || configQuery.isSuccess) &&
|
|
57
63
|
(configMetaQuery.isFetched || configMetaQuery.isSuccess);
|
|
@@ -101,16 +107,16 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
101
107
|
[allSessions, params.query]
|
|
102
108
|
);
|
|
103
109
|
const selectedSession = useMemo(
|
|
104
|
-
() => allSessions.find((session) => session.key === params.
|
|
105
|
-
[allSessions, params.
|
|
110
|
+
() => allSessions.find((session) => session.key === params.sessionKey) ?? null,
|
|
111
|
+
[allSessions, params.sessionKey]
|
|
106
112
|
);
|
|
107
113
|
const skillRecords = useMemo(
|
|
108
|
-
() =>
|
|
109
|
-
[
|
|
114
|
+
() => sessionSkillsQuery.data?.records ?? [],
|
|
115
|
+
[sessionSkillsQuery.data?.records]
|
|
110
116
|
);
|
|
111
117
|
const sessionTypeState = useChatSessionTypeState({
|
|
112
118
|
selectedSession,
|
|
113
|
-
selectedSessionKey: params.
|
|
119
|
+
selectedSessionKey: params.sessionKey,
|
|
114
120
|
pendingSessionType: params.pendingSessionType,
|
|
115
121
|
setPendingSessionType: params.setPendingSessionType,
|
|
116
122
|
sessionTypesData: sessionTypesQuery.data
|
|
@@ -127,10 +133,10 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
127
133
|
() =>
|
|
128
134
|
resolveRecentSessionPreferredModel({
|
|
129
135
|
sessions: allSessions,
|
|
130
|
-
selectedSessionKey: params.
|
|
136
|
+
selectedSessionKey: params.sessionKey,
|
|
131
137
|
sessionType: sessionTypeState.selectedSessionType
|
|
132
138
|
}),
|
|
133
|
-
[allSessions, params.
|
|
139
|
+
[allSessions, params.sessionKey, sessionTypeState.selectedSessionType]
|
|
134
140
|
);
|
|
135
141
|
const currentModelOption = useMemo(
|
|
136
142
|
() => filteredModelOptions.find((option) => option.value === params.currentSelectedModel),
|
|
@@ -148,15 +154,15 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
148
154
|
() =>
|
|
149
155
|
resolveRecentSessionPreferredThinking({
|
|
150
156
|
sessions: allSessions,
|
|
151
|
-
selectedSessionKey: params.
|
|
157
|
+
selectedSessionKey: params.sessionKey,
|
|
152
158
|
sessionType: sessionTypeState.selectedSessionType
|
|
153
159
|
}),
|
|
154
|
-
[allSessions, params.
|
|
160
|
+
[allSessions, params.sessionKey, sessionTypeState.selectedSessionType]
|
|
155
161
|
);
|
|
156
162
|
|
|
157
163
|
useSyncSelectedModel({
|
|
158
164
|
modelOptions: filteredModelOptions,
|
|
159
|
-
selectedSessionKey: params.
|
|
165
|
+
selectedSessionKey: params.sessionKey,
|
|
160
166
|
selectedSessionExists: Boolean(selectedSession),
|
|
161
167
|
selectedSessionPreferredModel: selectedSession?.preferredModel,
|
|
162
168
|
fallbackPreferredModel: sessionTypeState.selectedSessionTypeOption?.recommendedModel ?? recentSessionPreferredModel,
|
|
@@ -165,7 +171,7 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
165
171
|
});
|
|
166
172
|
useSyncSelectedThinking({
|
|
167
173
|
supportedThinkingLevels,
|
|
168
|
-
selectedSessionKey: params.
|
|
174
|
+
selectedSessionKey: params.sessionKey,
|
|
169
175
|
selectedSessionExists: Boolean(selectedSession),
|
|
170
176
|
selectedSessionPreferredThinking: selectedSession?.preferredThinking ?? null,
|
|
171
177
|
fallbackPreferredThinking: recentSessionPreferredThinking ?? null,
|
|
@@ -178,7 +184,7 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
178
184
|
configMetaQuery,
|
|
179
185
|
sessionsQuery,
|
|
180
186
|
sessionTypesQuery,
|
|
181
|
-
|
|
187
|
+
sessionSkillsQuery,
|
|
182
188
|
isProviderStateResolved,
|
|
183
189
|
modelOptions: filteredModelOptions,
|
|
184
190
|
sessionSummaries,
|