@nextclaw/ui 0.12.4 → 0.12.6

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 (149) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/assets/ChannelsList-D8p4OlM6.js +8 -0
  3. package/dist/assets/ChatPage-A45t1Rmf.js +58 -0
  4. package/dist/assets/DocBrowser-B2MpsnU9.js +1 -0
  5. package/dist/assets/{DocBrowser-NSzgVKka.js → DocBrowser-Cse_F8Nn.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-DpgVdRgk.js → DocBrowserContext-Bai1WU2H.js} +1 -1
  7. package/dist/assets/{LogoBadge-CHS4YNLw.js → LogoBadge-BdxMPc9v.js} +1 -1
  8. package/dist/assets/MarketplacePage-BNZ3Jx5d.js +1 -0
  9. package/dist/assets/MarketplacePage-BbpAkllU.js +49 -0
  10. package/dist/assets/McpMarketplacePage-CxPFOgxv.js +40 -0
  11. package/dist/assets/ModelConfig-3GLqQ5GY.js +1 -0
  12. package/dist/assets/ProviderScopedModelInput-BYNouw-i.js +1 -0
  13. package/dist/assets/ProvidersList-BR1gJ4Dm.js +1 -0
  14. package/dist/assets/{RemoteAccessPage-yfbrveNQ.js → RemoteAccessPage-DyYVWsyK.js} +1 -1
  15. package/dist/assets/RuntimeConfig-ChdfK4Y_.js +1 -0
  16. package/dist/assets/SearchConfig-DTeJvp8m.js +1 -0
  17. package/dist/assets/{SecretsConfig-CLFSSoTl.js → SecretsConfig-CCYO6NcV.js} +2 -2
  18. package/dist/assets/SessionsConfig-Du39vDgt.js +2 -0
  19. package/dist/assets/app-query-client-Dr5d-K8d.js +1 -0
  20. package/dist/assets/{book-open-C7TAghTk.js → book-open-Da4OEPqB.js} +1 -1
  21. package/dist/assets/chat-session-display-CAlPrnlV.js +1 -0
  22. package/dist/assets/{chunk-JZWAC4HX-DbL4EmiT.js → chunk-JZWAC4HX-CoFVxHXV.js} +1 -1
  23. package/dist/assets/client-CSk58DcF.js +7 -0
  24. package/dist/assets/config-D8KzikVB.js +1 -0
  25. package/dist/assets/{createLucideIcon-BRLFtf-8.js → createLucideIcon-83gaZMtv.js} +1 -1
  26. package/dist/assets/desktop-update-config-CfoVwf-w.js +1 -0
  27. package/dist/assets/dist-aTmhMDVh.js +9 -0
  28. package/dist/assets/{dist-DP-JKR4G.js → dist-toEYs-MZ.js} +1 -1
  29. package/dist/assets/{external-link-BkJkiWbH.js → external-link-QQ0TC6X4.js} +1 -1
  30. package/dist/assets/{hash-CbP6-6R9.js → hash-DaFBEkmi.js} +1 -1
  31. package/dist/assets/i18n-C3jb83S6.js +1 -0
  32. package/dist/assets/index-CE4N7ItL.css +1 -0
  33. package/dist/assets/index-riX7Sg0_.js +6 -0
  34. package/dist/assets/infiniteQueryBehavior-BmHX_ayZ.js +1 -0
  35. package/dist/assets/loader-circle-BjMg63eu.js +1 -0
  36. package/dist/assets/{logos-N3dbS6-I.js → logos-Dzlz30M3.js} +1 -1
  37. package/dist/assets/{page-layout-DyuvlNrg.js → page-layout-D2eRufRQ.js} +1 -1
  38. package/dist/assets/plus-CIXME2pD.js +1 -0
  39. package/dist/assets/{popover-BKKWGUaG.js → popover-BSXxm5bj.js} +1 -1
  40. package/dist/assets/{refresh-ccw-BGMdiNGq.js → refresh-ccw-B3zMtN-_.js} +1 -1
  41. package/dist/assets/refresh-cw-DlZkIHnJ.js +1 -0
  42. package/dist/assets/{save-Dh4GQzzX.js → save-Us9fg4Sj.js} +1 -1
  43. package/dist/assets/search-B_Qr0f6C.js +1 -0
  44. package/dist/assets/security-config-BGWYwxNr.js +1 -0
  45. package/dist/assets/{select-BtIi5fnh.js → select-DLYqySQK.js} +1 -1
  46. package/dist/assets/skeleton-CYQJazv6.js +1 -0
  47. package/dist/assets/{status-dot-C4O-2jZP.js → status-dot-DGayudyB.js} +1 -1
  48. package/dist/assets/{switch-DPegGIa_.js → switch-Dz2ScsKx.js} +1 -1
  49. package/dist/assets/{tabs-custom-x5GZexrF.js → tabs-custom-CdKyjiGk.js} +1 -1
  50. package/dist/assets/{trash-2-CU3LYIpQ.js → trash-2-Db-mZOZs.js} +1 -1
  51. package/dist/assets/use-infinite-scroll-loader-DBJX5hj0.js +1 -0
  52. package/dist/assets/{useConfirmDialog-S5WsGOGf.js → useConfirmDialog-DL0a-oGC.js} +1 -1
  53. package/dist/assets/useMutation-BdZm-9PL.js +1 -0
  54. package/dist/assets/x-B8Tho_xC.js +1 -0
  55. package/dist/index.html +20 -18
  56. package/package.json +6 -6
  57. package/src/App.tsx +2 -0
  58. package/src/account/components/account-panel.tsx +46 -4
  59. package/src/account/managers/account.manager.ts +19 -4
  60. package/src/api/raw-client.test.ts +37 -0
  61. package/src/api/raw-client.ts +51 -8
  62. package/src/api/remote.ts +9 -0
  63. package/src/api/remote.types.ts +5 -0
  64. package/src/components/chat/ChatConversationPanel.test.tsx +344 -142
  65. package/src/components/chat/ChatSidebar.test.tsx +109 -4
  66. package/src/components/chat/ChatSidebar.tsx +62 -9
  67. package/src/components/chat/adapters/chat-message-tool-agent-id.test.ts +11 -11
  68. package/src/components/chat/adapters/chat-message.adapter.test.ts +43 -6
  69. package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +182 -44
  70. package/src/components/chat/adapters/chat-message.session-spawn-tool-card.test.ts +104 -0
  71. package/src/components/chat/chat-child-session-panel.tsx +155 -59
  72. package/src/components/chat/chat-page-runtime.test.ts +16 -19
  73. package/src/components/chat/chat-session-preference-sync.test.ts +13 -0
  74. package/src/components/chat/chat-session-preference-sync.ts +9 -7
  75. package/src/components/chat/chat-sidebar-session-item.tsx +189 -121
  76. package/src/components/chat/containers/chat-message-list.container.test.tsx +21 -3
  77. package/src/components/chat/containers/chat-message-list.container.tsx +14 -0
  78. package/src/components/chat/hooks/use-chat-session-project.test.tsx +5 -5
  79. package/src/components/chat/hooks/use-chat-session-project.ts +0 -5
  80. package/src/components/chat/hooks/use-chat-session-update.test.tsx +75 -0
  81. package/src/components/chat/hooks/use-chat-session-update.ts +4 -2
  82. package/src/components/chat/managers/chat-session-list.manager.test.ts +79 -5
  83. package/src/components/chat/managers/chat-session-list.manager.ts +31 -4
  84. package/src/components/chat/ncp/NcpChatPage.tsx +32 -51
  85. package/src/components/chat/ncp/ncp-app-client-fetch.test.ts +1 -1
  86. package/src/components/chat/ncp/ncp-app-client-fetch.ts +45 -5
  87. package/src/components/chat/ncp/ncp-chat-input.manager.ts +3 -5
  88. package/src/components/chat/ncp/ncp-chat-page-data.ts +0 -1
  89. package/src/components/chat/ncp/ncp-chat.presenter.ts +1 -11
  90. package/src/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view.ts +35 -9
  91. package/src/components/chat/stores/chat-session-list.store.ts +99 -5
  92. package/src/components/chat/useChatSessionTypeState.test.tsx +0 -3
  93. package/src/components/chat/useChatSessionTypeState.ts +3 -5
  94. package/src/components/config/ChannelsList.test.tsx +68 -0
  95. package/src/components/config/ChannelsList.tsx +22 -4
  96. package/src/components/config/ProviderForm.tsx +9 -15
  97. package/src/components/config/ProvidersList.tsx +17 -3
  98. package/src/components/config/desktop-update-config.tsx +230 -0
  99. package/src/components/config/providers-list.test.tsx +68 -0
  100. package/src/components/layout/Sidebar.tsx +19 -14
  101. package/src/components/layout/sidebar.layout.test.tsx +33 -1
  102. package/src/components/marketplace/MarketplacePage.tsx +30 -30
  103. package/src/components/marketplace/marketplace-page-parts.tsx +16 -24
  104. package/src/components/marketplace/mcp/McpMarketplacePage.tsx +28 -26
  105. package/src/desktop/desktop-update.types.ts +36 -0
  106. package/src/desktop/managers/desktop-update.manager.ts +163 -0
  107. package/src/desktop/stores/desktop-update.store.ts +18 -0
  108. package/src/hooks/marketplace-list-pages.ts +27 -0
  109. package/src/hooks/use-infinite-scroll-loader.ts +88 -0
  110. package/src/hooks/useMarketplace.ts +14 -3
  111. package/src/hooks/useMcpMarketplace.ts +14 -3
  112. package/src/lib/desktop-update-labels.utils.ts +72 -0
  113. package/src/lib/i18n.chat.ts +13 -0
  114. package/src/lib/i18n.remote.ts +15 -0
  115. package/src/lib/i18n.ts +3 -9
  116. package/src/lib/ui-document-title.ts +1 -0
  117. package/src/transport/local.transport.ts +57 -18
  118. package/src/vite-env.d.ts +10 -0
  119. package/dist/assets/ChannelsList-CobWeI2V.js +0 -8
  120. package/dist/assets/ChatPage-ZIdFFVAv.js +0 -43
  121. package/dist/assets/DocBrowser-D55C0iyl.js +0 -1
  122. package/dist/assets/MarketplacePage-BFYsRss_.js +0 -49
  123. package/dist/assets/MarketplacePage-DII-q-Y1.js +0 -1
  124. package/dist/assets/McpMarketplacePage-CPqsGJzz.js +0 -40
  125. package/dist/assets/ModelConfig-Bvuo_IpS.js +0 -1
  126. package/dist/assets/ProviderScopedModelInput-BfY8rGsf.js +0 -1
  127. package/dist/assets/ProvidersList-3tlaqwSS.js +0 -1
  128. package/dist/assets/RuntimeConfig-CAd5Kta3.js +0 -1
  129. package/dist/assets/SearchConfig-DFwgaAa7.js +0 -1
  130. package/dist/assets/SessionsConfig-vYrvc2Fk.js +0 -2
  131. package/dist/assets/chat-session-display-5dVFkJyw.js +0 -1
  132. package/dist/assets/config-CMiW0yaK.js +0 -1
  133. package/dist/assets/dist-BFc_H-lY.js +0 -15
  134. package/dist/assets/i18n-C_2dKw6w.js +0 -1
  135. package/dist/assets/index-ChUXhq0G.css +0 -1
  136. package/dist/assets/index-DAE8Srx-.js +0 -6
  137. package/dist/assets/label-D8yyejJS.js +0 -1
  138. package/dist/assets/loader-circle-B0sKKO29.js +0 -1
  139. package/dist/assets/marketplace-localization-CxSTG9wr.js +0 -1
  140. package/dist/assets/plus-CYXs3JtZ.js +0 -1
  141. package/dist/assets/react-8EIEQjMP.js +0 -1
  142. package/dist/assets/search-DOsLw-P9.js +0 -1
  143. package/dist/assets/security-config-CM_tQRXQ.js +0 -1
  144. package/dist/assets/skeleton-GbHLjPC0.js +0 -1
  145. package/dist/assets/useMutation-DSinpgEq.js +0 -1
  146. package/dist/assets/x-Bnco_K8b.js +0 -1
  147. package/src/components/chat/ChatSessionsSidebar.tsx +0 -100
  148. /package/dist/assets/{config-hints-WtpHP_DW.js → config-hints-GSUMvmSo.js} +0 -0
  149. /package/dist/assets/{config-layout-LQ10ozRC.js → config-layout-CgBMG7OL.js} +0 -0
