@nextclaw/ui 0.11.23 → 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.
Files changed (117) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/assets/{ChannelsList-DVDu1xvz.js → ChannelsList-NKNKsf1J.js} +1 -1
  3. package/dist/assets/ChatPage-p23OnnEI.js +43 -0
  4. package/dist/assets/DocBrowser-C8b2uPgL.js +1 -0
  5. package/dist/assets/{DocBrowser-BmtBLFU0.js → DocBrowser-DxdSujSc.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-YIKkPb76.js → DocBrowserContext-CQ-8jMha.js} +1 -1
  7. package/dist/assets/{LogoBadge-F7ZWdxLT.js → LogoBadge-D-KQIN4U.js} +1 -1
  8. package/dist/assets/{MarketplacePage-Buo9HrOz.js → MarketplacePage-CRNvxtvx.js} +2 -2
  9. package/dist/assets/MarketplacePage-GGkEXowp.js +1 -0
  10. package/dist/assets/{McpMarketplacePage-JnkYwK7p.js → McpMarketplacePage-Cu7GmCcc.js} +2 -2
  11. package/dist/assets/{ModelConfig-BYRhgp0c.js → ModelConfig-CEpx9fro.js} +1 -1
  12. package/dist/assets/{ProvidersList-DmLyyHvX.js → ProvidersList-BWbUb7-2.js} +1 -1
  13. package/dist/assets/{RemoteAccessPage-CDSSvH7Z.js → RemoteAccessPage-NsawrZb0.js} +1 -1
  14. package/dist/assets/RuntimeConfig-BJHBsVTd.js +1 -0
  15. package/dist/assets/{SearchConfig-D5f1EkLE.js → SearchConfig-BsaX_WYy.js} +1 -1
  16. package/dist/assets/{SecretsConfig-D61IKcYt.js → SecretsConfig-CgDZOd3w.js} +1 -1
  17. package/dist/assets/{SessionsConfig-BRIxVTEv.js → SessionsConfig-Dd-KM7F7.js} +2 -2
  18. package/dist/assets/{book-open-CXoF5nQC.js → book-open-FnK2xCQd.js} +1 -1
  19. package/dist/assets/chat-session-display-BD_AN71I.js +1 -0
  20. package/dist/assets/{chunk-JZWAC4HX-CvRWvTy5.js → chunk-JZWAC4HX-B5l0hr_u.js} +1 -1
  21. package/dist/assets/{config-DJswxxE8.js → config-JKmXfZ3q.js} +1 -1
  22. package/dist/assets/{createLucideIcon-CjGHOWb6.js → createLucideIcon-o1WWhwhd.js} +1 -1
  23. package/dist/assets/{dist-nqTTbVdA.js → dist-C_moWYv7.js} +1 -1
  24. package/dist/assets/{dist-Cl2QB-2y.js → dist-DazA6Wd_.js} +1 -1
  25. package/dist/assets/{external-link-tIO7zING.js → external-link-BKje3SiD.js} +1 -1
  26. package/dist/assets/{hash-JWUyl1pT.js → hash-DfW4DT8O.js} +1 -1
  27. package/dist/assets/i18n-BK1w-oBy.js +1 -0
  28. package/dist/assets/index-BZaB1TqM.js +6 -0
  29. package/dist/assets/index-DaR9igPC.css +1 -0
  30. package/dist/assets/{label-BIpeNu4r.js → label-BzDWmdOe.js} +1 -1
  31. package/dist/assets/loader-circle-DdZPxBUz.js +1 -0
  32. package/dist/assets/{logos-DThdM9lk.js → logos-CTLlde_T.js} +1 -1
  33. package/dist/assets/{page-layout-D3Xo605Z.js → page-layout-BagR3t59.js} +1 -1
  34. package/dist/assets/plus-DP2PSCPO.js +1 -0
  35. package/dist/assets/{popover-BJRUGA_H.js → popover-5DWhNfd4.js} +1 -1
  36. package/dist/assets/{provider-models-bz5y28rq.js → provider-models-DJ29qHuA.js} +1 -1
  37. package/dist/assets/{react-7ZHqQtEV.js → react-C3yu5yge.js} +1 -1
  38. package/dist/assets/{refresh-ccw-CC6-_QuL.js → refresh-ccw-BAJf-h7w.js} +1 -1
  39. package/dist/assets/{save-DJM5RRWW.js → save-aa6z4GJL.js} +1 -1
  40. package/dist/assets/search-pD6ZwQYF.js +1 -0
  41. package/dist/assets/{security-config-DbUyWcQz.js → security-config-DRDxrApx.js} +1 -1
  42. package/dist/assets/{select-DSkTc61S.js → select-BHJPiJWt.js} +1 -1
  43. package/dist/assets/skeleton-D6kCk9Y6.js +1 -0
  44. package/dist/assets/{status-dot-LNBlDu3q.js → status-dot-DUwsTIdv.js} +1 -1
  45. package/dist/assets/{switch-Bo-Y46HZ.js → switch-B6nCfcOB.js} +1 -1
  46. package/dist/assets/{tabs-custom-DXv507_2.js → tabs-custom-B57SMElx.js} +1 -1
  47. package/dist/assets/{trash-2-DFZmW6Gg.js → trash-2-CrjYH5ok.js} +1 -1
  48. package/dist/assets/{useConfirmDialog-COwYXDKm.js → useConfirmDialog-DsxnXB1B.js} +1 -1
  49. package/dist/assets/{useMutation-DrZrOgVL.js → useMutation-oTTWXgLG.js} +1 -1
  50. package/dist/assets/x-CTIQHUuD.js +1 -0
  51. package/dist/index.html +18 -18
  52. package/package.json +6 -6
  53. package/src/App.tsx +2 -0
  54. package/src/api/agents.ts +26 -0
  55. package/src/api/types.ts +23 -2
  56. package/src/components/agents/AgentsPage.test.tsx +70 -0
  57. package/src/components/agents/AgentsPage.tsx +353 -0
  58. package/src/components/chat/ChatConversationPanel.test.tsx +141 -13
  59. package/src/components/chat/ChatConversationPanel.tsx +29 -7
  60. package/src/components/chat/ChatSidebar.test.tsx +8 -0
  61. package/src/components/chat/ChatSidebar.tsx +11 -0
  62. package/src/components/chat/ChatWelcome.test.tsx +25 -0
  63. package/src/components/chat/ChatWelcome.tsx +47 -1
  64. package/src/components/chat/adapters/chat-message-part.adapter.ts +5 -0
  65. package/src/components/chat/adapters/chat-message-tool-agent-id.test.ts +102 -0
  66. package/src/components/chat/adapters/chat-message-tool-agent-id.ts +47 -0
  67. package/src/components/chat/adapters/chat-message.adapter.test.ts +6 -0
  68. package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +24 -15
  69. package/src/components/chat/chat-child-session-panel.tsx +115 -49
  70. package/src/components/chat/chat-page-shell.tsx +8 -17
  71. package/src/components/chat/chat-session-route.ts +0 -14
  72. package/src/components/chat/chat-sidebar-session-item.tsx +16 -1
  73. package/src/components/chat/containers/chat-input-bar.container.tsx +2 -1
  74. package/src/components/chat/containers/chat-message-list.container.tsx +7 -0
  75. package/src/components/chat/ncp/NcpChatPage.tsx +58 -160
  76. package/src/components/chat/ncp/README.md +3 -0
  77. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +2 -0
  78. package/src/components/chat/ncp/ncp-chat-thread.manager.ts +66 -10
  79. package/src/components/chat/ncp/ncp-session-adapter.test.ts +2 -0
  80. package/src/components/chat/ncp/ncp-session-adapter.ts +1 -0
  81. package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +128 -0
  82. package/src/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view.ts +52 -0
  83. package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.test.tsx +101 -0
  84. package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.ts +72 -0
  85. package/src/components/chat/presenter/chat-presenter-context.tsx +1 -0
  86. package/src/components/chat/stores/chat-thread.store.ts +20 -6
  87. package/src/components/common/AgentAvatar.tsx +63 -0
  88. package/src/components/common/agent-identity/agent-identity-avatar.tsx +27 -0
  89. package/src/components/common/agent-identity/index.ts +3 -0
  90. package/src/components/common/agent-identity/use-agent-identity.ts +50 -0
  91. package/src/components/config/RuntimeConfig.tsx +13 -79
  92. package/src/components/config/runtime-config-agent.utils.ts +95 -0
  93. package/src/components/layout/AppLayout.tsx +3 -1
  94. package/src/components/layout/Sidebar.tsx +6 -1
  95. package/src/components/layout/app-layout.test.tsx +30 -0
  96. package/src/components/ui/tabs.tsx +2 -0
  97. package/src/hooks/README.md +3 -0
  98. package/src/hooks/agents/useAgents.ts +44 -0
  99. package/src/lib/i18n.agents.ts +66 -0
  100. package/src/lib/i18n.chat.ts +5 -0
  101. package/src/lib/i18n.ts +4 -4
  102. package/src/lib/ui-document-title.ts +1 -0
  103. package/dist/assets/ChatPage-Z9tRzm_n.js +0 -43
  104. package/dist/assets/DocBrowser-B9OaZjmg.js +0 -1
  105. package/dist/assets/MarketplacePage-D6rVQEQR.js +0 -1
  106. package/dist/assets/RuntimeConfig-v7a7Fe3x.js +0 -1
  107. package/dist/assets/chat-session-display-D0WpnuRZ.js +0 -1
  108. package/dist/assets/i18n-CDHMXlRZ.js +0 -1
  109. package/dist/assets/index-BuwbBgmT.js +0 -6
  110. package/dist/assets/index-bZ8cqQIS.css +0 -1
  111. package/dist/assets/loader-circle-Cs8XVFTw.js +0 -1
  112. package/dist/assets/plus-PHf8q-Ct.js +0 -1
  113. package/dist/assets/search-C91yH_6y.js +0 -1
  114. package/dist/assets/skeleton-Dzg-HOiN.js +0 -1
  115. package/dist/assets/x-D7Q1yqSF.js +0 -1
  116. /package/src/lib/{i18n → i18n-runtime}/i18n-language-owner.ts +0 -0
  117. /package/src/lib/{i18n → i18n-runtime}/i18n.path-picker.ts +0 -0
