@nextclaw/ui 0.11.22 → 0.12.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 +30 -0
- package/dist/assets/{ChannelsList-Zeys_w43.js → ChannelsList-NKNKsf1J.js} +1 -1
- package/dist/assets/ChatPage-p23OnnEI.js +43 -0
- package/dist/assets/DocBrowser-C8b2uPgL.js +1 -0
- package/dist/assets/{DocBrowser-BmtBLFU0.js → DocBrowser-DxdSujSc.js} +1 -1
- package/dist/assets/{DocBrowserContext-YIKkPb76.js → DocBrowserContext-CQ-8jMha.js} +1 -1
- package/dist/assets/{LogoBadge-F7ZWdxLT.js → LogoBadge-D-KQIN4U.js} +1 -1
- package/dist/assets/{MarketplacePage-Cd4faegU.js → MarketplacePage-CRNvxtvx.js} +2 -2
- package/dist/assets/MarketplacePage-GGkEXowp.js +1 -0
- package/dist/assets/{McpMarketplacePage-C09Ngs7O.js → McpMarketplacePage-Cu7GmCcc.js} +2 -2
- package/dist/assets/{ModelConfig-DJgdcgvQ.js → ModelConfig-CEpx9fro.js} +1 -1
- package/dist/assets/{ProvidersList-w0rVFIBf.js → ProvidersList-BWbUb7-2.js} +1 -1
- package/dist/assets/{RemoteAccessPage-BJ_ckkOV.js → RemoteAccessPage-NsawrZb0.js} +1 -1
- package/dist/assets/RuntimeConfig-BJHBsVTd.js +1 -0
- package/dist/assets/{SearchConfig-BT13qpR_.js → SearchConfig-BsaX_WYy.js} +1 -1
- package/dist/assets/{SecretsConfig-CvqEVn0B.js → SecretsConfig-CgDZOd3w.js} +1 -1
- package/dist/assets/{SessionsConfig-DHHcYznk.js → SessionsConfig-Dd-KM7F7.js} +2 -2
- package/dist/assets/{book-open-CXoF5nQC.js → book-open-FnK2xCQd.js} +1 -1
- package/dist/assets/chat-session-display-BD_AN71I.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-CvRWvTy5.js → chunk-JZWAC4HX-B5l0hr_u.js} +1 -1
- package/dist/assets/{config-DJswxxE8.js → config-JKmXfZ3q.js} +1 -1
- package/dist/assets/{createLucideIcon-CjGHOWb6.js → createLucideIcon-o1WWhwhd.js} +1 -1
- package/dist/assets/{dist-nqTTbVdA.js → dist-C_moWYv7.js} +1 -1
- package/dist/assets/{dist-Cl2QB-2y.js → dist-DazA6Wd_.js} +1 -1
- package/dist/assets/{external-link-tIO7zING.js → external-link-BKje3SiD.js} +1 -1
- package/dist/assets/{hash-JWUyl1pT.js → hash-DfW4DT8O.js} +1 -1
- package/dist/assets/i18n-BK1w-oBy.js +1 -0
- package/dist/assets/index-BZaB1TqM.js +6 -0
- package/dist/assets/index-DaR9igPC.css +1 -0
- package/dist/assets/{label-BIpeNu4r.js → label-BzDWmdOe.js} +1 -1
- package/dist/assets/loader-circle-DdZPxBUz.js +1 -0
- package/dist/assets/{logos-DThdM9lk.js → logos-CTLlde_T.js} +1 -1
- package/dist/assets/{page-layout-D3Xo605Z.js → page-layout-BagR3t59.js} +1 -1
- package/dist/assets/plus-DP2PSCPO.js +1 -0
- package/dist/assets/{popover-BJRUGA_H.js → popover-5DWhNfd4.js} +1 -1
- package/dist/assets/{provider-models-bz5y28rq.js → provider-models-DJ29qHuA.js} +1 -1
- package/dist/assets/{react-7ZHqQtEV.js → react-C3yu5yge.js} +1 -1
- package/dist/assets/{refresh-ccw-CC6-_QuL.js → refresh-ccw-BAJf-h7w.js} +1 -1
- package/dist/assets/{save-DJM5RRWW.js → save-aa6z4GJL.js} +1 -1
- package/dist/assets/search-pD6ZwQYF.js +1 -0
- package/dist/assets/{security-config-T5zpg16O.js → security-config-DRDxrApx.js} +1 -1
- package/dist/assets/{select-DSkTc61S.js → select-BHJPiJWt.js} +1 -1
- package/dist/assets/skeleton-D6kCk9Y6.js +1 -0
- package/dist/assets/{status-dot-LNBlDu3q.js → status-dot-DUwsTIdv.js} +1 -1
- package/dist/assets/{switch-Bo-Y46HZ.js → switch-B6nCfcOB.js} +1 -1
- package/dist/assets/{tabs-custom-DXv507_2.js → tabs-custom-B57SMElx.js} +1 -1
- package/dist/assets/{trash-2-DFZmW6Gg.js → trash-2-CrjYH5ok.js} +1 -1
- package/dist/assets/{useConfirmDialog-Bs5Ll17m.js → useConfirmDialog-DsxnXB1B.js} +1 -1
- package/dist/assets/{useMutation-DrZrOgVL.js → useMutation-oTTWXgLG.js} +1 -1
- package/dist/assets/x-CTIQHUuD.js +1 -0
- package/dist/index.html +18 -18
- package/package.json +5 -5
- package/src/App.tsx +2 -0
- package/src/api/agents.ts +26 -0
- package/src/api/types.ts +27 -2
- package/src/components/agents/AgentsPage.test.tsx +70 -0
- package/src/components/agents/AgentsPage.tsx +353 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +144 -8
- package/src/components/chat/ChatConversationPanel.tsx +136 -77
- package/src/components/chat/ChatSidebar.test.tsx +8 -0
- package/src/components/chat/ChatSidebar.tsx +11 -0
- package/src/components/chat/ChatWelcome.test.tsx +25 -0
- package/src/components/chat/ChatWelcome.tsx +47 -1
- package/src/components/chat/adapters/chat-message-part.adapter.ts +18 -5
- package/src/components/chat/adapters/chat-message-tool-agent-id.test.ts +102 -0
- package/src/components/chat/adapters/chat-message-tool-agent-id.ts +47 -0
- package/src/components/chat/adapters/chat-message.adapter.test.ts +89 -9
- package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +200 -0
- package/src/components/chat/chat-child-session-panel.tsx +166 -0
- package/src/components/chat/chat-page-runtime.test.ts +1 -0
- package/src/components/chat/chat-page-shell.tsx +8 -17
- package/src/components/chat/chat-session-display.test.ts +1 -0
- package/src/components/chat/chat-session-route.ts +0 -14
- package/src/components/chat/chat-sidebar-session-item.tsx +16 -1
- package/src/components/chat/containers/chat-input-bar.container.tsx +2 -1
- package/src/components/chat/containers/chat-message-list.container.tsx +11 -0
- package/src/components/chat/ncp/NcpChatPage.tsx +153 -190
- package/src/components/chat/ncp/README.md +3 -0
- package/src/components/chat/ncp/ncp-chat-page-data.test.ts +2 -0
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +106 -1
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +23 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +32 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +128 -0
- package/src/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view.ts +52 -0
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.test.tsx +101 -0
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.ts +72 -0
- package/src/components/chat/ncp/use-ncp-session-list-view.ts +10 -1
- package/src/components/chat/presenter/chat-presenter-context.tsx +5 -1
- package/src/components/chat/stores/chat-thread.store.ts +25 -1
- package/src/components/chat/useHydratedNcpAgent.test.tsx +30 -23
- package/src/components/common/AgentAvatar.tsx +63 -0
- package/src/components/common/agent-identity/agent-identity-avatar.tsx +27 -0
- package/src/components/common/agent-identity/index.ts +3 -0
- package/src/components/common/agent-identity/use-agent-identity.ts +50 -0
- package/src/components/config/RuntimeConfig.tsx +13 -79
- package/src/components/config/runtime-config-agent.utils.ts +95 -0
- package/src/components/layout/AppLayout.tsx +3 -1
- package/src/components/layout/Sidebar.tsx +6 -1
- package/src/components/layout/app-layout.test.tsx +30 -0
- package/src/components/ui/tabs.tsx +2 -0
- package/src/hooks/README.md +3 -0
- package/src/hooks/agents/useAgents.ts +44 -0
- package/src/lib/i18n.agents.ts +66 -0
- package/src/lib/i18n.chat.ts +5 -0
- package/src/lib/i18n.ts +4 -4
- package/src/lib/ui-document-title.ts +1 -0
- package/dist/assets/ChatPage-DWOU_8P6.js +0 -43
- package/dist/assets/DocBrowser-B9OaZjmg.js +0 -1
- package/dist/assets/MarketplacePage-BfaTTqN6.js +0 -1
- package/dist/assets/RuntimeConfig-Cmn2xPQO.js +0 -1
- package/dist/assets/chat-session-display-VW6ZMvZP.js +0 -1
- package/dist/assets/i18n-CDHMXlRZ.js +0 -1
- package/dist/assets/index-BlH4-cBw.css +0 -1
- package/dist/assets/index-C6d0xmtm.js +0 -6
- package/dist/assets/loader-circle-Cs8XVFTw.js +0 -1
- package/dist/assets/plus-PHf8q-Ct.js +0 -1
- package/dist/assets/search-C91yH_6y.js +0 -1
- package/dist/assets/skeleton-Dzg-HOiN.js +0 -1
- package/dist/assets/x-D7Q1yqSF.js +0 -1
- package/src/components/chat/adapters/chat-message.subagent-tool-card.ts +0 -154
- /package/src/lib/{i18n → i18n-runtime}/i18n-language-owner.ts +0 -0
- /package/src/lib/{i18n → i18n-runtime}/i18n.path-picker.ts +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import type { NcpMessage } from "@nextclaw/ncp";
|
|
3
3
|
import {
|
|
4
|
+
type ChatToolActionViewModel,
|
|
4
5
|
type ChatMessageViewModel,
|
|
5
6
|
ChatMessageList,
|
|
6
7
|
} from "@nextclaw/agent-chat-ui";
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
} from "@/components/chat/adapters/chat-message.adapter";
|
|
12
13
|
import { readInlineTokensFromMetadata } from "@/components/chat/chat-inline-token.utils";
|
|
13
14
|
import { adaptNcpMessageToUiMessage } from "@/components/chat/ncp/ncp-session-adapter";
|
|
15
|
+
import { AgentIdentityAvatar } from "@/components/common/agent-identity";
|
|
14
16
|
import { useI18n } from "@/components/providers/I18nProvider";
|
|
15
17
|
import { formatDateTime, t } from "@/lib/i18n";
|
|
16
18
|
|
|
@@ -18,6 +20,7 @@ type ChatMessageListContainerProps = {
|
|
|
18
20
|
messages: readonly NcpMessage[];
|
|
19
21
|
isSending: boolean;
|
|
20
22
|
className?: string;
|
|
23
|
+
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
const messageViewModelCache = new WeakMap<
|
|
@@ -69,6 +72,7 @@ export function ChatMessageListContainer({
|
|
|
69
72
|
messages: rawMessages,
|
|
70
73
|
isSending,
|
|
71
74
|
className,
|
|
75
|
+
onToolAction,
|
|
72
76
|
}: ChatMessageListContainerProps) {
|
|
73
77
|
const { language } = useI18n();
|
|
74
78
|
const texts = useMemo<ChatMessageAdapterTexts>(
|
|
@@ -125,6 +129,13 @@ export function ChatMessageListContainer({
|
|
|
125
129
|
hasAssistantDraft={hasAssistantDraft}
|
|
126
130
|
className={className}
|
|
127
131
|
texts={messageTexts}
|
|
132
|
+
onToolAction={onToolAction}
|
|
133
|
+
renderToolAgent={(agentId) => (
|
|
134
|
+
<AgentIdentityAvatar
|
|
135
|
+
agentId={agentId}
|
|
136
|
+
className="h-4 w-4 shrink-0"
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
128
139
|
/>
|
|
129
140
|
);
|
|
130
141
|
}
|
|
@@ -1,32 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
} from "react";
|
|
3
7
|
import {
|
|
4
8
|
buildNcpRequestEnvelope,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
9
|
+
} from "@nextclaw/ncp-react";
|
|
10
|
+
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|
11
|
+
import {
|
|
12
|
+
ChatPageLayout,
|
|
13
|
+
type ChatPageProps,
|
|
14
|
+
useChatSessionSync,
|
|
15
|
+
} from "@/components/chat/chat-page-shell";
|
|
16
|
+
import {
|
|
17
|
+
buildInlineSkillTokensFromComposer,
|
|
18
|
+
CHAT_UI_INLINE_TOKENS_METADATA_KEY,
|
|
19
|
+
} from "@/components/chat/chat-inline-token.utils";
|
|
20
|
+
import {
|
|
21
|
+
parseSessionKeyFromRoute,
|
|
22
|
+
} from "@/components/chat/chat-session-route";
|
|
23
|
+
import { useNcpChatPageData } from "@/components/chat/ncp/ncp-chat-page-data";
|
|
24
|
+
import { NcpChatPresenter } from "@/components/chat/ncp/ncp-chat.presenter";
|
|
25
|
+
import { createNcpSessionId } from "@/components/chat/ncp/ncp-session-adapter";
|
|
26
|
+
import { useNcpSessionConversation } from "@/components/chat/ncp/session-conversation/use-ncp-session-conversation";
|
|
27
|
+
import { useNcpChatDerivedState, useNcpChatSnapshotSync } from "@/components/chat/ncp/page/ncp-chat-derived-state";
|
|
28
|
+
import { ChatPresenterProvider } from "@/components/chat/presenter/chat-presenter-context";
|
|
29
|
+
import type { ResumeRunParams } from "@/components/chat/chat-stream/types";
|
|
30
|
+
import { useChatInputStore } from "@/components/chat/stores/chat-input.store";
|
|
31
|
+
import { useChatSessionListStore } from "@/components/chat/stores/chat-session-list.store";
|
|
32
|
+
import { useConfirmDialog } from "@/hooks/useConfirmDialog";
|
|
33
|
+
import { useAgents } from "@/hooks/agents/useAgents";
|
|
34
|
+
import { normalizeRequestedSkills } from "@/lib/chat-runtime-utils";
|
|
35
|
+
import {
|
|
36
|
+
getSessionProjectName,
|
|
37
|
+
normalizeSessionProjectRootValue,
|
|
38
|
+
} from "@/lib/session-project/session-project.utils";
|
|
28
39
|
|
|
29
40
|
export function buildNcpSendMetadata(payload: {
|
|
41
|
+
agentId?: string;
|
|
30
42
|
model?: string;
|
|
31
43
|
thinkingLevel?: string;
|
|
32
44
|
sessionType?: string;
|
|
@@ -46,6 +58,9 @@ export function buildNcpSendMetadata(payload: {
|
|
|
46
58
|
if (payload.sessionType?.trim()) {
|
|
47
59
|
metadata.session_type = payload.sessionType.trim();
|
|
48
60
|
}
|
|
61
|
+
if (payload.agentId?.trim()) {
|
|
62
|
+
metadata.agent_id = payload.agentId.trim();
|
|
63
|
+
}
|
|
49
64
|
const projectRoot = normalizeSessionProjectRootValue(payload.projectRoot);
|
|
50
65
|
if (projectRoot) {
|
|
51
66
|
metadata.project_root = projectRoot;
|
|
@@ -63,37 +78,49 @@ export function buildNcpSendMetadata(payload: {
|
|
|
63
78
|
return metadata;
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
function isMissingNcpSessionError(error: unknown): boolean {
|
|
67
|
-
if (!(error instanceof Error)) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
return error.message.includes('ncp session not found:');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
81
|
export function NcpChatPage({ view }: ChatPageProps) {
|
|
74
82
|
const [presenter] = useState(() => new NcpChatPresenter());
|
|
75
|
-
const [draftSessionId, setDraftSessionId] = useState(() =>
|
|
83
|
+
const [draftSessionId, setDraftSessionId] = useState(() =>
|
|
84
|
+
createNcpSessionId(),
|
|
85
|
+
);
|
|
76
86
|
const query = useChatSessionListStore((state) => state.snapshot.query);
|
|
77
|
-
const selectedSessionKey = useChatSessionListStore(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
const selectedSessionKey = useChatSessionListStore(
|
|
88
|
+
(state) => state.snapshot.selectedSessionKey,
|
|
89
|
+
);
|
|
90
|
+
const selectedAgentId = useChatSessionListStore(
|
|
91
|
+
(state) => state.snapshot.selectedAgentId,
|
|
92
|
+
);
|
|
93
|
+
const pendingSessionType = useChatInputStore(
|
|
94
|
+
(state) => state.snapshot.pendingSessionType,
|
|
95
|
+
);
|
|
96
|
+
const pendingProjectRoot = useChatInputStore(
|
|
97
|
+
(state) => state.snapshot.pendingProjectRoot,
|
|
98
|
+
);
|
|
99
|
+
const pendingProjectRootSessionKey = useChatInputStore(
|
|
100
|
+
(state) => state.snapshot.pendingProjectRootSessionKey,
|
|
101
|
+
);
|
|
102
|
+
const agentsQuery = useAgents();
|
|
103
|
+
const currentSelectedModel = useChatInputStore(
|
|
104
|
+
(state) => state.snapshot.selectedModel,
|
|
105
|
+
);
|
|
83
106
|
const { confirm, ConfirmDialog } = useConfirmDialog();
|
|
84
107
|
const location = useLocation();
|
|
85
108
|
const navigate = useNavigate();
|
|
86
|
-
const { sessionId: routeSessionIdParam } = useParams<{
|
|
109
|
+
const { sessionId: routeSessionIdParam } = useParams<{
|
|
110
|
+
sessionId?: string;
|
|
111
|
+
}>();
|
|
87
112
|
const threadRef = useRef<HTMLDivElement | null>(null);
|
|
88
113
|
const selectedSessionKeyRef = useRef<string | null>(selectedSessionKey);
|
|
89
|
-
const sessionStreamAttachInFlightRef = useRef(false);
|
|
90
114
|
const routeSessionKey = useMemo(
|
|
91
115
|
() => parseSessionKeyFromRoute(routeSessionIdParam),
|
|
92
|
-
[routeSessionIdParam]
|
|
116
|
+
[routeSessionIdParam],
|
|
93
117
|
);
|
|
94
118
|
const sessionKey = selectedSessionKey ?? draftSessionId;
|
|
95
|
-
const hasSessionProjectRootOverride =
|
|
96
|
-
|
|
119
|
+
const hasSessionProjectRootOverride =
|
|
120
|
+
pendingProjectRootSessionKey === sessionKey;
|
|
121
|
+
const sessionProjectRootOverride = hasSessionProjectRootOverride
|
|
122
|
+
? pendingProjectRoot
|
|
123
|
+
: undefined;
|
|
97
124
|
const {
|
|
98
125
|
sessionSkillsQuery,
|
|
99
126
|
isProviderStateResolved,
|
|
@@ -106,7 +133,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
106
133
|
selectedSessionType,
|
|
107
134
|
canEditSessionType,
|
|
108
135
|
sessionTypeUnavailable,
|
|
109
|
-
sessionTypeUnavailableMessage
|
|
136
|
+
sessionTypeUnavailableMessage,
|
|
110
137
|
} = useNcpChatPageData({
|
|
111
138
|
query,
|
|
112
139
|
sessionKey,
|
|
@@ -115,47 +142,11 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
115
142
|
pendingSessionType,
|
|
116
143
|
setPendingSessionType: presenter.chatInputManager.setPendingSessionType,
|
|
117
144
|
setSelectedModel: presenter.chatInputManager.setSelectedModel,
|
|
118
|
-
setSelectedThinkingLevel:
|
|
145
|
+
setSelectedThinkingLevel:
|
|
146
|
+
presenter.chatInputManager.setSelectedThinkingLevel,
|
|
119
147
|
});
|
|
120
148
|
|
|
121
|
-
const
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
sessionSummariesRef.current = sessionSummaries;
|
|
124
|
-
}, [sessionSummaries]);
|
|
125
|
-
|
|
126
|
-
const [ncpClient] = useState(
|
|
127
|
-
() =>
|
|
128
|
-
new NcpHttpAgentClientEndpoint({
|
|
129
|
-
baseUrl: API_BASE,
|
|
130
|
-
basePath: '/api/ncp/agent',
|
|
131
|
-
fetchImpl: createNcpAppClientFetch()
|
|
132
|
-
})
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const loadSeed = useCallback(async (sessionId: string, signal: AbortSignal): Promise<NcpConversationSeed> => {
|
|
136
|
-
signal.throwIfAborted();
|
|
137
|
-
let history: Awaited<ReturnType<typeof fetchNcpSessionMessages>> | null = null;
|
|
138
|
-
try {
|
|
139
|
-
history = await fetchNcpSessionMessages(sessionId, 300);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (!isMissingNcpSessionError(error)) {
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
signal.throwIfAborted();
|
|
146
|
-
|
|
147
|
-
const sessionSummary = sessionSummariesRef.current.find((item) => item.sessionId === sessionId) ?? null;
|
|
148
|
-
return {
|
|
149
|
-
messages: history?.messages ?? [],
|
|
150
|
-
status: sessionSummary?.status === 'running' ? 'running' : 'idle'
|
|
151
|
-
};
|
|
152
|
-
}, []);
|
|
153
|
-
|
|
154
|
-
const agent = useHydratedNcpAgent({
|
|
155
|
-
sessionId: sessionKey,
|
|
156
|
-
client: ncpClient,
|
|
157
|
-
loadSeed
|
|
158
|
-
});
|
|
149
|
+
const agent = useNcpSessionConversation(sessionKey);
|
|
159
150
|
|
|
160
151
|
useEffect(() => {
|
|
161
152
|
presenter.setDraftSessionId(draftSessionId);
|
|
@@ -169,43 +160,21 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
169
160
|
}
|
|
170
161
|
}, [presenter, selectedSessionKey]);
|
|
171
162
|
|
|
172
|
-
const effectiveSessionProjectRoot = hasSessionProjectRootOverride
|
|
173
|
-
|
|
163
|
+
const effectiveSessionProjectRoot = hasSessionProjectRootOverride
|
|
164
|
+
? pendingProjectRoot
|
|
165
|
+
: (selectedSession?.projectRoot ?? null);
|
|
166
|
+
const effectiveSessionProjectName = hasSessionProjectRootOverride
|
|
167
|
+
? getSessionProjectName(effectiveSessionProjectRoot)
|
|
168
|
+
: (selectedSession?.projectName ??
|
|
169
|
+
getSessionProjectName(effectiveSessionProjectRoot));
|
|
170
|
+
const parentSessionId = selectedSession?.parentSessionId ?? null;
|
|
174
171
|
|
|
175
172
|
const isSending = agent.isSending || agent.isRunning;
|
|
176
173
|
const isAwaitingAssistantOutput = agent.isRunning;
|
|
177
174
|
const canStopCurrentRun = agent.isRunning;
|
|
178
|
-
const stopDisabledReason = agent.isRunning ? null :
|
|
179
|
-
const lastSendError =
|
|
180
|
-
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
const attachRealtimeSessionStream = () => {
|
|
183
|
-
if (sessionStreamAttachInFlightRef.current) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (agent.isHydrating || agent.isRunning || agent.isSending) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
sessionStreamAttachInFlightRef.current = true;
|
|
191
|
-
void ncpClient
|
|
192
|
-
.stream({ sessionId: sessionKey })
|
|
193
|
-
.catch(() => undefined)
|
|
194
|
-
.finally(() => {
|
|
195
|
-
sessionStreamAttachInFlightRef.current = false;
|
|
196
|
-
});
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
return appClient.subscribe((event) => {
|
|
200
|
-
if (
|
|
201
|
-
event.type === 'session.run-status' &&
|
|
202
|
-
event.payload.sessionKey === sessionKey &&
|
|
203
|
-
event.payload.status === 'running'
|
|
204
|
-
) {
|
|
205
|
-
attachRealtimeSessionStream();
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
}, [agent.isHydrating, agent.isRunning, agent.isSending, ncpClient, sessionKey]);
|
|
175
|
+
const stopDisabledReason = agent.isRunning ? null : "__preparing__";
|
|
176
|
+
const lastSendError =
|
|
177
|
+
agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
|
|
209
178
|
|
|
210
179
|
useEffect(() => {
|
|
211
180
|
presenter.chatStreamActionsManager.bind({
|
|
@@ -214,22 +183,23 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
214
183
|
return;
|
|
215
184
|
}
|
|
216
185
|
const metadata = buildNcpSendMetadata({
|
|
186
|
+
agentId: payload.agentId,
|
|
217
187
|
model: payload.model,
|
|
218
188
|
thinkingLevel: payload.thinkingLevel,
|
|
219
189
|
sessionType: payload.sessionType,
|
|
220
190
|
projectRoot:
|
|
221
191
|
payload.sessionKey === pendingProjectRootSessionKey
|
|
222
192
|
? pendingProjectRoot
|
|
223
|
-
: selectedSession?.projectRoot ?? null,
|
|
193
|
+
: (selectedSession?.projectRoot ?? null),
|
|
224
194
|
requestedSkills: payload.requestedSkills,
|
|
225
|
-
composerNodes: payload.composerNodes
|
|
195
|
+
composerNodes: payload.composerNodes,
|
|
226
196
|
});
|
|
227
197
|
const envelope = buildNcpRequestEnvelope({
|
|
228
198
|
sessionId: payload.sessionKey,
|
|
229
199
|
text: payload.message,
|
|
230
200
|
attachments: payload.attachments,
|
|
231
201
|
parts: payload.parts,
|
|
232
|
-
metadata
|
|
202
|
+
metadata,
|
|
233
203
|
});
|
|
234
204
|
if (!envelope) {
|
|
235
205
|
return;
|
|
@@ -241,11 +211,13 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
241
211
|
if (payload.composerNodes && payload.composerNodes.length > 0) {
|
|
242
212
|
presenter.chatInputManager.restoreComposerState?.(
|
|
243
213
|
payload.composerNodes,
|
|
244
|
-
payload.attachments ?? []
|
|
214
|
+
payload.attachments ?? [],
|
|
245
215
|
);
|
|
246
216
|
} else {
|
|
247
217
|
presenter.chatInputManager.setDraft((currentDraft) =>
|
|
248
|
-
currentDraft.trim().length === 0
|
|
218
|
+
currentDraft.trim().length === 0
|
|
219
|
+
? payload.message
|
|
220
|
+
: currentDraft,
|
|
249
221
|
);
|
|
250
222
|
}
|
|
251
223
|
}
|
|
@@ -264,7 +236,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
264
236
|
resetStreamState: () => {
|
|
265
237
|
selectedSessionKeyRef.current = null;
|
|
266
238
|
},
|
|
267
|
-
applyHistoryMessages: () => {}
|
|
239
|
+
applyHistoryMessages: () => {},
|
|
268
240
|
});
|
|
269
241
|
}, [
|
|
270
242
|
agent,
|
|
@@ -272,16 +244,20 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
272
244
|
pendingProjectRootSessionKey,
|
|
273
245
|
presenter,
|
|
274
246
|
selectedSession?.projectRoot,
|
|
275
|
-
sessionKey
|
|
247
|
+
sessionKey,
|
|
276
248
|
]);
|
|
277
249
|
|
|
278
250
|
useEffect(() => {
|
|
279
|
-
if (
|
|
251
|
+
if (
|
|
252
|
+
!selectedSession ||
|
|
253
|
+
pendingProjectRootSessionKey !== selectedSession.key ||
|
|
254
|
+
(selectedSession.projectRoot ?? null) !== pendingProjectRoot
|
|
255
|
+
) {
|
|
280
256
|
return;
|
|
281
257
|
}
|
|
282
258
|
useChatInputStore.getState().setSnapshot({
|
|
283
259
|
pendingProjectRoot: null,
|
|
284
|
-
pendingProjectRootSessionKey: null
|
|
260
|
+
pendingProjectRootSessionKey: null,
|
|
285
261
|
});
|
|
286
262
|
}, [pendingProjectRoot, pendingProjectRootSessionKey, selectedSession]);
|
|
287
263
|
|
|
@@ -289,91 +265,78 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
289
265
|
view,
|
|
290
266
|
routeSessionKey,
|
|
291
267
|
selectedSessionKey,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
setSelectedAgentId: presenter.chatSessionListManager.setSelectedAgentId,
|
|
268
|
+
setSelectedSessionKey:
|
|
269
|
+
presenter.chatSessionListManager.setSelectedSessionKey,
|
|
295
270
|
selectedSessionKeyRef,
|
|
296
271
|
resetStreamState: presenter.chatStreamActionsManager.resetStreamState,
|
|
297
|
-
resolveAgentIdFromSessionKey
|
|
298
272
|
});
|
|
299
273
|
|
|
300
274
|
useEffect(() => {
|
|
301
275
|
presenter.chatUiManager.syncState({
|
|
302
|
-
pathname: location.pathname
|
|
276
|
+
pathname: location.pathname,
|
|
303
277
|
});
|
|
304
278
|
presenter.chatUiManager.bindActions({
|
|
305
279
|
navigate,
|
|
306
|
-
confirm
|
|
280
|
+
confirm,
|
|
307
281
|
});
|
|
308
282
|
}, [confirm, location.pathname, navigate, presenter]);
|
|
309
283
|
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
284
|
+
const availableAgents = (agentsQuery.data?.agents?.length ?? 0) > 0
|
|
285
|
+
? (agentsQuery.data?.agents ?? [])
|
|
286
|
+
: [{ id: selectedSession?.agentId ?? selectedAgentId }];
|
|
287
|
+
const {
|
|
288
|
+
currentSessionDisplayName,
|
|
289
|
+
currentAgentId,
|
|
290
|
+
currentAgent,
|
|
291
|
+
parentSession,
|
|
292
|
+
currentSessionTypeLabel
|
|
293
|
+
} = useNcpChatDerivedState({
|
|
294
|
+
selectedSession,
|
|
295
|
+
selectedAgentId,
|
|
296
|
+
availableAgents,
|
|
297
|
+
parentSessionId,
|
|
298
|
+
sessionSummaries,
|
|
299
|
+
selectedSessionType,
|
|
300
|
+
sessionTypeOptions
|
|
301
|
+
});
|
|
314
302
|
|
|
315
303
|
useEffect(() => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
isSending,
|
|
325
|
-
modelOptions,
|
|
326
|
-
sessionTypeOptions,
|
|
327
|
-
selectedSessionType,
|
|
328
|
-
canEditSessionType,
|
|
329
|
-
sessionTypeUnavailable,
|
|
330
|
-
skillRecords,
|
|
331
|
-
isSkillsLoading: sessionSkillsQuery.isLoading
|
|
332
|
-
});
|
|
333
|
-
presenter.chatThreadManager.syncSnapshot({
|
|
334
|
-
isProviderStateResolved,
|
|
335
|
-
modelOptions,
|
|
336
|
-
sessionTypeUnavailable,
|
|
337
|
-
sessionTypeUnavailableMessage,
|
|
338
|
-
sessionTypeLabel: currentSessionTypeLabel,
|
|
339
|
-
sessionKey,
|
|
340
|
-
sessionDisplayName: currentSessionDisplayName,
|
|
341
|
-
sessionProjectRoot: effectiveSessionProjectRoot,
|
|
342
|
-
sessionProjectName: effectiveSessionProjectName,
|
|
343
|
-
canDeleteSession: Boolean(selectedSession),
|
|
344
|
-
threadRef,
|
|
345
|
-
isHistoryLoading: agent.isHydrating,
|
|
346
|
-
messages: agent.visibleMessages,
|
|
347
|
-
isSending,
|
|
348
|
-
isAwaitingAssistantOutput
|
|
349
|
-
});
|
|
350
|
-
}, [
|
|
351
|
-
agent.isHydrating,
|
|
352
|
-
canEditSessionType,
|
|
353
|
-
canStopCurrentRun,
|
|
354
|
-
currentSessionDisplayName,
|
|
355
|
-
currentSessionTypeLabel,
|
|
356
|
-
defaultSessionType,
|
|
357
|
-
sessionSkillsQuery.isLoading,
|
|
358
|
-
isAwaitingAssistantOutput,
|
|
304
|
+
if (!selectedSession?.agentId || selectedAgentId === selectedSession.agentId) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
presenter.chatSessionListManager.setSelectedAgentId(selectedSession.agentId);
|
|
308
|
+
}, [presenter, selectedAgentId, selectedSession?.agentId]);
|
|
309
|
+
|
|
310
|
+
useNcpChatSnapshotSync({
|
|
311
|
+
presenter,
|
|
359
312
|
isProviderStateResolved,
|
|
360
|
-
|
|
313
|
+
defaultSessionType,
|
|
314
|
+
canStopCurrentRun,
|
|
315
|
+
stopDisabledReason,
|
|
361
316
|
lastSendError,
|
|
317
|
+
isSending,
|
|
362
318
|
modelOptions,
|
|
363
|
-
presenter,
|
|
364
|
-
effectiveSessionProjectName,
|
|
365
|
-
effectiveSessionProjectRoot,
|
|
366
|
-
selectedSession,
|
|
367
|
-
sessionKey,
|
|
368
|
-
selectedSessionType,
|
|
369
319
|
sessionTypeOptions,
|
|
320
|
+
selectedSessionType,
|
|
321
|
+
canEditSessionType,
|
|
370
322
|
sessionTypeUnavailable,
|
|
371
|
-
sessionTypeUnavailableMessage,
|
|
372
323
|
skillRecords,
|
|
373
|
-
|
|
324
|
+
isSkillsLoading: sessionSkillsQuery.isLoading,
|
|
325
|
+
sessionTypeUnavailableMessage,
|
|
326
|
+
currentSessionTypeLabel,
|
|
327
|
+
sessionKey,
|
|
328
|
+
currentAgentId,
|
|
329
|
+
currentAgent,
|
|
330
|
+
availableAgents,
|
|
331
|
+
currentSessionDisplayName,
|
|
332
|
+
effectiveSessionProjectRoot,
|
|
333
|
+
effectiveSessionProjectName,
|
|
334
|
+
selectedSession,
|
|
374
335
|
threadRef,
|
|
375
|
-
agent
|
|
376
|
-
|
|
336
|
+
agent,
|
|
337
|
+
isAwaitingAssistantOutput,
|
|
338
|
+
parentSession
|
|
339
|
+
});
|
|
377
340
|
|
|
378
341
|
return (
|
|
379
342
|
<ChatPresenterProvider presenter={presenter}>
|
|
@@ -48,10 +48,12 @@ describe('buildNcpSendMetadata', () => {
|
|
|
48
48
|
it('includes the project root in the first-message metadata when present', () => {
|
|
49
49
|
expect(
|
|
50
50
|
buildNcpSendMetadata({
|
|
51
|
+
agentId: 'engineer',
|
|
51
52
|
sessionType: 'codex',
|
|
52
53
|
projectRoot: ' /tmp/project-alpha ',
|
|
53
54
|
}),
|
|
54
55
|
).toMatchObject({
|
|
56
|
+
agent_id: 'engineer',
|
|
55
57
|
session_type: 'codex',
|
|
56
58
|
project_root: '/tmp/project-alpha',
|
|
57
59
|
});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { appQueryClient } from '@/app-query-client';
|
|
2
2
|
import { deleteNcpSessionSummaryInQueryClient } from '@/api/ncp-session-query-cache';
|
|
3
3
|
import { deleteNcpSession as deleteNcpSessionApi } from '@/api/ncp-session';
|
|
4
|
+
import type { ChatToolActionViewModel } from '@nextclaw/agent-chat-ui';
|
|
4
5
|
import type { ChatSessionListManager } from '@/components/chat/managers/chat-session-list.manager';
|
|
5
6
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
6
7
|
import type { ChatUiManager } from '@/components/chat/managers/chat-ui.manager';
|
|
7
8
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
8
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ChatChildSessionTab,
|
|
11
|
+
ChatThreadSnapshot,
|
|
12
|
+
} from '@/components/chat/stores/chat-thread.store';
|
|
9
13
|
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
10
14
|
import { t } from '@/lib/i18n';
|
|
11
15
|
|
|
@@ -45,6 +49,107 @@ export class NcpChatThreadManager {
|
|
|
45
49
|
this.uiManager.goToProviders();
|
|
46
50
|
};
|
|
47
51
|
|
|
52
|
+
private upsertChildSessionTab = (tab: ChatChildSessionTab) => {
|
|
53
|
+
const { snapshot } = useChatThreadStore.getState();
|
|
54
|
+
const existingIndex = snapshot.childSessionTabs.findIndex(
|
|
55
|
+
(item) => item.sessionKey === tab.sessionKey,
|
|
56
|
+
);
|
|
57
|
+
const nextTabs =
|
|
58
|
+
existingIndex >= 0
|
|
59
|
+
? snapshot.childSessionTabs.map((item, index) =>
|
|
60
|
+
index === existingIndex ? { ...item, ...tab } : item,
|
|
61
|
+
)
|
|
62
|
+
: [...snapshot.childSessionTabs, tab];
|
|
63
|
+
useChatThreadStore.getState().setSnapshot({
|
|
64
|
+
childSessionTabs: nextTabs,
|
|
65
|
+
activeChildSessionKey: tab.sessionKey,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
openSessionFromToolAction = (action: ChatToolActionViewModel) => {
|
|
70
|
+
if (action.kind !== 'open-session') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (action.sessionKind === 'child' && !this.isCompactViewport()) {
|
|
74
|
+
const parentSessionKey =
|
|
75
|
+
action.parentSessionId?.trim() ||
|
|
76
|
+
useChatSessionListStore.getState().snapshot.selectedSessionKey ||
|
|
77
|
+
null;
|
|
78
|
+
this.upsertChildSessionTab({
|
|
79
|
+
sessionKey: action.sessionId,
|
|
80
|
+
parentSessionKey,
|
|
81
|
+
label: action.label?.trim() || null,
|
|
82
|
+
agentId: action.agentId?.trim() || null,
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.uiManager.goToSession(action.sessionId);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
selectChildSessionDetail = (sessionKey: string) => {
|
|
90
|
+
const normalizedSessionKey = sessionKey.trim();
|
|
91
|
+
if (!normalizedSessionKey) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const { childSessionTabs } = useChatThreadStore.getState().snapshot;
|
|
95
|
+
if (!childSessionTabs.some((tab) => tab.sessionKey === normalizedSessionKey)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
useChatThreadStore.getState().setSnapshot({
|
|
99
|
+
activeChildSessionKey: normalizedSessionKey,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
closeChildSessionDetail = () => {
|
|
104
|
+
const {
|
|
105
|
+
sessionKey,
|
|
106
|
+
childSessionTabs,
|
|
107
|
+
activeChildSessionKey,
|
|
108
|
+
} = useChatThreadStore.getState().snapshot;
|
|
109
|
+
if (!sessionKey) {
|
|
110
|
+
useChatThreadStore.getState().setSnapshot({
|
|
111
|
+
childSessionTabs: [],
|
|
112
|
+
activeChildSessionKey: null,
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const nextTabs = childSessionTabs.filter(
|
|
117
|
+
(tab) => tab.parentSessionKey !== sessionKey,
|
|
118
|
+
);
|
|
119
|
+
const nextActiveKey = nextTabs.some((tab) => tab.sessionKey === activeChildSessionKey)
|
|
120
|
+
? activeChildSessionKey
|
|
121
|
+
: null;
|
|
122
|
+
useChatThreadStore.getState().setSnapshot({
|
|
123
|
+
childSessionTabs: nextTabs,
|
|
124
|
+
activeChildSessionKey: nextActiveKey,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
goToParentSession = () => {
|
|
129
|
+
const {
|
|
130
|
+
parentSessionKey,
|
|
131
|
+
childSessionTabs,
|
|
132
|
+
activeChildSessionKey,
|
|
133
|
+
} = useChatThreadStore.getState().snapshot;
|
|
134
|
+
const activeChildParentSessionKey =
|
|
135
|
+
childSessionTabs.find((tab) => tab.sessionKey === activeChildSessionKey)
|
|
136
|
+
?.parentSessionKey ?? null;
|
|
137
|
+
const resolvedParentSessionKey =
|
|
138
|
+
parentSessionKey ?? activeChildParentSessionKey;
|
|
139
|
+
if (!resolvedParentSessionKey) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.closeChildSessionDetail();
|
|
143
|
+
this.uiManager.goToSession(resolvedParentSessionKey);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
private isCompactViewport = (): boolean => {
|
|
147
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return window.matchMedia('(max-width: 767px)').matches;
|
|
151
|
+
};
|
|
152
|
+
|
|
48
153
|
private deleteCurrentSession = async () => {
|
|
49
154
|
const {
|
|
50
155
|
snapshot: { selectedSessionKey }
|