@@ -18,8 +18,11 @@ const mocks = vi.hoisted(() => ({
18
18
  isLoading: false
19
19
  }));
20
20
 
21
- function createSessionItem(session: NcpSessionListItemView['session']): NcpSessionListItemView {
22
- return { session };
21
+ function createSessionItem(
22
+ session: NcpSessionListItemView['session'],
23
+ runStatus?: NcpSessionListItemView['runStatus'],
24
+ ): NcpSessionListItemView {
25
+ return { session, runStatus };
23
26
  }
24
27
 
25
28
  vi.mock('@/components/chat/presenter/chat-presenter-context', () => ({
@@ -28,7 +31,12 @@ vi.mock('@/components/chat/presenter/chat-presenter-context', () => ({
28
31
  createSession: mocks.createSession,
29
32
  setQuery: mocks.setQuery,
30
33
  setListMode: mocks.setListMode,
31
- selectSession: mocks.selectSession
34
+ selectSession: mocks.selectSession,
35
+ markSessionRead: (sessionKey: string | null | undefined, updatedAt: string | null | undefined) =>
36
+ sessionKey ? useChatSessionListStore.getState().markSessionRead(sessionKey, updatedAt) : undefined,
37
+ hydrateReadWatermarks: (
38
+ entries: readonly { sessionKey: string; updatedAt: string | null | undefined }[],
39
+ ) => useChatSessionListStore.getState().hydrateReadWatermarks(entries)
32
40
  }
33
41
  })
34
42
  }));
@@ -124,10 +132,13 @@ function resetSidebarTestState() {
124
132
  }
125
133
  });
