@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
@@ -9,6 +9,7 @@ import type { ChatToolPartViewModel } from "@nextclaw/agent-chat-ui";
9
9
  type ToolCardViewSource = ToolCard & {
10
10
  statusTone: ChatToolPartViewModel["statusTone"];
11
11
  statusLabel: string;
12
+ input?: string;
12
13
  action?: ChatToolPartViewModel["action"];
13
14
  };
14
15
 
@@ -31,15 +32,30 @@ type SessionRequestResult = {
31
32
  sessionId?: string;
32
33
  agentId?: string;
33
34
  isChildSession?: boolean;
35
+ lifecycle?: string;
34
36
  title?: string;
35
37
  task?: string;
36
38
  status?: string;
39
+ notify?: string;
40
+ spawnedByRequestId?: string;
37
41
  message?: unknown;
38
42
  finalResponseText?: unknown;
39
43
  error?: unknown;
40
44
  parentSessionId?: string;
41
45
  };
42
46
 
47
+ type SessionSpawnResult = {
48
+ kind: string;
49
+ sessionId?: string;
50
+ agentId?: string;
51
+ isChildSession?: boolean;
52
+ title?: string;
53
+ sessionType?: string;
54
+ lifecycle?: string;
55
+ createdAt?: string;
56
+ parentSessionId?: string;
57
+ };
58
+
43
59
  function isRecord(value: unknown): value is Record<string, unknown> {
44
60
  return typeof value === "object" && value !== null;
45
61
  }
@@ -59,6 +75,33 @@ function readSessionRequestResult(value: unknown): SessionRequestResult | null {
59
75
  return value as SessionRequestResult;
60
76
  }
61
77
 
