@nextclaw/ui 0.12.4 → 0.12.5

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 (115) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{ChannelsList-CobWeI2V.js → ChannelsList-C6-lh55g.js} +2 -2
  3. package/dist/assets/ChatPage-DOW0gPc2.js +45 -0
  4. package/dist/assets/DocBrowser-CGyeswYP.js +1 -0
  5. package/dist/assets/{DocBrowser-NSzgVKka.js → DocBrowser-QUZ3nfmH.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-DpgVdRgk.js → DocBrowserContext-CpiIfhJO.js} +1 -1
  7. package/dist/assets/{LogoBadge-CHS4YNLw.js → LogoBadge-BUK13xK5.js} +1 -1
  8. package/dist/assets/MarketplacePage-BDVwhIYE.js +1 -0
  9. package/dist/assets/MarketplacePage-LnKKL3xK.js +49 -0
  10. package/dist/assets/McpMarketplacePage-BG4T_Pcx.js +40 -0
  11. package/dist/assets/ModelConfig-LtWuogIw.js +1 -0
  12. package/dist/assets/ProviderScopedModelInput-DGn6sFEN.js +1 -0
  13. package/dist/assets/ProvidersList-ma-_MlLo.js +1 -0
  14. package/dist/assets/{RemoteAccessPage-yfbrveNQ.js → RemoteAccessPage-ff15qO-c.js} +1 -1
  15. package/dist/assets/RuntimeConfig-TgPandXF.js +1 -0
  16. package/dist/assets/SearchConfig-C9iBt7pl.js +1 -0
  17. package/dist/assets/{SecretsConfig-CLFSSoTl.js → SecretsConfig-Bew4EF2A.js} +2 -2
  18. package/dist/assets/{SessionsConfig-vYrvc2Fk.js → SessionsConfig-2r2yAGZg.js} +2 -2
  19. package/dist/assets/{book-open-C7TAghTk.js → book-open-CJG8Yz3U.js} +1 -1
  20. package/dist/assets/{chat-session-display-5dVFkJyw.js → chat-session-display-DkAC5OMC.js} +1 -1
  21. package/dist/assets/{chunk-JZWAC4HX-DbL4EmiT.js → chunk-JZWAC4HX-D5b3Iyas.js} +1 -1
  22. package/dist/assets/{config-CMiW0yaK.js → config-zvnxSXSP.js} +1 -1
  23. package/dist/assets/{createLucideIcon-BRLFtf-8.js → createLucideIcon-_FMJqZw2.js} +1 -1
  24. package/dist/assets/{dist-DP-JKR4G.js → dist-B1fpOuON.js} +1 -1
  25. package/dist/assets/{dist-BFc_H-lY.js → dist-BCXX7FD-.js} +2 -2
  26. package/dist/assets/{external-link-BkJkiWbH.js → external-link-b7gAJWYY.js} +1 -1
  27. package/dist/assets/{hash-CbP6-6R9.js → hash-Bhy4TwfZ.js} +1 -1
  28. package/dist/assets/{i18n-C_2dKw6w.js → i18n-DJg9BPYk.js} +1 -1
  29. package/dist/assets/index-BoJbxdvZ.css +1 -0
  30. package/dist/assets/index-CtlT4E9Y.js +6 -0
  31. package/dist/assets/infiniteQueryBehavior-CTcVlD9s.js +1 -0
  32. package/dist/assets/loader-circle-B60I0hEk.js +1 -0
  33. package/dist/assets/{logos-N3dbS6-I.js → logos-GMeYU9vc.js} +1 -1
  34. package/dist/assets/{page-layout-DyuvlNrg.js → page-layout-C8UbWuMt.js} +1 -1
  35. package/dist/assets/plus-CR7RfK3H.js +1 -0
  36. package/dist/assets/{popover-BKKWGUaG.js → popover-8HSx9wQj.js} +1 -1
  37. package/dist/assets/react-BB4jko2M.js +1 -0
  38. package/dist/assets/{refresh-ccw-BGMdiNGq.js → refresh-ccw-CA4_C7Zg.js} +1 -1
  39. package/dist/assets/{save-Dh4GQzzX.js → save-BtvMy4lk.js} +1 -1
  40. package/dist/assets/search-C60UA27E.js +1 -0
  41. package/dist/assets/security-config-BkFDYZ6j.js +1 -0
  42. package/dist/assets/{select-BtIi5fnh.js → select-xp_Ac8ip.js} +1 -1
  43. package/dist/assets/skeleton-uxz_5h3A.js +1 -0
  44. package/dist/assets/{status-dot-C4O-2jZP.js → status-dot-Cn4Pp7DZ.js} +1 -1
  45. package/dist/assets/{switch-DPegGIa_.js → switch-BTi6UOij.js} +1 -1
  46. package/dist/assets/{tabs-custom-x5GZexrF.js → tabs-custom-BiiN8DME.js} +1 -1
  47. package/dist/assets/{trash-2-CU3LYIpQ.js → trash-2-BpsF0N-r.js} +1 -1
  48. package/dist/assets/use-infinite-scroll-loader-C8jBv11-.js +1 -0
  49. package/dist/assets/{useConfirmDialog-S5WsGOGf.js → useConfirmDialog-BJIwUZjH.js} +1 -1
  50. package/dist/assets/{useMutation-DSinpgEq.js → useMutation-BjBOKHj_.js} +1 -1
  51. package/dist/assets/x-BfTu-g7D.js +1 -0
  52. package/dist/index.html +19 -18
  53. package/package.json +4 -4
  54. package/src/account/components/account-panel.tsx +46 -4
  55. package/src/account/managers/account.manager.ts +19 -4
  56. package/src/api/remote.ts +9 -0
  57. package/src/api/remote.types.ts +5 -0
  58. package/src/components/chat/ChatConversationPanel.test.tsx +183 -141
  59. package/src/components/chat/adapters/chat-message-tool-agent-id.test.ts +11 -11
  60. package/src/components/chat/adapters/chat-message.adapter.test.ts +43 -6
  61. package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +182 -44
  62. package/src/components/chat/adapters/chat-message.session-spawn-tool-card.test.ts +104 -0
  63. package/src/components/chat/chat-child-session-panel.tsx +103 -45
  64. package/src/components/chat/chat-page-runtime.test.ts +16 -19
  65. package/src/components/chat/chat-session-preference-sync.test.ts +13 -0
  66. package/src/components/chat/chat-session-preference-sync.ts +9 -7
  67. package/src/components/chat/hooks/use-chat-session-project.test.tsx +5 -5
  68. package/src/components/chat/hooks/use-chat-session-project.ts +0 -5
  69. package/src/components/chat/hooks/use-chat-session-update.test.tsx +75 -0
  70. package/src/components/chat/hooks/use-chat-session-update.ts +4 -2
  71. package/src/components/chat/managers/chat-session-list.manager.test.ts +45 -5
  72. package/src/components/chat/managers/chat-session-list.manager.ts +18 -4
  73. package/src/components/chat/ncp/NcpChatPage.tsx +32 -51
  74. package/src/components/chat/ncp/ncp-chat-input.manager.ts +3 -5
  75. package/src/components/chat/ncp/ncp-chat-page-data.ts +0 -1
  76. package/src/components/chat/ncp/ncp-chat.presenter.ts +1 -11
  77. package/src/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view.ts +20 -7
  78. package/src/components/chat/stores/chat-session-list.store.ts +3 -0
  79. package/src/components/chat/useChatSessionTypeState.test.tsx +0 -3
  80. package/src/components/chat/useChatSessionTypeState.ts +3 -5
  81. package/src/components/config/ChannelsList.test.tsx +68 -0
  82. package/src/components/config/ChannelsList.tsx +22 -4
  83. package/src/components/config/ProvidersList.tsx +17 -3
  84. package/src/components/config/providers-list.test.tsx +68 -0
  85. package/src/components/layout/Sidebar.tsx +13 -13
  86. package/src/components/layout/sidebar.layout.test.tsx +32 -1
  87. package/src/components/marketplace/MarketplacePage.tsx +30 -30
  88. package/src/components/marketplace/marketplace-page-parts.tsx +16 -24
  89. package/src/components/marketplace/mcp/McpMarketplacePage.tsx +28 -26
  90. package/src/hooks/marketplace-list-pages.ts +27 -0
  91. package/src/hooks/use-infinite-scroll-loader.ts +88 -0
  92. package/src/hooks/useMarketplace.ts +14 -3
  93. package/src/hooks/useMcpMarketplace.ts +14 -3
  94. package/src/lib/i18n.remote.ts +15 -0
  95. package/dist/assets/ChatPage-ZIdFFVAv.js +0 -43
  96. package/dist/assets/DocBrowser-D55C0iyl.js +0 -1
  97. package/dist/assets/MarketplacePage-BFYsRss_.js +0 -49
  98. package/dist/assets/MarketplacePage-DII-q-Y1.js +0 -1
  99. package/dist/assets/McpMarketplacePage-CPqsGJzz.js +0 -40
  100. package/dist/assets/ModelConfig-Bvuo_IpS.js +0 -1
  101. package/dist/assets/ProviderScopedModelInput-BfY8rGsf.js +0 -1
  102. package/dist/assets/ProvidersList-3tlaqwSS.js +0 -1
  103. package/dist/assets/RuntimeConfig-CAd5Kta3.js +0 -1
  104. package/dist/assets/SearchConfig-DFwgaAa7.js +0 -1
  105. package/dist/assets/index-ChUXhq0G.css +0 -1
  106. package/dist/assets/index-DAE8Srx-.js +0 -6
  107. package/dist/assets/label-D8yyejJS.js +0 -1
  108. package/dist/assets/loader-circle-B0sKKO29.js +0 -1
  109. package/dist/assets/marketplace-localization-CxSTG9wr.js +0 -1
  110. package/dist/assets/plus-CYXs3JtZ.js +0 -1
  111. package/dist/assets/react-8EIEQjMP.js +0 -1
  112. package/dist/assets/search-DOsLw-P9.js +0 -1
  113. package/dist/assets/security-config-CM_tQRXQ.js +0 -1
  114. package/dist/assets/skeleton-GbHLjPC0.js +0 -1
  115. package/dist/assets/x-Bnco_K8b.js +0 -1
@@ -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:",
@@ -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
+ });