126
134
  useChatSessionListStore.setState({
135
+ readUpdatedAtBySessionKey: {},
136
+ hasHydratedReadWatermarks: false,
127
137
  snapshot: {
128
138
  ...useChatSessionListStore.getState().snapshot,
129
139
  query: '',
130
- listMode: 'time-first'
140
+ listMode: 'time-first',
141
+ selectedSessionKey: null
131
142
  }
132
143
  });
133
144
  }
@@ -496,4 +507,98 @@ describe('ChatSidebar session item interactions', () => {
496
507
  expect(screen.queryByDisplayValue('Should Not Persist')).toBeNull();
497
508
  expect(screen.getByText('Cancelable Label')).not.toBeNull();
498
509
  });
510
+
511
+ it('shows an unread dot only after a non-active session finishes its newer update', () => {
512
+ mocks.sessionItems = [
513
+ createSessionItem({
514
+ key: 'session:ncp-1',
515
+ createdAt: '2026-03-19T09:00:00.000Z',
516
+ updatedAt: '2026-03-19T09:05:00.000Z',
517
+ label: 'Current Task',
518
+ sessionType: 'native',
519
+ sessionTypeMutable: false,
520
+ messageCount: 1
521
+ }),
522
+ createSessionItem({
523
+ key: 'session:ncp-2',
524
+ createdAt: '2026-03-19T10:00:00.000Z',
525
+ updatedAt: '2026-03-19T10:05:00.000Z',
526
+ label: 'Background Task',
527
+ sessionType: 'native',
528
+ sessionTypeMutable: false,
529
+ messageCount: 1
530
+ }, 'running')
531
+ ];
532
+ useChatSessionListStore.setState({
533
+ snapshot: {
534
+ ...useChatSessionListStore.getState().snapshot,
535
+ selectedSessionKey: 'session:ncp-1'
536
+ }
537
+ });
538
+
539
+ const { rerender } = render(
540
+ <MemoryRouter>
541
+ <ChatSidebar />
542
+ </MemoryRouter>
543
+ );
544
+
545
+ expect(screen.queryByLabelText('Session has unread updates')).toBeNull();
546
+
547
+ mocks.sessionItems = [
548
+ mocks.sessionItems[0]!,
549
+ createSessionItem({
550
+ key: 'session:ncp-2',
551
+ createdAt: '2026-03-19T10:00:00.000Z',
552
+ updatedAt: '2026-03-19T10:06:00.000Z',
553
+ label: 'Background Task',
554
+ sessionType: 'native',
555
+ sessionTypeMutable: false,
556
+ messageCount: 2
557
+ }, 'running')
558
+ ];
559
+
560
+ rerender(
561
+ <MemoryRouter>
562
+ <ChatSidebar />
563
+ </MemoryRouter>
564
+ );
565
+
566
+ expect(screen.queryByLabelText('Session has unread updates')).toBeNull();
567
+
568
+ mocks.sessionItems = [
569
+ mocks.sessionItems[0]!,
570
+ createSessionItem({
571
+ key: 'session:ncp-2',
572
+ createdAt: '2026-03-19T10:00:00.000Z',
573
+ updatedAt: '2026-03-19T10:06:00.000Z',
574
+ label: 'Background Task',
575
+ sessionType: 'native',
576
+ sessionTypeMutable: false,
577
+ messageCount: 2
578
+ })
579
+ ];
580
+
581
+ rerender(
582
+ <MemoryRouter>
583
+ <ChatSidebar />
584
+ </MemoryRouter>
585
+ );
586
+
587
+ expect(screen.getByLabelText('Session has unread updates')).toBeTruthy();
588
+
589
+ useChatSessionListStore.setState({
590
+ snapshot: {
591
+ ...useChatSessionListStore.getState().snapshot,
592
+ selectedSessionKey: 'session:ncp-2'
593
+ }
594
+ });
595
+
596
+ rerender(
597
+ <MemoryRouter>
598
+ <ChatSidebar />
599
+ </MemoryRouter>
600
+ );
601
+
602
+ expect(screen.queryByLabelText('Session has unread updates')).toBeNull();
603
+ });
499
604
  });
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'react';
1
+ import { useEffect, useMemo, useState } from 'react';
2
2
  import type { SessionEntryView } from '@/api/types';
