@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/assets/{ChannelsList-ByHWHkQS.js → ChannelsList-DVDu1xvz.js} +6 -6
  3. package/dist/assets/ChatPage-Z9tRzm_n.js +43 -0
  4. package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
  5. package/dist/assets/{DocBrowser-3y_NHZ71.js → DocBrowser-BmtBLFU0.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-CVJuwCcw.js → DocBrowserContext-YIKkPb76.js} +1 -1
  7. package/dist/assets/{LogoBadge-D8fyilO-.js → LogoBadge-F7ZWdxLT.js} +1 -1
  8. package/dist/assets/{MarketplacePage-CmhsZXr1.js → MarketplacePage-Buo9HrOz.js} +2 -2
  9. package/dist/assets/MarketplacePage-D6rVQEQR.js +1 -0
  10. package/dist/assets/{McpMarketplacePage-C7PkCYbp.js → McpMarketplacePage-JnkYwK7p.js} +2 -2
  11. package/dist/assets/ModelConfig-BYRhgp0c.js +1 -0
  12. package/dist/assets/ProvidersList-DmLyyHvX.js +1 -0
  13. package/dist/assets/RemoteAccessPage-CDSSvH7Z.js +1 -0
  14. package/dist/assets/RuntimeConfig-v7a7Fe3x.js +1 -0
  15. package/dist/assets/{SearchConfig-Dm7r2yfp.js → SearchConfig-D5f1EkLE.js} +1 -1
  16. package/dist/assets/{SecretsConfig-BBP_mbQh.js → SecretsConfig-D61IKcYt.js} +2 -2
  17. package/dist/assets/{SessionsConfig-6wNJloZN.js → SessionsConfig-BRIxVTEv.js} +2 -2
  18. package/dist/assets/{book-open-B26jGBjY.js → book-open-CXoF5nQC.js} +1 -1
  19. package/dist/assets/chat-session-display-D0WpnuRZ.js +1 -0
  20. package/dist/assets/{chunk-JZWAC4HX-B-4B29RN.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
  21. package/dist/assets/{config-BaC29Qf-.js → config-DJswxxE8.js} +1 -1
  22. package/dist/assets/{createLucideIcon-DiFAvXmK.js → createLucideIcon-CjGHOWb6.js} +1 -1
  23. package/dist/assets/{dist-pCfWPG1A.js → dist-Cl2QB-2y.js} +1 -1
  24. package/dist/assets/{dist-kW_O3kyZ.js → dist-nqTTbVdA.js} +1 -1
  25. package/dist/assets/{external-link-D5-p-Gmm.js → external-link-tIO7zING.js} +1 -1
  26. package/dist/assets/{hash-BlwrSV0q.js → hash-JWUyl1pT.js} +1 -1
  27. package/dist/assets/i18n-CDHMXlRZ.js +1 -0
  28. package/dist/assets/{index-DvKS3L9j.js → index-BuwbBgmT.js} +3 -3
  29. package/dist/assets/index-bZ8cqQIS.css +1 -0
  30. package/dist/assets/{label-RyXfZqkP.js → label-BIpeNu4r.js} +1 -1
  31. package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
  32. package/dist/assets/{logos-Bpl8QTgI.js → logos-DThdM9lk.js} +1 -1
  33. package/dist/assets/{page-layout--S0YBU0W.js → page-layout-D3Xo605Z.js} +1 -1
  34. package/dist/assets/plus-PHf8q-Ct.js +1 -0
  35. package/dist/assets/{popover-BEjfbEwy.js → popover-BJRUGA_H.js} +1 -1
  36. package/dist/assets/provider-models-bz5y28rq.js +1 -0
  37. package/dist/assets/{react-BuSP2-8B.js → react-7ZHqQtEV.js} +1 -1
  38. package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
  39. package/dist/assets/{save-DPPPpD_c.js → save-DJM5RRWW.js} +1 -1
  40. package/dist/assets/search-C91yH_6y.js +1 -0
  41. package/dist/assets/{security-config-6t78Ph-I.js → security-config-DbUyWcQz.js} +1 -1
  42. package/dist/assets/{select-CT50pzod.js → select-DSkTc61S.js} +1 -1
  43. package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
  44. package/dist/assets/{status-dot-BbBqRHfh.js → status-dot-LNBlDu3q.js} +1 -1
  45. package/dist/assets/{switch-D3l6AcCk.js → switch-Bo-Y46HZ.js} +1 -1
  46. package/dist/assets/tabs-custom-DXv507_2.js +1 -0
  47. package/dist/assets/{trash-2-B2_AGVE3.js → trash-2-DFZmW6Gg.js} +1 -1
  48. package/dist/assets/useConfirmDialog-COwYXDKm.js +1 -0
  49. package/dist/assets/{useMutation-BzCrO8j-.js → useMutation-DrZrOgVL.js} +1 -1
  50. package/dist/assets/x-D7Q1yqSF.js +1 -0
  51. package/dist/index.html +18 -18
  52. package/package.json +6 -6
  53. package/src/api/ncp-session.test.ts +37 -0
  54. package/src/api/ncp-session.ts +29 -1
  55. package/src/api/server-path.ts +23 -0
  56. package/src/api/types.ts +45 -0
  57. package/src/components/chat/ChatConversationPanel.test.tsx +53 -9
  58. package/src/components/chat/ChatConversationPanel.tsx +122 -79
  59. package/src/components/chat/ChatSidebar.test.tsx +2 -2
  60. package/src/components/chat/ChatSidebar.tsx +2 -2
  61. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
  62. package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
  63. package/src/components/chat/adapters/chat-message-part.adapter.ts +26 -14
  64. package/src/components/chat/adapters/chat-message.adapter.test.ts +159 -13
  65. package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +191 -0
  66. package/src/components/chat/adapters/{chat-message.file-operation-card.ts → file-operation/card.ts} +74 -181
  67. package/src/components/chat/adapters/{chat-message.file-operation-diff.ts → file-operation/diff.ts} +178 -188
  68. package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
  69. package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
  70. package/src/components/chat/chat-child-session-panel.tsx +100 -0
  71. package/src/components/chat/chat-composer-state.ts +3 -3
  72. package/src/components/chat/chat-page-runtime.test.ts +1 -0
  73. package/src/components/chat/chat-session-display.test.ts +22 -0
  74. package/src/components/chat/chat-session-display.ts +6 -1
  75. package/src/components/chat/containers/chat-input-bar.container.tsx +21 -24
  76. package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
  77. package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
  78. package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
  79. package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
  80. package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
  81. package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
  82. package/src/components/chat/ncp/NcpChatPage.tsx +219 -116
  83. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
  84. package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
  85. package/src/components/chat/ncp/ncp-chat-thread.manager.ts +49 -0
  86. package/src/components/chat/ncp/ncp-session-adapter.test.ts +24 -0
  87. package/src/components/chat/ncp/ncp-session-adapter.ts +47 -0
  88. package/src/components/chat/ncp/use-ncp-session-list-view.ts +10 -1
  89. package/src/components/chat/presenter/chat-presenter-context.tsx +4 -1
  90. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
  91. package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
  92. package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
  93. package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
  94. package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
  95. package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
  96. package/src/components/chat/stores/chat-input.store.ts +6 -3
  97. package/src/components/chat/stores/chat-thread.store.ts +17 -3
  98. package/src/components/chat/useHydratedNcpAgent.test.tsx +30 -23
  99. package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
  100. package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
  101. package/src/hooks/server-path/use-server-path-browse.ts +19 -0
  102. package/src/hooks/useConfig.ts +26 -1
  103. package/src/lib/i18n/i18n-language-owner.ts +94 -0
  104. package/src/lib/i18n/i18n.path-picker.ts +12 -0
  105. package/src/lib/i18n.chat.ts +23 -0
  106. package/src/lib/i18n.ts +21 -84
  107. package/src/lib/session-project/session-project.utils.ts +30 -0
  108. package/dist/assets/ChatPage-FdT3pDnw.js +0 -42
  109. package/dist/assets/DocBrowser-CMdPdbZj.js +0 -1
  110. package/dist/assets/MarketplacePage-9oKmxN2n.js +0 -1
  111. package/dist/assets/ModelConfig-DmCY6jWM.js +0 -1
  112. package/dist/assets/ProvidersList-ClT-34aX.js +0 -1
  113. package/dist/assets/RemoteAccessPage-B6hUZl1O.js +0 -1
  114. package/dist/assets/RuntimeConfig-C5aqliGk.js +0 -1
  115. package/dist/assets/chat-session-display-Bjmn4aIZ.js +0 -1
  116. package/dist/assets/i18n-CSytxMFI.js +0 -1
  117. package/dist/assets/index-CUy6doWo.css +0 -1
  118. package/dist/assets/loader-circle-B2J777gj.js +0 -1
  119. package/dist/assets/plus-CM9XJ0Tf.js +0 -1
  120. package/dist/assets/provider-models-C8JQUd1E.js +0 -1
  121. package/dist/assets/search-Ctaw34Kp.js +0 -1
  122. package/dist/assets/skeleton-Bycyb0zU.js +0 -1
  123. package/dist/assets/tabs-custom-TZQ5WPWP.js +0 -1
  124. package/dist/assets/useConfirmDialog-BDpdjfIO.js +0 -1
  125. package/dist/assets/x-CHOBE-63.js +0 -1
  126. package/src/components/chat/adapters/chat-message.subagent-tool-card.ts +0 -154
  127. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
  128. /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
  129. /package/dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
@@ -1,16 +1,18 @@
1
1
  import { useRef } from "react";
2
+ import { ArrowLeft } from "lucide-react";
2
3
  import { useStickyBottomScroll } from "@nextclaw/agent-chat-ui";
3
- import { Button } from "@/components/ui/button";
4
4
  import {
5
5
  ChatInputBarContainer,
6
6
  ChatMessageListContainer,
7
7
  } from "@/components/chat/nextclaw";
8
+ import { ChatChildSessionPanel } from "@/components/chat/chat-child-session-panel";
8
9
  import { ChatWelcome } from "@/components/chat/ChatWelcome";
9
10
  import { usePresenter } from "@/components/chat/presenter/chat-presenter-context";
11
+ import { ChatSessionHeaderActions } from "@/components/chat/session-header/chat-session-header-actions";
12
+ import { ChatSessionProjectBadge } from "@/components/chat/session-header/chat-session-project-badge";
10
13
  import { useChatThreadStore } from "@/components/chat/stores/chat-thread.store";
11
14
  import { t } from "@/lib/i18n";
12
15
  import { cn } from "@/lib/utils";
13
- import { Trash2 } from "lucide-react";
14
16
 
15
17
  function ChatConversationSkeleton() {
16
18
  return (
@@ -45,16 +47,20 @@ export function ChatConversationPanel() {
45
47
  const snapshot = useChatThreadStore((state) => state.snapshot);
46
48
  const fallbackThreadRef = useRef<HTMLDivElement | null>(null);
47
49
  const threadRef = snapshot.threadRef ?? fallbackThreadRef;
50
+ const detailSessionKey =
51
+ snapshot.childSessionDetailParentSessionKey === snapshot.sessionKey
52
+ ? snapshot.childSessionDetailSessionKey
53
+ : null;
48
54
  const shouldShowSessionHeader = Boolean(
49
- snapshot.selectedSessionKey || snapshot.sessionTypeLabel,
55
+ snapshot.sessionKey || snapshot.sessionTypeLabel,
50
56
  );
51
57
  const sessionHeaderTitle =
52
58
  snapshot.sessionDisplayName ||
53
- snapshot.selectedSessionKey ||
59
+ (snapshot.canDeleteSession && snapshot.sessionKey ? snapshot.sessionKey : null) ||
54
60
  t("chatSidebarNewTask");
55
61
 
56
62
  const showWelcome =
57
- !snapshot.selectedSessionKey &&
63
+ !snapshot.canDeleteSession &&
58
64
  snapshot.messages.length === 0 &&
59
65
  !snapshot.isSending;
60
66
  const hasConfiguredModel = snapshot.modelOptions.length > 0;
@@ -68,7 +74,7 @@ export function ChatConversationPanel() {
68
74
 
69
75
  const { onScroll: handleScroll } = useStickyBottomScroll({
70
76
  scrollRef: threadRef,
71
- resetKey: snapshot.selectedSessionKey,
77
+ resetKey: snapshot.sessionKey,
72
78
  isLoading: snapshot.isHistoryLoading,
73
79
  hasContent: snapshot.messages.length > 0,
74
80
  contentVersion: snapshot.messages[snapshot.messages.length - 1] ?? null,
@@ -79,91 +85,128 @@ export function ChatConversationPanel() {
79
85
  }
80
86
 
81
87
  return (
82
- <section className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
83
- <div
84
- className={cn(
85
- "px-5 border-b border-gray-200/60 bg-white/80 backdrop-blur-sm flex items-center justify-between shrink-0 overflow-hidden transition-all duration-200",
86
- shouldShowSessionHeader
87
- ? "py-3 opacity-100"
88
- : "h-0 py-0 opacity-0 border-b-0",
89
- )}
90
- >
91
- <div className="min-w-0 flex-1 flex items-center gap-2">
92
- <span className="text-sm font-medium text-gray-700 truncate">
93
- {sessionHeaderTitle}
94
- </span>
95
- {snapshot.sessionTypeLabel ? (
96
- <span className="shrink-0 rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">
97
- {snapshot.sessionTypeLabel}
98
- </span>
99
- ) : null}
100
- </div>
101
- {snapshot.selectedSessionKey ? (
102
- <Button
103
- variant="ghost"
104
- size="icon"
105
- className="rounded-lg shrink-0 text-gray-400 hover:text-destructive"
106
- onClick={presenter.chatThreadManager.deleteSession}
107
- disabled={!snapshot.canDeleteSession || snapshot.isDeletePending}
108
- >
109
- <Trash2 className="h-4 w-4" />
110
- </Button>
88
+ <section className="flex-1 min-h-0 flex overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
89
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
90
+ {snapshot.parentSessionKey ? (
91
+ <div className="border-b border-gray-200/60 bg-white/75 px-5 py-2 backdrop-blur-sm">
92
+ <button
93
+ type="button"
94
+ onClick={presenter.chatThreadManager.goToParentSession}
95
+ className="inline-flex items-center gap-2 text-xs font-medium text-gray-600 transition-colors hover:text-gray-900"
96
+ >
97
+ <ArrowLeft className="h-3.5 w-3.5" />
98
+ <span>
99
+ Back to parent
100
+ {snapshot.parentSessionLabel?.trim()
101
+ ? ` · ${snapshot.parentSessionLabel.trim()}`
102
+ : ""}
103
+ </span>
104
+ </button>
105
+ </div>
111
106
  ) : null}
112
- </div>
113
107
 
114
- {shouldShowProviderHint && (
115
- <div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 flex items-center justify-between gap-3 shrink-0">
116
- <span className="text-xs text-amber-800">
117
- {t("chatModelNoOptions")}
118
- </span>
119
- <button
120
- type="button"
121
- onClick={presenter.chatThreadManager.goToProviders}
122
- className="text-xs font-semibold text-amber-900 underline-offset-2 hover:underline"
123
- >
124
- {t("chatGoConfigureProvider")}
125
- </button>
108
+ <div
109
+ className={cn(
110
+ "px-5 border-b border-gray-200/60 bg-white/80 backdrop-blur-sm flex items-center justify-between shrink-0 overflow-hidden transition-all duration-200",
111
+ shouldShowSessionHeader
112
+ ? "py-3 opacity-100"
113
+ : "h-0 py-0 opacity-0 border-b-0",
114
+ )}
115
+ >
116
+ <div className="min-w-0 flex-1 flex items-center gap-2">
117
+ <span className="text-sm font-medium text-gray-700 truncate">
118
+ {sessionHeaderTitle}
119
+ </span>
120
+ {snapshot.sessionTypeLabel ? (
121
+ <span className="shrink-0 rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">
122
+ {snapshot.sessionTypeLabel}
123
+ </span>
124
+ ) : null}
125
+ {snapshot.sessionProjectName ? (
126
+ <ChatSessionProjectBadge
127
+ sessionKey={snapshot.sessionKey ?? "draft"}
128
+ projectName={snapshot.sessionProjectName}
129
+ projectRoot={snapshot.sessionProjectRoot}
130
+ persistToServer={snapshot.canDeleteSession}
131
+ />
132
+ ) : null}
133
+ </div>
134
+ {snapshot.sessionKey ? (
135
+ <ChatSessionHeaderActions
136
+ sessionKey={snapshot.sessionKey}
137
+ canDeleteSession={snapshot.canDeleteSession}
138
+ isDeletePending={snapshot.isDeletePending}
139
+ projectRoot={snapshot.sessionProjectRoot}
140
+ onDeleteSession={presenter.chatThreadManager.deleteSession}
141
+ />
142
+ ) : null}
126
143
  </div>
127
- )}
128
144
 
129
- {snapshot.sessionTypeUnavailable &&
130
- snapshot.sessionTypeUnavailableMessage?.trim() && (
131
- <div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 shrink-0">
145
+ {shouldShowProviderHint && (
146
+ <div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 flex items-center justify-between gap-3 shrink-0">
132
147
  <span className="text-xs text-amber-800">
133
- {snapshot.sessionTypeUnavailableMessage}
148
+ {t("chatModelNoOptions")}
134
149
  </span>
150
+ <button
151
+ type="button"
152
+ onClick={presenter.chatThreadManager.goToProviders}
153
+ className="text-xs font-semibold text-amber-900 underline-offset-2 hover:underline"
154
+ >
155
+ {t("chatGoConfigureProvider")}
156
+ </button>
135
157
  </div>
136
158
  )}
137
159
 
138
- <div
139
- ref={threadRef}
140
- onScroll={handleScroll}
141
- className="flex-1 min-h-0 overflow-y-auto custom-scrollbar"
142
- >
143
- {showWelcome ? (
144
- <ChatWelcome
145
- onCreateSession={presenter.chatThreadManager.createSession}
146
- />
147
- ) : hideEmptyHint ? (
148
- <div className="h-full" />
149
- ) : snapshot.messages.length === 0 ? (
150
- <div className="px-5 py-5 text-sm text-gray-500">
151
- {t("chatNoMessages")}
152
- </div>
153
- ) : (
154
- <div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
155
- <ChatMessageListContainer
156
- key={snapshot.selectedSessionKey ?? "draft"}
157
- messages={snapshot.messages}
158
- isSending={
159
- snapshot.isSending && snapshot.isAwaitingAssistantOutput
160
- }
160
+ {snapshot.sessionTypeUnavailable &&
161
+ snapshot.sessionTypeUnavailableMessage?.trim() && (
162
+ <div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 shrink-0">
163
+ <span className="text-xs text-amber-800">
164
+ {snapshot.sessionTypeUnavailableMessage}
165
+ </span>
166
+ </div>
167
+ )}
168
+
169
+ <div
170
+ ref={threadRef}
171
+ onScroll={handleScroll}
172
+ className="flex-1 min-h-0 overflow-y-auto custom-scrollbar"
173
+ >
174
+ {showWelcome ? (
175
+ <ChatWelcome
176
+ onCreateSession={presenter.chatThreadManager.createSession}
161
177
  />
162
- </div>
163
- )}
178
+ ) : hideEmptyHint ? (
179
+ <div className="h-full" />
180
+ ) : snapshot.messages.length === 0 ? (
181
+ <div className="px-5 py-5 text-sm text-gray-500">
182
+ {t("chatNoMessages")}
183
+ </div>
184
+ ) : (
185
+ <div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
186
+ <ChatMessageListContainer
187
+ key={snapshot.sessionKey ?? "draft"}
188
+ messages={snapshot.messages}
189
+ isSending={
190
+ snapshot.isSending && snapshot.isAwaitingAssistantOutput
191
+ }
192
+ onToolAction={presenter.chatThreadManager.openSessionFromToolAction}
193
+ />
194
+ </div>
195
+ )}
196
+ </div>
197
+
198
+ <ChatInputBarContainer />
164
199
  </div>
165
200
 
166
- <ChatInputBarContainer />
201
+ {detailSessionKey ? (
202
+ <ChatChildSessionPanel
203
+ sessionKey={detailSessionKey}
204
+ title={snapshot.childSessionDetailLabel}
205
+ onClose={presenter.chatThreadManager.closeChildSessionDetail}
206
+ onBackToParent={presenter.chatThreadManager.goToParentSession}
207
+ onToolAction={presenter.chatThreadManager.openSessionFromToolAction}
208
+ />
209
+ ) : null}
167
210
  </section>
168
211
  );
169
212
  }
@@ -36,8 +36,8 @@ vi.mock('@/components/doc-browser', () => ({
36
36
  })
37
37
  }));
38
38
 
39
- vi.mock('@/components/chat/chat-session-label.service', () => ({
40
- useChatSessionLabelService: () => async (params: {
39
+ vi.mock('@/components/chat/hooks/use-chat-session-label', () => ({
40
+ useChatSessionLabel: () => async (params: {
41
41
  sessionKey: string;
42
42
  label: string | null;
43
43
  }) => {
@@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
8
8
  import { SelectItem } from '@/components/ui/select';
9
9
  import { ChatSidebarSessionItem } from '@/components/chat/chat-sidebar-session-item';
10
10
  import { resolveSessionContextView } from '@/lib/session-context.utils';
11
- import { useChatSessionLabelService } from '@/components/chat/chat-session-label.service';
11
+ import { useChatSessionLabel } from '@/components/chat/hooks/use-chat-session-label';
12
12
  import { useNcpSessionListView, type NcpSessionListItemView } from '@/components/chat/ncp/use-ncp-session-list-view';
13
13
  import { usePresenter } from '@/components/chat/presenter/chat-presenter-context';
14
14
  import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
@@ -108,7 +108,7 @@ export function ChatSidebar() {
108
108
  const { isLoading, items } = useNcpSessionListView();
109
109
  const { language, setLanguage } = useI18n();
110
110
  const { theme, setTheme } = useTheme();
111
- const updateSessionLabel = useChatSessionLabelService();
111
+ const updateSessionLabel = useChatSessionLabel();
112
112
  const currentThemeLabel = t(THEME_OPTIONS.find((o) => o.value === theme)?.labelKey ?? 'themeWarm');
113
113
  const currentLanguageLabel = LANGUAGE_OPTIONS.find((o) => o.value === language)?.label ?? language;
114
114
 
@@ -18,6 +18,7 @@ describe('buildChatSlashItems', () => {
18
18
  const texts = {
19
19
  slashSkillSubtitle: 'Skill',
20
20
  slashSkillSpecLabel: 'Spec',
21
+ slashSkillScopeLabel: 'Scope',
21
22
  noSkillDescription: 'No description'
22
23
  };
23
24
 
@@ -12,6 +12,7 @@ export type ChatThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' |
12
12
  export type ChatSkillRecord = {
13
13
  key: string;
14
14
  label: string;
15
+ scopeLabel?: string;
15
16
  description?: string;
16
17
  descriptionZh?: string;
17
18
  badgeLabel?: string;
@@ -43,6 +44,7 @@ const SLASH_ITEM_MATCH_SCORE = {
43
44
  export type ChatInputBarAdapterTexts = {
44
45
  slashSkillSubtitle: string;
45
46
  slashSkillSpecLabel: string;
47
+ slashSkillScopeLabel: string;
46
48
  noSkillDescription: string;
47
49
  recentSkillsLabel: string;
48
50
  allSkillsLabel: string;
@@ -171,7 +173,7 @@ function prioritizeSkillRecords(skillRecords: ChatSkillRecord[], recentSkillValu
171
173
  export function buildChatSlashItems(
172
174
  skillRecords: ChatSkillRecord[],
173
175
  normalizedSlashQuery: string,
174
- texts: Pick<ChatInputBarAdapterTexts, 'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'noSkillDescription'>,
176
+ texts: Pick<ChatInputBarAdapterTexts, 'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'slashSkillScopeLabel' | 'noSkillDescription'>,
175
177
  recentSkillValues: string[] = []
176
178
  ): ChatSlashItem[] {
177
179
  const skillSortCollator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
@@ -211,7 +213,10 @@ export function buildChatSlashItems(
211
213
  title: record.label || record.key,
212
214
  subtitle: texts.slashSkillSubtitle,
213
215
  description: (record.descriptionZh ?? record.description ?? '').trim() || texts.noSkillDescription,
214
- detailLines: [`${texts.slashSkillSpecLabel}: ${record.key}`],
216
+ detailLines: [
217
+ `${texts.slashSkillSpecLabel}: ${record.key}`,
218
+ ...(record.scopeLabel ? [`${texts.slashSkillScopeLabel}: ${record.scopeLabel}`] : [])
219
+ ],
215
220
  value: record.key
216
221
  }));
217
222
  }
@@ -3,15 +3,13 @@ import {
3
3
  summarizeToolArgs,
4
4
  type ToolCard,
5
5
  } from "@/lib/chat-message";
6
- import {
7
- type ChatInlineTokenSource,
8
- } from "@/components/chat/chat-inline-token.utils";
6
+ import { type ChatInlineTokenSource } from "@/components/chat/chat-inline-token.utils";
9
7
  import {
10
8
  buildRenderableText,
11
9
  buildTextPart,
12
10
  } from "@/components/chat/adapters/chat-message-inline-content.adapter";
13
- import { buildFileOperationCardData } from "@/components/chat/adapters/chat-message.file-operation-card";
14
- import { buildSubagentToolCard } from "@/components/chat/adapters/chat-message.subagent-tool-card";
11
+ import { buildFileOperationCardData } from "@/components/chat/adapters/file-operation/card";
12
+ import { buildSessionRequestToolCard } from "@/components/chat/adapters/chat-message.session-request-tool-card";
15
13
  import type {
16
14
  ChatMessagePartViewModel,
17
15
  ChatToolPartViewModel,
@@ -79,6 +77,7 @@ export type ChatMessagePartSource =
79
77
  type ToolCardViewSource = ToolCard & {
80
78
  statusTone: ChatToolPartViewModel["statusTone"];
81
79
  statusLabel: string;
80
+ action?: ChatToolPartViewModel["action"];
82
81
  fileOperation?: ChatToolPartViewModel["fileOperation"];
83
82
  outputData?: unknown;
84
83
  };
@@ -116,7 +115,9 @@ function readOptionalNumber(value: unknown): number | null {
116
115
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
117
116
  }
118
117
 
119
- function isTerminalResultRecord(value: unknown): value is Record<string, unknown> {
118
+ function isTerminalResultRecord(
119
+ value: unknown,
120
+ ): value is Record<string, unknown> {
120
121
  if (!isRecord(value)) {
121
122
  return false;
122
123
  }
@@ -181,9 +182,10 @@ function buildToolCard(
181
182
  toolName: toolCard.name,
182
183
  summary: toolCard.detail,
183
184
  inputLabel: texts.toolInputLabel,
184
- input: "input" in toolCard && typeof toolCard.input === "string"
185
- ? toolCard.input
186
- : undefined,
185
+ input:
186
+ "input" in toolCard && typeof toolCard.input === "string"
187
+ ? toolCard.input
188
+ : undefined,
187
189
  output: toolCard.text,
188
190
  outputData: toolCard.outputData,
189
191
  hasResult: Boolean(toolCard.hasResult),
@@ -193,6 +195,9 @@ function buildToolCard(
193
195
  toolCard.kind === "call" ? texts.toolCallLabel : texts.toolResultLabel,
194
196
  outputLabel: texts.toolOutputLabel,
195
197
  emptyLabel: texts.toolNoOutputLabel,
198
+ ...("action" in toolCard && toolCard.action
199
+ ? { action: toolCard.action }
200
+ : {}),
196
201
  ...("fileOperation" in toolCard && toolCard.fileOperation
197
202
  ? { fileOperation: toolCard.fileOperation }
198
203
  : {}),
@@ -269,7 +274,10 @@ function parseStructuredValue(value: unknown): unknown {
269
274
  }
270
275
  }
271
276
 
272
- function buildToolInvocationInput(args?: unknown, parsedArgs?: unknown): string | undefined {
277
+ function buildToolInvocationInput(
278
+ args?: unknown,
279
+ parsedArgs?: unknown,
280
+ ): string | undefined {
273
281
  const source = parsedArgs ?? parseStructuredValue(args);
274
282
  const text = stringifyUnknown(source).trim();
275
283
  return text || undefined;
@@ -326,14 +334,18 @@ function buildToolInvocationPart(
326
334
  return assetFileView;
327
335
  }
328
336
 
329
- const subagentToolCard = buildSubagentToolCard({
337
+ const sessionRequestToolCard = buildSessionRequestToolCard({
330
338
  invocation,
331
- texts,
339
+ texts: {
340
+ toolStatusRunningLabel: texts.toolStatusRunningLabel,
341
+ toolStatusCompletedLabel: texts.toolStatusCompletedLabel,
342
+ toolStatusFailedLabel: texts.toolStatusFailedLabel,
343
+ },
332
344
  });
333
- if (subagentToolCard) {
345
+ if (sessionRequestToolCard) {
334
346
  return {
335
347
  type: "tool-card",
336
- card: buildToolCard(subagentToolCard, texts),
348
+ card: buildToolCard(sessionRequestToolCard, texts),
337
349
  };
338
350
  }
339
351