78
+ function readSessionSpawnResult(value: unknown): SessionSpawnResult | null {
79
+ if (!isRecord(value) || value.kind !== "nextclaw.session") {
80
+ return null;
81
+ }
82
+ return value as SessionSpawnResult;
83
+ }
84
+
85
+ function parseStructuredValue(value: unknown): unknown {
86
+ if (typeof value !== "string") {
87
+ return value;
88
+ }
89
+ const trimmed = value.trim();
90
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
91
+ return value;
92
+ }
93
+ try {
94
+ return JSON.parse(trimmed) as unknown;
95
+ } catch {
96
+ return value;
97
+ }
98
+ }
99
+
100
+ function buildStructuredInput(value: unknown): string | undefined {
101
+ const text = stringifyUnknown(parseStructuredValue(value)).trim();
102
+ return text || undefined;
103
+ }
104
+
62
105
  function buildSessionRequestDetail(
63
106
  result: SessionRequestResult,
64
107
  fallbackArgs: unknown,
@@ -78,11 +121,32 @@ function buildSessionRequestDetail(
78
121
  return detailParts.join(" · ") || summarizeToolArgs(fallbackArgs);
79
122
  }
80
123
 
124
+ function buildSessionSpawnDetail(
125
+ result: SessionSpawnResult,
126
+ fallbackArgs: unknown,
127
+ ): string | undefined {
128
+ const detailParts = [
129
+ readOptionalString(result.title)
130
+ ? `title: ${result.title?.trim()}`
131
+ : null,
132
+ readOptionalString(result.sessionId)
133
+ ? `session: ${result.sessionId?.trim()}`
134
+ : null,
135
+ ].filter((value): value is string => Boolean(value));
136
+
137
+ return detailParts.join(" · ") || summarizeToolArgs(fallbackArgs);
138
+ }
139
+
81
140
  function buildSessionRequestOutput(result: SessionRequestResult): string | undefined {
82
141
  const requestId = readOptionalString(result.requestId);
83
142
  const sessionId = readOptionalString(result.sessionId);
84
143
  const title = readOptionalString(result.title);
85
144
  const task = readOptionalString(result.task);
145
+ const status = readOptionalString(result.status);
146
+ const notify = readOptionalString(result.notify);
147
+ const lifecycle = readOptionalString(result.lifecycle);
148
+ const parentSessionId = readOptionalString(result.parentSessionId);
149
+ const spawnedByRequestId = readOptionalString(result.spawnedByRequestId);
86
150
  const messageText =
87
151
  typeof result.message !== "undefined"
88
152
  ? stringifyUnknown(result.message).trim()
@@ -102,6 +166,11 @@ function buildSessionRequestOutput(result: SessionRequestResult): string | undef
102
166
  typeof result.isChildSession === "boolean"
103
167
  ? `Target: ${result.isChildSession ? "child" : "session"}`
104
168
  : null,
169
+ status ? `Status: ${status}` : null,
170
+ notify ? `Notify: ${notify}` : null,
171
+ lifecycle ? `Lifecycle: ${lifecycle}` : null,
172
+ parentSessionId ? `Parent Session ID: ${parentSessionId}` : null,
173
+ spawnedByRequestId ? `Spawned By Request ID: ${spawnedByRequestId}` : null,
105
174
  title ? `Title: ${title}` : null,
106
175
  task ? `Task:\n${task}` : null,
107
176
  finalResponseText
@@ -116,6 +185,29 @@ function buildSessionRequestOutput(result: SessionRequestResult): string | undef
116
185
  return sections.length > 0 ? sections.join("\n\n") : undefined;
117
186
  }
118
187
 
188
+ function buildSessionSpawnOutput(result: SessionSpawnResult): string | undefined {
189
+ const sessionId = readOptionalString(result.sessionId);
190
+ const title = readOptionalString(result.title);
191
+ const sessionType = readOptionalString(result.sessionType);
192
+ const lifecycle = readOptionalString(result.lifecycle);
193
+ const parentSessionId = readOptionalString(result.parentSessionId);
194
+ const createdAt = readOptionalString(result.createdAt);
195
+
196
+ const sections = [
197
+ sessionId ? `Session ID: ${sessionId}` : null,
198
+ typeof result.isChildSession === "boolean"
199
+ ? `Target: ${result.isChildSession ? "child" : "session"}`
200
+ : null,
201
+ title ? `Title: ${title}` : null,
202
+ sessionType ? `Session Type: ${sessionType}` : null,
203
+ lifecycle ? `Lifecycle: ${lifecycle}` : null,
204
+ parentSessionId ? `Parent Session ID: ${parentSessionId}` : null,
205
+ createdAt ? `Created At: ${createdAt}` : null,
206
+ ].filter((value): value is string => Boolean(value));
207
+
208
+ return sections.length > 0 ? sections.join("\n\n") : undefined;
209
+ }
210
+
119
211
  export function buildSessionRequestToolCard(params: {
120
212
  invocation: SessionRequestInvocation;
121
213
  texts: SessionRequestToolCardTexts;
@@ -123,77 +215,123 @@ export function buildSessionRequestToolCard(params: {
123
215
  const { invocation, texts } = params;
124
216
  const { toolName, toolCallId, args, result } = invocation;
125
217
 
126
- if (toolName !== "spawn" && toolName !== "sessions_request") {
218
+ if (
219
+ toolName !== "spawn" &&
220
+ toolName !== "sessions_request" &&
221
+ toolName !== "sessions_spawn"
222
+ ) {
127
223
  return null;
128
224
  }
129
225
 
130
226
  const sessionRequest = readSessionRequestResult(result);
131
- if (!sessionRequest) {
132
- return null;
133
- }
227
+ if (sessionRequest) {
228
+ const normalizedStatus = readOptionalString(sessionRequest.status)?.toLowerCase();
229
+ const detail = buildSessionRequestDetail(sessionRequest, args);
230
+ const output = buildSessionRequestOutput(sessionRequest);
231
+ const targetSessionId = readOptionalString(sessionRequest.sessionId);
232
+ const agentId = resolveToolInvocationAgentId({ args, result: sessionRequest });
233
+ const action =
234
+ targetSessionId
235
+ ? {
236
+ kind: "open-session" as const,
237
+ sessionId: targetSessionId,
238
+ sessionKind: sessionRequest.isChildSession === true ? ("child" as const) : ("session" as const),
239
+ ...(agentId
240
+ ? { agentId }
241
+ : {}),
242
+ ...(readOptionalString(sessionRequest.title)
243
+ ? { label: sessionRequest.title!.trim() }
244
+ : {}),
245
+ ...(readOptionalString(sessionRequest.parentSessionId)
246
+ ? { parentSessionId: sessionRequest.parentSessionId!.trim() }
247
+ : {}),
248
+ }
249
+ : undefined;
134
250
 
135
- const normalizedStatus = readOptionalString(sessionRequest.status)?.toLowerCase();
136
- const detail = buildSessionRequestDetail(sessionRequest, args);
137
- const output = buildSessionRequestOutput(sessionRequest);
138
- const targetSessionId = readOptionalString(sessionRequest.sessionId);
139
- const agentId = resolveToolInvocationAgentId({ args, result: sessionRequest });
140
- const action =
141
- targetSessionId
142
- ? {
143
- kind: "open-session" as const,
144
- sessionId: targetSessionId,
145
- sessionKind: sessionRequest.isChildSession === true ? ("child" as const) : ("session" as const),
146
- ...(agentId
147
- ? { agentId }
148
- : {}),
149
- ...(readOptionalString(sessionRequest.title)
150
- ? { label: sessionRequest.title!.trim() }
151
- : {}),
152
- ...(readOptionalString(sessionRequest.parentSessionId)
153
- ? { parentSessionId: sessionRequest.parentSessionId!.trim() }
154
- : {}),
155
- }
156
- : undefined;
251
+ if (normalizedStatus === "failed") {
252
+ return {
253
+ kind: "result",
254
+ name: toolName,
255
+ detail,
256
+ input: buildStructuredInput(args),
257
+ text: output,
258
+ callId: toolCallId || undefined,
259
+ hasResult: Boolean(output),
260
+ statusTone: "error",
261
+ statusLabel: texts.toolStatusFailedLabel,
262
+ ...(agentId ? { agentId } : {}),
263
+ ...(action ? { action } : {}),
264
+ };
265
+ }
266
+
267
+ if (normalizedStatus === "completed") {
268
+ return {
269
+ kind: "result",
270
+ name: toolName,
271
+ detail,
272
+ input: buildStructuredInput(args),
273
+ text: output,
274
+ callId: toolCallId || undefined,
275
+ hasResult: Boolean(output),
276
+ statusTone: "success",
277
+ statusLabel: texts.toolStatusCompletedLabel,
278
+ ...(agentId ? { agentId } : {}),
279
+ ...(action ? { action } : {}),
280
+ };
281
+ }
157
282
 
158
- if (normalizedStatus === "failed") {
159
283
  return {
160
284
  kind: "result",
161
285
  name: toolName,
162
286
  detail,
287
+ input: buildStructuredInput(args),
163
288
  text: output,
164
289
  callId: toolCallId || undefined,
165
290
  hasResult: Boolean(output),
166
- statusTone: "error",
167
- statusLabel: texts.toolStatusFailedLabel,
291
+ statusTone: "running",
292
+ statusLabel: texts.toolStatusRunningLabel,
168
293
  ...(agentId ? { agentId } : {}),
169
294
  ...(action ? { action } : {}),
170
295
  };
171
296
  }
172
297
 
173
- if (normalizedStatus === "completed") {
174
- return {
175
- kind: "result",
176
- name: toolName,
177
- detail,
178
- text: output,
179
- callId: toolCallId || undefined,
180
- hasResult: Boolean(output),
181
- statusTone: "success",
182
- statusLabel: texts.toolStatusCompletedLabel,
183
- ...(agentId ? { agentId } : {}),
184
- ...(action ? { action } : {}),
185
- };
298
+ const sessionSpawn = readSessionSpawnResult(result);
299
+ if (!sessionSpawn) {
300
+ return null;
186
301
  }
187
302
 
303
+ const detail = buildSessionSpawnDetail(sessionSpawn, args);
304
+ const output = buildSessionSpawnOutput(sessionSpawn);
305
+ const targetSessionId = readOptionalString(sessionSpawn.sessionId);
306
+ const agentId = resolveToolInvocationAgentId({ args, result: sessionSpawn });
307
+ const action =
308
+ targetSessionId
309
+ ? {
310
+ kind: "open-session" as const,
311
+ sessionId: targetSessionId,
312
+ sessionKind: sessionSpawn.isChildSession === true ? ("child" as const) : ("session" as const),
313
+ ...(agentId
314
+ ? { agentId }
315
+ : {}),
316
+ ...(readOptionalString(sessionSpawn.title)
317
+ ? { label: sessionSpawn.title!.trim() }
318
+ : {}),
319
+ ...(readOptionalString(sessionSpawn.parentSessionId)
320
+ ? { parentSessionId: sessionSpawn.parentSessionId!.trim() }
321
+ : {}),
322
+ }
323
+ : undefined;
324
+
188
325
  return {
189
326
  kind: "result",
190
327
  name: toolName,
191
328
  detail,
329
+ input: buildStructuredInput(args),
192
330
  text: output,
193
331
  callId: toolCallId || undefined,
194
332
  hasResult: Boolean(output),
195
- statusTone: "running",
196
- statusLabel: texts.toolStatusRunningLabel,
333
+ statusTone: "success",
334
+ statusLabel: texts.toolStatusCompletedLabel,
197
335
  ...(agentId ? { agentId } : {}),
198
336
  ...(action ? { action } : {}),
199
337
  };
@@ -0,0 +1,104 @@
1
+ import { ToolInvocationStatus, type UiMessage } from "@nextclaw/agent-chat";
2
+ import { adaptChatMessages } from "@/components/chat/adapters/chat-message.adapter";
3
+ import type { ChatMessageSource } from "@/components/chat/adapters/chat-message.adapter";
4
+
5
+ const defaultTexts = {
6
+ roleLabels: {
7
+ user: "You",
8
+ assistant: "Assistant",
9
+ tool: "Tool",
10
+ system: "System",
11
+ fallback: "Message",
12
+ },
13
+ reasoningLabel: "Reasoning",
14
+ toolCallLabel: "Tool Call",
15
+ toolResultLabel: "Tool Result",
16
+ toolInputLabel: "Input",
17
+ toolNoOutputLabel: "No output",
18
+ toolOutputLabel: "Output",
19
+ toolStatusPreparingLabel: "Preparing",
20
+ toolStatusRunningLabel: "Running",
21
+ toolStatusCompletedLabel: "Completed",
22
+ toolStatusFailedLabel: "Failed",
23
+ toolStatusCancelledLabel: "Cancelled",
24
+ imageAttachmentLabel: "Image attachment",
25
+ fileAttachmentLabel: "File attachment",
26
+ unknownPartLabel: "Unknown Part",
27
+ };
28
+
29
+ function adapt(uiMessages: UiMessage[]) {
30
+ return adaptChatMessages({
31
+ uiMessages: uiMessages as unknown as ChatMessageSource[],
32
+ formatTimestamp: (value) => `formatted:${value}`,
33
+ texts: defaultTexts,
34
+ });
35
+ }
36
+
37
+ it("renders child-session creation cards for sessions_spawn and keeps child-panel navigation", () => {
38
+ const adapted = adapt([
39
+ {
40
+ id: "assistant-child-session-create",
41
+ role: "assistant",
42
+ parts: [
43
+ {
44
+ type: "tool-invocation",
45
+ toolInvocation: {
46
+ status: ToolInvocationStatus.RESULT,
47
+ toolCallId: "sessions-spawn-child-only-1",
48
+ toolName: "sessions_spawn",
49
+ args: '{"scope":"child","title":"Verifier","task":"Prepare a child workspace"}',
50
+ result: {
51
+ kind: "nextclaw.session",
52
+ sessionId: "child-session-2",
53
+ agentId: "verifier-agent",
54
+ isChildSession: true,
55
+ title: "Verifier",
56
+ sessionType: "native",
57
+ lifecycle: "persistent",
58
+ parentSessionId: "parent-session-2",
59
+ createdAt: "2026-04-09T09:00:00.000Z",
60
+ },
61
+ },
62
+ },
63
+ ],
64
+ },
65
+ ]);
66
+
67
+ expect(adapted[0]?.parts[0]).toMatchObject({
68
+ type: "tool-card",
69
+ card: {
70
+ toolName: "sessions_spawn",
71
+ agentId: "verifier-agent",
72
+ summary: "title: Verifier · session: child-session-2",
73
+ input: `{
74
+ "scope": "child",
75
+ "title": "Verifier",
76
+ "task": "Prepare a child workspace"
77
+ }`,
78
+ output: [
79
+ "Session ID: child-session-2",
80
+ "",
81
+ "Target: child",
82
+ "",
83
+ "Title: Verifier",
84
+ "",
85
+ "Session Type: native",
86
+ "",
87
+ "Lifecycle: persistent",
88
+ "",
89
+ "Parent Session ID: parent-session-2",
90
+ "",
91
+ "Created At: 2026-04-09T09:00:00.000Z",
92
+ ].join("\n"),
93
+ statusTone: "success",
94
+ action: {
95
+ kind: "open-session",
96
+ sessionId: "child-session-2",
97
+ sessionKind: "child",
98
+ agentId: "verifier-agent",
99
+ label: "Verifier",
100
+ parentSessionId: "parent-session-2",
101
+ },
102
+ },
103
+ });
104
+ });