@@ -1,38 +1,88 @@
1
- import { useMemo } from 'react';
2
- import { useQuery } from '@tanstack/react-query';
3
1
  import { ArrowLeft, Loader2, X } from 'lucide-react';
4
- import { fetchNcpSessionMessages } from '@/api/ncp-session';
5
2
  import { ChatMessageListContainer } from '@/components/chat/containers/chat-message-list.container';
6
- import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
3
+ import { useNcpChildSessionTabsView } from '@/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view';
4
+ import { useNcpSessionConversation } from '@/components/chat/ncp/session-conversation/use-ncp-session-conversation';
5
+ import type { ChatChildSessionTab } from '@/components/chat/stores/chat-thread.store';
6
+ import { AgentIdentityAvatar } from '@/components/common/agent-identity';
7
+ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
8
+ import { t } from '@/lib/i18n';
7
9
  import { cn } from '@/lib/utils';
8
10
  import type { ChatToolActionViewModel } from '@nextclaw/agent-chat-ui';
9
11
 
10
12
  type ChatChildSessionPanelProps = {
11
- sessionKey: string;
12
- title?: string | null;
13
+ tabs: readonly ChatChildSessionTab[];
14
+ activeSessionKey: string;
15
+ onSelectSession: (sessionKey: string) => void;
13
16
  onClose: () => void;
14
17
  onBackToParent: () => void;
15
18
  onToolAction?: (action: ChatToolActionViewModel) => void;
16
19
  };