3
3
  import { Button } from '@/components/ui/button';
4
4
  import { BrandHeader } from '@/components/common/BrandHeader';
@@ -17,7 +17,10 @@ import { useChatSessionLabel } from '@/components/chat/hooks/use-chat-session-la
17
17
  import { useNcpSessionListView, type NcpSessionListItemView } from '@/components/chat/ncp/use-ncp-session-list-view';
18
18
  import { usePresenter } from '@/components/chat/presenter/chat-presenter-context';
19
19
  import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
20
- import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
20
+ import {
21
+ shouldShowUnreadSessionIndicator,
22
+ useChatSessionListStore
23
+ } from '@/components/chat/stores/chat-session-list.store';
21
24
  import { useAgents } from '@/hooks/agents/useAgents';
22
25
  import { getSessionProjectName } from '@/lib/session-project/session-project.utils';
23
26
  import { cn } from '@/lib/utils';
@@ -143,6 +146,50 @@ const navItems = [
143
146
  { target: '/agents', label: () => t('agentsPageTitle'), icon: Bot },
144
147
  ];
145
148
 
149
+ function useChatSessionUnreadState(
150
+ items: readonly NcpSessionListItemView[],
151
+ selectedSessionKey: string | null,
152
+ markSessionRead: (sessionKey: string | null | undefined, updatedAt: string | null | undefined) => void,
153
+ hydrateReadWatermarks: (
154
+ entries: readonly { sessionKey: string; updatedAt: string | null | undefined }[],
155
+ ) => void,
156
+ ): Record<string, string> {
157
+ const readUpdatedAtBySessionKey = useChatSessionListStore((state) => state.readUpdatedAtBySessionKey);
158
+ const hasHydratedReadWatermarks = useChatSessionListStore((state) => state.hasHydratedReadWatermarks);
159
+
160
+ useEffect(() => {
161
+ const syncHydratedReadWatermarks = () => {
162
+ if (hasHydratedReadWatermarks || items.length === 0) {
163
+ return;
164
+ }
165
+ hydrateReadWatermarks(
166
+ items.map(({ session }) => ({
167
+ sessionKey: session.key,
168
+ updatedAt: session.updatedAt
169
+ }))
170
+ );
171
+ };
172
+ syncHydratedReadWatermarks();
173
+ }, [hasHydratedReadWatermarks, hydrateReadWatermarks, items]);
174
+
175
+ useEffect(() => {
176
+ const syncSelectedSessionReadState = () => {
177
+ if (!selectedSessionKey) {
178
+ return;
179
+ }
180
+ const selectedItem = items.find(({ session }) => session.key === selectedSessionKey);
181
+ if (!selectedItem) {
182
+ return;
183
+ }
184
+ const { session: selectedSession } = selectedItem;
185
+ markSessionRead(selectedSession.key, selectedSession.updatedAt);
186
+ };
187
+ syncSelectedSessionReadState();
188
+ }, [items, markSessionRead, selectedSessionKey]);
189
+
190
+ return readUpdatedAtBySessionKey;
191
+ }
192
+
146
193
  export function ChatSidebar() {
147
194
  const presenter = usePresenter();
148
195
  const docBrowser = useDocBrowser();
@@ -164,7 +211,6 @@ export function ChatSidebar() {
164
211
  () => new Map((agentsQuery.data?.agents ?? []).map((agent) => [agent.id, agent])),
165
212
  [agentsQuery.data?.agents]
166
213
  );
167
-
168
214
  const sortedItems = useMemo(() => sortSessionItemsByUpdatedAtDesc(items), [items]);
169
215
  const groups = useMemo(() => groupSessionsByDate(sortedItems), [sortedItems]);
170
216
  const projectGroups = useMemo(() => groupSessionsByProject(sortedItems), [sortedItems]);
@@ -174,24 +220,26 @@ export function ChatSidebar() {
174
220
  [defaultSessionType, inputSnapshot.sessionTypeOptions]
175
221
  );
176
222
  const isProjectFirstView = listSnapshot.listMode === 'project-first';
177
-
223
+ const readUpdatedAtBySessionKey = useChatSessionUnreadState(
224
+ items,
225
+ listSnapshot.selectedSessionKey,
226
+ presenter.chatSessionListManager.markSessionRead,
227
+ presenter.chatSessionListManager.hydrateReadWatermarks,
228
+ );
178
229
  const handleLanguageSwitch = (nextLang: I18nLanguage) => {
179
230
  if (language === nextLang) return;
180
231
  setLanguage(nextLang);
181
232
  window.location.reload();
182
233
  };
183
-
184
234
  const startEditingSessionLabel = (session: SessionEntryView) => {
185
235
  setEditingSessionKey(session.key);
186
236
  setDraftLabel(session.label?.trim() ?? '');
187
237
  };
188
-
189
238
  const cancelEditingSessionLabel = () => {
190
239
  setEditingSessionKey(null);
191
240
  setDraftLabel('');
192
241
  setSavingSessionKey(null);
193
242
  };
194
-
195
243
  const saveSessionLabel = async (session: SessionEntryView) => {
196
244
  const normalizedLabel = draftLabel.trim();
197
245
  const currentLabel = session.label?.trim() ?? '';
@@ -211,9 +259,14 @@ export function ChatSidebar() {
211
259
  setSavingSessionKey(null);
212
260
  }
213
261
  };
214
-
215
262
  const renderSessionItem = ({ session, runStatus }: NcpSessionListItemView) => {
216
263
  const active = listSnapshot.selectedSessionKey === session.key;
264
+ const showUnreadDot = shouldShowUnreadSessionIndicator({
265
+ active,
266
+ updatedAt: session.updatedAt,
267
+ readUpdatedAt: readUpdatedAtBySessionKey[session.key],
268
+ runStatus,
269
+ });
217
270
  const context = resolveSessionContextView(session, inputSnapshot.sessionTypeOptions);
218
271
  const isEditing = editingSessionKey === session.key;
219
272
  const isSaving = savingSessionKey === session.key;
@@ -222,6 +275,7 @@ export function ChatSidebar() {
222
275
  key={session.key}
223
276
  session={session}
224
277
  active={active}
278
+ showUnreadDot={showUnreadDot}
225
279
  runStatus={runStatus}
226
280
  context={context}
227
281
  title={sessionTitle(session)}
@@ -239,7 +293,6 @@ export function ChatSidebar() {
239
293
  />
240
294
  );
241
295
  };
242
-
243
296
  return (
244
297
  <aside className="w-[280px] shrink-0 flex flex-col h-full bg-secondary border-r border-gray-200/60">
245
298
  <div className="px-5 pt-5 pb-3">
@@ -33,19 +33,19 @@ function adapt(uiMessages: ChatMessageSource[]) {
33
33
  });
34
34
  }
35
35
 
36
- it("exposes agentId on spawn call cards when the invocation args include it", () => {
36
+ it("exposes agentId on sessions_spawn call cards when the invocation args include it", () => {
37
37
  const adapted = adapt([
38
38
  {
39
- id: "assistant-spawn-call",
39
+ id: "assistant-sessions-spawn-call",
40
40
  role: "assistant",
41
41
  parts: [
42
42
  {
43
43
  type: "tool-invocation",
44
44
  toolInvocation: {
45
45
  status: ToolInvocationStatus.PARTIAL_CALL,
46
- toolCallId: "spawn-call-args-1",
47
- toolName: "spawn",
48
- args: '{"agentId":"planner-agent","label":"Planner","task":"Plan the rollout"}',
46
+ toolCallId: "sessions-spawn-call-args-1",
47
+ toolName: "sessions_spawn",
48
+ args: '{"agentId":"planner-agent","scope":"child","title":"Planner","task":"Plan the rollout","request":{"notify":"final_reply"}}',
49
49
  result: {
50
50
  kind: "nextclaw.session_request",
51
51
  requestId: "request-3",
@@ -64,7 +64,7 @@ it("exposes agentId on spawn call cards when the invocation args include it", ()
64
64
  expect(adapted[0]?.parts[0]).toMatchObject({
65
65
  type: "tool-card",
66
66
  card: {
67
- toolName: "spawn",
67
+ toolName: "sessions_spawn",
68
68
  agentId: "planner-agent",
69
69
  statusTone: "running",
70
70
  },
@@ -74,16 +74,16 @@ it("exposes agentId on spawn call cards when the invocation args include it", ()
74
74
  it("exposes agentId on running tool call cards even before a session-request result exists", () => {
75
75
  const adapted = adapt([
76
76
  {
77
- id: "assistant-spawn-call-running",
77
+ id: "assistant-sessions-spawn-call-running",
78
78
  role: "assistant",
79
79
  parts: [
80
80
  {
81
81
  type: "tool-invocation",
82
82
  toolInvocation: {
83
83
  status: ToolInvocationStatus.PARTIAL_CALL,
84
- toolCallId: "spawn-call-running-1",
85
- toolName: "spawn",
86
- args: '{"agentId":"planner-agent","task":"Plan the rollout"}',
84
+ toolCallId: "sessions-spawn-call-running-1",
85
+ toolName: "sessions_spawn",
86
+ args: '{"agentId":"planner-agent","scope":"child","task":"Plan the rollout"}',
87
87
  },
88
88
  },
89
89
  ],
@@ -93,7 +93,7 @@ it("exposes agentId on running tool call cards even before a session-request res
93
93
  expect(adapted[0]?.parts[0]).toMatchObject({
94
94
  type: "tool-card",
95
95
  card: {
96
- toolName: "spawn",
96
+ toolName: "sessions_spawn",
97
97
  agentId: "planner-agent",
98
98
  statusTone: "running",
99
99
  titleLabel: "Tool Call",
@@ -223,7 +223,7 @@ it("keeps structured terminal results as structured data instead of raw json out
223
223
  });
224
224
  });
225
225
 
226
- it("renders session request tool cards from structured child-session status updates", () => {
226
+ it("renders child-session request cards for sessions_spawn when the new child starts immediately", () => {
227
227
  const adapted = adapt([
228
228
  {
229
229
  id: "assistant-subagent",
@@ -233,18 +233,21 @@ it("renders session request tool cards from structured child-session status upda
233
233
  type: "tool-invocation",
234
234
  toolInvocation: {
235
235
  status: ToolInvocationStatus.RESULT,
236
- toolCallId: "spawn-call-1",
237
- toolName: "spawn",
238
- args: '{"label":"Verifier","task":"Verify 1+1=2"}',
236
+ toolCallId: "sessions-spawn-call-1",
237
+ toolName: "sessions_spawn",
238
+ args: '{"scope":"child","title":"Verifier","task":"Verify 1+1=2","request":{"notify":"final_reply"}}',
239
239
  result: {
240
240
  kind: "nextclaw.session_request",
241
241
  requestId: "request-1",
242
242
  sessionId: "child-session-1",
243
243
  agentId: "verifier-agent",
244
244
  isChildSession: true,
245
+ lifecycle: "persistent",
245
246
  title: "Verifier",
246
247
  task: "Verify 1+1=2",
247
248
  status: "completed",
249
+ notify: "final_reply",
250
+ spawnedByRequestId: "request-1",
248
251
  finalResponseText: "Verified 1+1=2.",
249
252
  parentSessionId: "parent-session-1",
250
253
  },
@@ -257,9 +260,17 @@ it("renders session request tool cards from structured child-session status upda
257
260
  expect(adapted[0]?.parts[0]).toMatchObject({
258
261
  type: "tool-card",
259
262
  card: {
260
- toolName: "spawn",
263
+ toolName: "sessions_spawn",
261
264
  agentId: "verifier-agent",
262
265
  summary: "title: Verifier · session: child-session-1 · task: Verify 1+1=2",
266
+ input: `{
267
+ "scope": "child",
268
+ "title": "Verifier",
269
+ "task": "Verify 1+1=2",
270
+ "request": {
271
+ "notify": "final_reply"
272
+ }
273
+ }`,
263
274
  output: [
264
275
  "Request ID: request-1",
265
276
  "",
@@ -267,6 +278,16 @@ it("renders session request tool cards from structured child-session status upda
267
278
  "",
268
279
  "Target: child",
269
280
  "",
281
+ "Status: completed",
282
+ "",
283
+ "Notify: final_reply",
284
+ "",
285
+ "Lifecycle: persistent",
286
+ "",
287
+ "Parent Session ID: parent-session-1",
288
+ "",
289
+ "Spawned By Request ID: request-1",
290
+ "",
270
291
  "Title: Verifier",
271
292
  "",
272
293
  "Task:",
@@ -302,16 +323,18 @@ it("renders regular session request tool cards with session navigation instead o
302
323
  status: ToolInvocationStatus.RESULT,
303
324
  toolCallId: "session-request-call-1",
304
325
  toolName: "sessions_request",
305
- args: '{"sessionId":"session-2","task":"Summarize the latest findings"}',
326
+ args: '{"target":{"session_id":"session-2"},"task":"Summarize the latest findings","notify":"none","title":"Research thread"}',
306
327
  result: {
307
328
  kind: "nextclaw.session_request",
308
329
  requestId: "request-2",
309
330
  sessionId: "session-2",
310
331
  agentId: "research-agent",
311
332
  isChildSession: false,
333
+ lifecycle: "persistent",
312
334
  title: "Research thread",
313
335
  task: "Summarize the latest findings",
314
336
  status: "completed",
337
+ notify: "none",
315
338
  finalResponseText: "Here is the summary.",
316
339
  },
317
340
  },
@@ -326,6 +349,14 @@ it("renders regular session request tool cards with session navigation instead o
326
349
  toolName: "sessions_request",
327
350
  agentId: "research-agent",
328
351
  summary: "title: Research thread · session: session-2 · task: Summarize the latest findings",
352
+ input: `{
353
+ "target": {
354
+ "session_id": "session-2"
355
+ },
356
+ "task": "Summarize the latest findings",
357
+ "notify": "none",
358
+ "title": "Research thread"
359
+ }`,
329
360
  output: [
330
361
  "Request ID: request-2",
331
362
  "",
@@ -333,6 +364,12 @@ it("renders regular session request tool cards with session navigation instead o
333
364
  "",
334
365
  "Target: session",
335
366
  "",
367
+ "Status: completed",
368
+ "",
369
+ "Notify: none",
370
+ "",
371
+ "Lifecycle: persistent",
372
+ "",
336
373
  "Title: Research thread",
337
374
  "",
338
375
  "Task:",