17
20
 
18
- export function ChatChildSessionPanel({
21
+ function ChildSessionPanelConversation({
19
22
  sessionKey,
20
- title,
23
+ onToolAction,
24
+ }: {
25
+ sessionKey: string;
26
+ onToolAction?: (action: ChatToolActionViewModel) => void;
27
+ }) {
28
+ const agent = useNcpSessionConversation(sessionKey);
29
+ const messages = agent.visibleMessages;
30
+
31
+ if (agent.isHydrating) {
32
+ return (
33
+ <div className="flex h-full items-center justify-center text-sm text-gray-500">
34
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
35
+ {t('chatChildSessionLoading')}
36
+ </div>
37
+ );
38
+ }
39
+
40
+ if (agent.hydrateError) {
41
+ return (
42
+ <div className="px-4 py-5 text-sm text-rose-600">
43
+ {agent.hydrateError.message}
44
+ </div>
45
+ );
46
+ }
47
+
48
+ if (messages.length === 0 && !agent.isRunning) {
49
+ return (
50
+ <div className="px-4 py-5 text-sm text-gray-500">
51
+ {t('chatChildSessionEmpty')}
52
+ </div>
53
+ );
54
+ }
55
+
56
+ return (
57
+ <div className="px-4 py-5">
58
+ <ChatMessageListContainer
59
+ messages={messages}
60
+ isSending={agent.isRunning}
61
+ onToolAction={onToolAction}
62
+ />
63
+ </div>
64
+ );
65
+ }
66
+
67
+ export function ChatChildSessionPanel({
68
+ tabs,
69
+ activeSessionKey,
70
+ onSelectSession,
21
71
  onClose,
22
72
  onBackToParent,
23
73
  onToolAction,
24
74
  }: ChatChildSessionPanelProps) {
25
- const detailParentSessionKey = useChatThreadStore(
26
- (state) => state.snapshot.childSessionDetailParentSessionKey,
27
- );
28
- const query = useQuery({
29
- queryKey: ['ncp-session-messages', sessionKey, 'child-panel'],
30
- queryFn: () => fetchNcpSessionMessages(sessionKey, 300),
31
- staleTime: 5_000,
32
- });
75
+ const resolvedTabs = useNcpChildSessionTabsView(tabs);
76
+ const activeTab =
77
+ resolvedTabs.find((tab) => tab.sessionKey === activeSessionKey) ??
78
+ resolvedTabs[0] ??
79
+ null;
80
+ const hasParentSession = resolvedTabs.some((tab) => Boolean(tab.parentSessionKey));
81
+ const shouldShowTabs = resolvedTabs.length > 1;
33
82
 
34
- const messages = query.data?.messages ?? [];
35
- const headerTitle = useMemo(() => title?.trim() || sessionKey, [sessionKey, title]);
83
+ if (!activeTab) {
84
+ return null;
85
+ }
36
86
 
37
87
  return (
38
88
  <aside className="hidden md:flex md:w-[24rem] lg:w-[28rem] shrink-0 border-l border-gray-200/70 bg-white/90 backdrop-blur-sm">
@@ -43,56 +93,72 @@ export function ChatChildSessionPanel({
43
93
  type="button"
44
94
  onClick={onBackToParent}
45
95
  className={cn(
46
- 'inline-flex items-center gap-1 text-xs font-medium text-gray-600 hover:text-gray-900',
47
- !detailParentSessionKey && 'pointer-events-none opacity-0',
96
+ 'inline-flex items-center gap-1 text-xs font-medium text-gray-600 transition-colors hover:text-gray-900',
97
+ !hasParentSession && 'pointer-events-none opacity-0',
48
98
  )}
49
99
  >
50
100
  <ArrowLeft className="h-3.5 w-3.5" />
51
- <span>Back to parent</span>
101
+ <span>{t('chatBackToParent')}</span>
52
102
  </button>
53
103
  <button
54
104
  type="button"
55
105
  onClick={onClose}
56
- className="rounded-md border border-gray-200 p-1 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
57
- aria-label="Close child session panel"
106
+ className="rounded-full border border-gray-200/80 p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
107
+ aria-label={t('chatChildSessionClosePanel')}
58
108
  >
59
109
  <X className="h-4 w-4" />
60
110
  </button>
61
111
  </div>
62
- <div className="mt-2">
63
- <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-gray-400">
64
- Child Session
112
+ {!shouldShowTabs ? (
113
+ <div className="mt-3 flex min-w-0 items-center gap-2 text-sm font-semibold text-gray-900">
114
+ {activeTab.agentId ? (
115
+ <AgentIdentityAvatar
116
+ agentId={activeTab.agentId}
117
+ className="h-5 w-5 shrink-0"
118
+ />
119
+ ) : null}
120
+ <span className="truncate" title={activeTab.sessionKey}>
121
+ {activeTab.title}
122
+ </span>
65
123
  </div>
66
- <div className="mt-1 text-sm font-semibold text-gray-900">
67
- {headerTitle}
124
+ ) : null}
125
+ {shouldShowTabs ? (
126
+ <div className="mt-3 overflow-x-auto custom-scrollbar">
127
+ <Tabs value={activeSessionKey} onValueChange={onSelectSession}>
128
+ <TabsList className="h-auto min-w-max justify-start gap-1.5 rounded-none bg-transparent p-0 text-gray-500">
129
+ {resolvedTabs.map((tab) => (
130
+ <TabsTrigger
131
+ key={tab.sessionKey}
132
+ value={tab.sessionKey}
133
+ className="gap-2 rounded-full border border-gray-200/80 bg-white/85 px-2.5 py-1.5 text-xs font-medium text-gray-600 shadow-none hover:border-primary/30 hover:text-primary data-[state=active]:border-primary/30 data-[state=active]:bg-primary-50/70 data-[state=active]:text-primary data-[state=active]:shadow-sm"
134
+ >
135
+ {tab.agentId ? (
136
+ <AgentIdentityAvatar
137
+ agentId={tab.agentId}
138
+ className="h-4 w-4 shrink-0"
139
+ />
140
+ ) : null}
141
+ <span className="max-w-[132px] truncate">{tab.title}</span>
142
+ </TabsTrigger>
143
+ ))}
144
+ </TabsList>
145
+ </Tabs>
68
146
  </div>
69
- <div className="mt-1 text-[11px] text-gray-500">{sessionKey}</div>
70
- </div>
147
+ ) : null}
71
148
  </div>
72
149
 
73
150
  <div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar">
74
- {query.isLoading ? (
75
- <div className="flex h-full items-center justify-center text-sm text-gray-500">
76
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
77
- Loading child session
78
- </div>
79
- ) : query.isError ? (
80
- <div className="px-4 py-5 text-sm text-rose-600">
81
- {(query.error as Error).message}
82
- </div>
83
- ) : messages.length === 0 ? (
84
- <div className="px-4 py-5 text-sm text-gray-500">
85
- No child session messages yet.
86
- </div>
87
- ) : (
88
- <div className="px-4 py-5">
89
- <ChatMessageListContainer
90
- messages={messages}
91
- isSending={false}
151
+ {resolvedTabs.map((tab) => (
152
+ <div
153
+ key={tab.sessionKey}
154
+ className={cn(tab.sessionKey === activeSessionKey ? 'block' : 'hidden')}
155
+ >
156
+ <ChildSessionPanelConversation
157
+ sessionKey={tab.sessionKey}
92
158
  onToolAction={onToolAction}
93
159
  />
94
160
  </div>
95
- )}
161
+ ))}
96
162
  </div>
97
163
  </div>
98
164
  </aside>
@@ -2,10 +2,11 @@ import { useEffect } from 'react';
2
2
  import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
3
3
  import { ChatSidebar } from '@/components/chat/ChatSidebar';
4
4
  import { ChatConversationPanel } from '@/components/chat/ChatConversationPanel';
5
+ import { AgentsPage } from '@/components/agents/AgentsPage';
5
6
  import { CronConfig } from '@/components/config/CronConfig';
6
7
  import { MarketplacePage } from '@/components/marketplace/MarketplacePage';
7
8
 
8
- export type MainPanelView = 'chat' | 'cron' | 'skills';
9
+ export type MainPanelView = 'chat' | 'cron' | 'skills' | 'agents';
9
10
 
10
11
  export type ChatPageProps = {
11
12
  view: MainPanelView;
@@ -15,12 +16,9 @@ type UseChatSessionSyncParams = {
15
16
  view: MainPanelView;
16
17
  routeSessionKey: string | null;
17
18
  selectedSessionKey: string | null;
18
- selectedAgentId: string;
19
19
  setSelectedSessionKey: Dispatch<SetStateAction<string | null>>;
20
- setSelectedAgentId: Dispatch<SetStateAction<string>>;
21
20
  selectedSessionKeyRef: MutableRefObject<string | null>;
22
21
  resetStreamState: () => void;
23
- resolveAgentIdFromSessionKey: (sessionKey: string) => string | null;
24
22
  };
25
23
 
26
24
  export function useChatSessionSync(params: UseChatSessionSyncParams): void {
@@ -28,12 +26,9 @@ export function useChatSessionSync(params: UseChatSessionSyncParams): void {
28
26
  view,
29
27
  routeSessionKey,
30
28
  selectedSessionKey,
31
- selectedAgentId,
32
29
  setSelectedSessionKey,
33
- setSelectedAgentId,
34
30
  selectedSessionKeyRef,
35
31
  resetStreamState,
36
- resolveAgentIdFromSessionKey
37
32
  } = params;
38
33
 
39
34
  useEffect(() => {
@@ -52,16 +47,6 @@ export function useChatSessionSync(params: UseChatSessionSyncParams): void {
52
47
  }
53
48
  }, [resetStreamState, routeSessionKey, selectedSessionKey, setSelectedSessionKey, view]);
54
49
 
55
- useEffect(() => {
56
- const inferred = selectedSessionKey ? resolveAgentIdFromSessionKey(selectedSessionKey) : null;
57
- if (!inferred) {
58
- return;
59
- }
60
- if (selectedAgentId !== inferred) {
61
- setSelectedAgentId(inferred);
62
- }
63
- }, [resolveAgentIdFromSessionKey, selectedAgentId, selectedSessionKey, setSelectedAgentId]);
64
-
65
50
  useEffect(() => {
66
51
  selectedSessionKeyRef.current = selectedSessionKey;
67
52
  }, [selectedSessionKey, selectedSessionKeyRef]);
@@ -87,6 +72,12 @@ export function ChatPageLayout({ view, confirmDialog }: ChatPageLayoutProps) {
87
72
  <CronConfig />
88
73
  </div>
89
74
  </div>
75
+ ) : view === 'agents' ? (
76
+ <div className="h-full overflow-auto custom-scrollbar">
77
+ <div className="mx-auto w-full max-w-[min(1180px,100%)] px-6 py-5">
78
+ <AgentsPage />
79
+ </div>
80
+ </div>
90
81
  ) : (
91
82
  <div className="h-full overflow-hidden">
92
83
  <div className="mx-auto flex h-full min-h-0 w-full max-w-[min(1120px,100%)] flex-col px-6 py-5">
@@ -1,19 +1,5 @@
1
1
  const SESSION_ROUTE_PREFIX = 'sid_';
2
2
 
3
- export function resolveAgentIdFromSessionKey(sessionKey: string): string | null {
4
- const match = /^agent:([^:]+):/i.exec(sessionKey.trim());
5
- if (!match) {
6
- return null;
7
- }
8
- const value = match[1]?.trim();
9
- return value ? value : null;
10
- }
11
-
12
- export function buildNewSessionKey(agentId: string): string {
13
- const slug = Math.random().toString(36).slice(2, 8);
14
- return `agent:${agentId}:ui:direct:web-${Date.now().toString(36)}${slug}`;
15
- }
16
-
17
3
  export function encodeSessionRouteId(sessionKey: string): string {
18
4
  const bytes = new TextEncoder().encode(sessionKey);
19
5
  let binary = '';
@@ -1,4 +1,5 @@
1
1
  import type { SessionEntryView } from '@/api/types';
2
+ import { AgentAvatar } from '@/components/common/AgentAvatar';
2
3
  import { SessionContextIconNode } from '@/components/common/session-context-icon';
3
4
  import { SessionRunBadge } from '@/components/common/SessionRunBadge';
4
5
  import { Button } from '@/components/ui/button';
@@ -15,6 +16,9 @@ type ChatSidebarSessionItemProps = {
15
16
  runStatus?: SessionRunStatus;
16
17
  context: SessionContextView;
17
18
  title: string;
19
+ agentId?: string | null;
20
+ agentLabel?: string | null;
21
+ agentAvatarUrl?: string | null;
18
22
  isEditing: boolean;
19
23
  draftLabel: string;
20
24
  isSaving: boolean;
@@ -32,6 +36,9 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
32
36
  runStatus,
33
37
  context,
34
38
  title,
39
+ agentId,
40
+ agentLabel,
41
+ agentAvatarUrl,
35
42
  isEditing,
36
43
  draftLabel,
37
44
  isSaving,
@@ -105,6 +112,14 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
105
112
  <button type="button" onClick={onSelect} className="w-full text-left">
106
113
  <div className="grid grid-cols-[minmax(0,1fr)_0.875rem] items-center gap-1.5 pr-8">
107
114
  <span className="flex min-w-0 items-center gap-1.5">
115
+ {agentId ? (
116
+ <AgentAvatar
117
+ agentId={agentId}
118
+ displayName={agentLabel}
119
+ avatarUrl={agentAvatarUrl}
120
+ className="h-5 w-5 shrink-0"
121
+ />
122
+ ) : null}
108
123
  <span className="truncate font-medium">{title}</span>
109
124
  {context.label ? (
110
125
  <span
@@ -129,7 +144,7 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
129
144
  </span>
130
145
  </div>
131
146
  <div className="mt-0.5 text-[11px] text-gray-400 truncate">
132
- {session.messageCount} · {formatDateTime(session.updatedAt)}
147
+ {agentLabel?.trim() ? `${agentLabel} · ` : ''}{session.messageCount} · {formatDateTime(session.updatedAt)}
133
148
  </div>
134
149
  </button>
135
150
  <button
@@ -87,8 +87,9 @@ export function ChatInputBarContainer() {
87
87
  const inputBarRef = useRef<ChatInputBarHandle | null>(null);
88
88
  const fileInputRef = useRef<HTMLInputElement | null>(null);
89
89
 
90
- const skillScopeLabels = useMemo<Record<'project' | 'workspace', string>>(() => {
90
+ const skillScopeLabels = useMemo<Record<'builtin' | 'project' | 'workspace', string>>(() => {
91
91
  return {
92
+ builtin: t('chatSkillScopeBuiltin'),
92
93
  project: t('chatSkillScopeProject'),
93
94
  workspace: t('chatSkillScopeWorkspace'),
94
95
  };
@@ -12,6 +12,7 @@ import {
12
12
  } from "@/components/chat/adapters/chat-message.adapter";
13
13
  import { readInlineTokensFromMetadata } from "@/components/chat/chat-inline-token.utils";
14
14
  import { adaptNcpMessageToUiMessage } from "@/components/chat/ncp/ncp-session-adapter";
15
+ import { AgentIdentityAvatar } from "@/components/common/agent-identity";
15
16
  import { useI18n } from "@/components/providers/I18nProvider";
16
17
  import { formatDateTime, t } from "@/lib/i18n";
17
18
 
@@ -129,6 +130,12 @@ export function ChatMessageListContainer({
129
130
  className={className}
130
131
  texts={messageTexts}
131
132
  onToolAction={onToolAction}
133
+ renderToolAgent={(agentId) => (
134
+ <AgentIdentityAvatar
135
+ agentId={agentId}
136
+ className="h-4 w-4 shrink-0"
137
+ />
138
+ )}
132
139
  />
133
140
  );
134
141
  }