@nextclaw/ui 0.11.21 → 0.11.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/assets/{ChannelsList-ByHWHkQS.js → ChannelsList-DVDu1xvz.js} +6 -6
  3. package/dist/assets/ChatPage-Z9tRzm_n.js +43 -0
  4. package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
  5. package/dist/assets/{DocBrowser-3y_NHZ71.js → DocBrowser-BmtBLFU0.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-CVJuwCcw.js → DocBrowserContext-YIKkPb76.js} +1 -1
  7. package/dist/assets/{LogoBadge-D8fyilO-.js → LogoBadge-F7ZWdxLT.js} +1 -1
  8. package/dist/assets/{MarketplacePage-CmhsZXr1.js → MarketplacePage-Buo9HrOz.js} +2 -2
  9. package/dist/assets/MarketplacePage-D6rVQEQR.js +1 -0
  10. package/dist/assets/{McpMarketplacePage-C7PkCYbp.js → McpMarketplacePage-JnkYwK7p.js} +2 -2
  11. package/dist/assets/ModelConfig-BYRhgp0c.js +1 -0
  12. package/dist/assets/ProvidersList-DmLyyHvX.js +1 -0
  13. package/dist/assets/RemoteAccessPage-CDSSvH7Z.js +1 -0
  14. package/dist/assets/RuntimeConfig-v7a7Fe3x.js +1 -0
  15. package/dist/assets/{SearchConfig-Dm7r2yfp.js → SearchConfig-D5f1EkLE.js} +1 -1
  16. package/dist/assets/{SecretsConfig-BBP_mbQh.js → SecretsConfig-D61IKcYt.js} +2 -2
  17. package/dist/assets/{SessionsConfig-6wNJloZN.js → SessionsConfig-BRIxVTEv.js} +2 -2
  18. package/dist/assets/{book-open-B26jGBjY.js → book-open-CXoF5nQC.js} +1 -1
  19. package/dist/assets/chat-session-display-D0WpnuRZ.js +1 -0
  20. package/dist/assets/{chunk-JZWAC4HX-B-4B29RN.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
  21. package/dist/assets/{config-BaC29Qf-.js → config-DJswxxE8.js} +1 -1
  22. package/dist/assets/{createLucideIcon-DiFAvXmK.js → createLucideIcon-CjGHOWb6.js} +1 -1
  23. package/dist/assets/{dist-pCfWPG1A.js → dist-Cl2QB-2y.js} +1 -1
  24. package/dist/assets/{dist-kW_O3kyZ.js → dist-nqTTbVdA.js} +1 -1
  25. package/dist/assets/{external-link-D5-p-Gmm.js → external-link-tIO7zING.js} +1 -1
  26. package/dist/assets/{hash-BlwrSV0q.js → hash-JWUyl1pT.js} +1 -1
  27. package/dist/assets/i18n-CDHMXlRZ.js +1 -0
  28. package/dist/assets/{index-DvKS3L9j.js → index-BuwbBgmT.js} +3 -3
  29. package/dist/assets/index-bZ8cqQIS.css +1 -0
  30. package/dist/assets/{label-RyXfZqkP.js → label-BIpeNu4r.js} +1 -1
  31. package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
  32. package/dist/assets/{logos-Bpl8QTgI.js → logos-DThdM9lk.js} +1 -1
  33. package/dist/assets/{page-layout--S0YBU0W.js → page-layout-D3Xo605Z.js} +1 -1
  34. package/dist/assets/plus-PHf8q-Ct.js +1 -0
  35. package/dist/assets/{popover-BEjfbEwy.js → popover-BJRUGA_H.js} +1 -1
  36. package/dist/assets/provider-models-bz5y28rq.js +1 -0
  37. package/dist/assets/{react-BuSP2-8B.js → react-7ZHqQtEV.js} +1 -1
  38. package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
  39. package/dist/assets/{save-DPPPpD_c.js → save-DJM5RRWW.js} +1 -1
  40. package/dist/assets/search-C91yH_6y.js +1 -0
  41. package/dist/assets/{security-config-6t78Ph-I.js → security-config-DbUyWcQz.js} +1 -1
  42. package/dist/assets/{select-CT50pzod.js → select-DSkTc61S.js} +1 -1
  43. package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
  44. package/dist/assets/{status-dot-BbBqRHfh.js → status-dot-LNBlDu3q.js} +1 -1
  45. package/dist/assets/{switch-D3l6AcCk.js → switch-Bo-Y46HZ.js} +1 -1
  46. package/dist/assets/tabs-custom-DXv507_2.js +1 -0
  47. package/dist/assets/{trash-2-B2_AGVE3.js → trash-2-DFZmW6Gg.js} +1 -1
  48. package/dist/assets/useConfirmDialog-COwYXDKm.js +1 -0
  49. package/dist/assets/{useMutation-BzCrO8j-.js → useMutation-DrZrOgVL.js} +1 -1
  50. package/dist/assets/x-D7Q1yqSF.js +1 -0
  51. package/dist/index.html +18 -18
  52. package/package.json +6 -6
  53. package/src/api/ncp-session.test.ts +37 -0
  54. package/src/api/ncp-session.ts +29 -1
  55. package/src/api/server-path.ts +23 -0
  56. package/src/api/types.ts +45 -0
  57. package/src/components/chat/ChatConversationPanel.test.tsx +53 -9
  58. package/src/components/chat/ChatConversationPanel.tsx +122 -79
  59. package/src/components/chat/ChatSidebar.test.tsx +2 -2
  60. package/src/components/chat/ChatSidebar.tsx +2 -2
  61. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
  62. package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
  63. package/src/components/chat/adapters/chat-message-part.adapter.ts +26 -14
  64. package/src/components/chat/adapters/chat-message.adapter.test.ts +159 -13
  65. package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +191 -0
  66. package/src/components/chat/adapters/{chat-message.file-operation-card.ts → file-operation/card.ts} +74 -181
  67. package/src/components/chat/adapters/{chat-message.file-operation-diff.ts → file-operation/diff.ts} +178 -188
  68. package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
  69. package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
  70. package/src/components/chat/chat-child-session-panel.tsx +100 -0
  71. package/src/components/chat/chat-composer-state.ts +3 -3
  72. package/src/components/chat/chat-page-runtime.test.ts +1 -0
  73. package/src/components/chat/chat-session-display.test.ts +22 -0
  74. package/src/components/chat/chat-session-display.ts +6 -1
  75. package/src/components/chat/containers/chat-input-bar.container.tsx +21 -24
  76. package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
  77. package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
  78. package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
  79. package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
  80. package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
  81. package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
  82. package/src/components/chat/ncp/NcpChatPage.tsx +219 -116
  83. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
  84. package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
  85. package/src/components/chat/ncp/ncp-chat-thread.manager.ts +49 -0
  86. package/src/components/chat/ncp/ncp-session-adapter.test.ts +24 -0
  87. package/src/components/chat/ncp/ncp-session-adapter.ts +47 -0
  88. package/src/components/chat/ncp/use-ncp-session-list-view.ts +10 -1
  89. package/src/components/chat/presenter/chat-presenter-context.tsx +4 -1
  90. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
  91. package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
  92. package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
  93. package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
  94. package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
  95. package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
  96. package/src/components/chat/stores/chat-input.store.ts +6 -3
  97. package/src/components/chat/stores/chat-thread.store.ts +17 -3
  98. package/src/components/chat/useHydratedNcpAgent.test.tsx +30 -23
  99. package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
  100. package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
  101. package/src/hooks/server-path/use-server-path-browse.ts +19 -0
  102. package/src/hooks/useConfig.ts +26 -1
  103. package/src/lib/i18n/i18n-language-owner.ts +94 -0
  104. package/src/lib/i18n/i18n.path-picker.ts +12 -0
  105. package/src/lib/i18n.chat.ts +23 -0
  106. package/src/lib/i18n.ts +21 -84
  107. package/src/lib/session-project/session-project.utils.ts +30 -0
  108. package/dist/assets/ChatPage-FdT3pDnw.js +0 -42
  109. package/dist/assets/DocBrowser-CMdPdbZj.js +0 -1
  110. package/dist/assets/MarketplacePage-9oKmxN2n.js +0 -1
  111. package/dist/assets/ModelConfig-DmCY6jWM.js +0 -1
  112. package/dist/assets/ProvidersList-ClT-34aX.js +0 -1
  113. package/dist/assets/RemoteAccessPage-B6hUZl1O.js +0 -1
  114. package/dist/assets/RuntimeConfig-C5aqliGk.js +0 -1
  115. package/dist/assets/chat-session-display-Bjmn4aIZ.js +0 -1
  116. package/dist/assets/i18n-CSytxMFI.js +0 -1
  117. package/dist/assets/index-CUy6doWo.css +0 -1
  118. package/dist/assets/loader-circle-B2J777gj.js +0 -1
  119. package/dist/assets/plus-CM9XJ0Tf.js +0 -1
  120. package/dist/assets/provider-models-C8JQUd1E.js +0 -1
  121. package/dist/assets/search-Ctaw34Kp.js +0 -1
  122. package/dist/assets/skeleton-Bycyb0zU.js +0 -1
  123. package/dist/assets/tabs-custom-TZQ5WPWP.js +0 -1
  124. package/dist/assets/useConfirmDialog-BDpdjfIO.js +0 -1
  125. package/dist/assets/x-CHOBE-63.js +0 -1
  126. package/src/components/chat/adapters/chat-message.subagent-tool-card.ts +0 -154
  127. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
  128. /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
  129. /package/dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
@@ -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 spawn tool cards from structured subagent status updates", () => {
226
+ it("renders session request tool cards from structured child-session status updates", () => {
227
227
  const adapted = adapt([
228
228
  {
229
229
  id: "assistant-subagent",
@@ -237,12 +237,15 @@ it("renders spawn tool cards from structured subagent status updates", () => {
237
237
  toolName: "spawn",
238
238
  args: '{"label":"Verifier","task":"Verify 1+1=2"}',
239
239
  result: {
240
- kind: "nextclaw.subagent_run",
241
- runId: "subagent-1",
242
- label: "Verifier",
240
+ kind: "nextclaw.session_request",
241
+ requestId: "request-1",
242
+ sessionId: "child-session-1",
243
+ isChildSession: true,
244
+ title: "Verifier",
243
245
  task: "Verify 1+1=2",
244
246
  status: "completed",
245
- result: "Verified 1+1=2.",
247
+ finalResponseText: "Verified 1+1=2.",
248
+ parentSessionId: "parent-session-1",
246
249
  },
247
250
  },
248
251
  },
@@ -254,21 +257,92 @@ it("renders spawn tool cards from structured subagent status updates", () => {
254
257
  type: "tool-card",
255
258
  card: {
256
259
  toolName: "spawn",
257
- summary: "label: Verifier · run: subagent-1 · task: Verify 1+1=2",
260
+ summary: "title: Verifier · session: child-session-1 · task: Verify 1+1=2",
258
261
  output: [
259
- "Run ID: subagent-1",
262
+ "Request ID: request-1",
260
263
  "",
261
- "Label: Verifier",
264
+ "Session ID: child-session-1",
265
+ "",
266
+ "Target: child",
267
+ "",
268
+ "Title: Verifier",
262
269
  "",
263
270
  "Task:",
264
271
  "Verify 1+1=2",
265
272
  "",
266
- "Result:",
273
+ "Final Response:",
267
274
  "Verified 1+1=2.",
268
275
  ].join("\n"),
269
276
  statusTone: "success",
270
277
  statusLabel: "Completed",
271
278
  titleLabel: "Tool Result",
279
+ action: {
280
+ kind: "open-session",
281
+ sessionId: "child-session-1",
282
+ sessionKind: "child",
283
+ label: "Verifier",
284
+ parentSessionId: "parent-session-1",
285
+ },
286
+ },
287
+ });
288
+ });
289
+
290
+ it("renders regular session request tool cards with session navigation instead of child navigation", () => {
291
+ const adapted = adapt([
292
+ {
293
+ id: "assistant-session-request",
294
+ role: "assistant",
295
+ parts: [
296
+ {
297
+ type: "tool-invocation",
298
+ toolInvocation: {
299
+ status: ToolInvocationStatus.RESULT,
300
+ toolCallId: "session-request-call-1",
301
+ toolName: "sessions_request",
302
+ args: '{"sessionId":"session-2","task":"Summarize the latest findings"}',
303
+ result: {
304
+ kind: "nextclaw.session_request",
305
+ requestId: "request-2",
306
+ sessionId: "session-2",
307
+ isChildSession: false,
308
+ title: "Research thread",
309
+ task: "Summarize the latest findings",
310
+ status: "completed",
311
+ finalResponseText: "Here is the summary.",
312
+ },
313
+ },
314
+ },
315
+ ],
316
+ },
317
+ ] as unknown as ChatMessageSource[]);
318
+
319
+ expect(adapted[0]?.parts[0]).toMatchObject({
320
+ type: "tool-card",
321
+ card: {
322
+ toolName: "sessions_request",
323
+ summary: "title: Research thread · session: session-2 · task: Summarize the latest findings",
324
+ output: [
325
+ "Request ID: request-2",
326
+ "",
327
+ "Session ID: session-2",
328
+ "",
329
+ "Target: session",
330
+ "",
331
+ "Title: Research thread",
332
+ "",
333
+ "Task:",
334
+ "Summarize the latest findings",
335
+ "",
336
+ "Final Response:",
337
+ "Here is the summary.",
338
+ ].join("\n"),
339
+ statusTone: "success",
340
+ action: {
341
+ kind: "open-session",
342
+ sessionId: "session-2",
343
+ sessionKind: "session",
344
+ label: "Research thread",
345
+ },
272
346
  },
273
347
  });
274
348
  });
@@ -488,6 +562,11 @@ it("builds edit-file previews from structured args before the tool finishes", ()
488
562
  },
489
563
  ] as unknown as ChatMessageSource[]);
490
564
 
565
+ const editLines =
566
+ adapted[0]?.parts[0]?.type === "tool-card"
567
+ ? (adapted[0].parts[0].card.fileOperation?.blocks[0]?.lines ?? [])
568
+ : [];
569
+
491
570
  expect(adapted[0]?.parts[0]).toMatchObject({
492
571
  type: "tool-card",
493
572
  card: {
@@ -513,6 +592,71 @@ it("builds edit-file previews from structured args before the tool finishes", ()
513
592
  },
514
593
  },
515
594
  });
595
+ expect(editLines[0]).not.toHaveProperty("oldLineNumber");
596
+ expect(editLines[1]).not.toHaveProperty("newLineNumber");
597
+ });
598
+
599
+ it("uses structured edit-file result line numbers after the tool finishes", () => {
600
+ const adapted = adapt([
601
+ {
602
+ id: "assistant-edit-result",
603
+ role: "assistant",
604
+ parts: [
605
+ {
606
+ type: "tool-invocation",
607
+ toolInvocation: {
608
+ status: ToolInvocationStatus.RESULT,
609
+ toolCallId: "edit-result-1",
610
+ toolName: "edit_file",
611
+ args: JSON.stringify({
612
+ path: "src/app.ts",
613
+ oldText: "const color = 'red';",
614
+ newText: "const color = 'blue';",
615
+ }),
616
+ parsedArgs: {
617
+ path: "src/app.ts",
618
+ oldText: "const color = 'red';",
619
+ newText: "const color = 'blue';",
620
+ },
621
+ result: {
622
+ path: "src/app.ts",
623
+ oldStartLine: 27,
624
+ newStartLine: 27,
625
+ message: "Edited src/app.ts",
626
+ },
627
+ },
628
+ },
629
+ ],
630
+ },
631
+ ] as unknown as ChatMessageSource[]);
632
+
633
+ expect(adapted[0]?.parts[0]).toMatchObject({
634
+ type: "tool-card",
635
+ card: {
636
+ toolName: "edit_file",
637
+ summary: "src/app.ts",
638
+ statusTone: "success",
639
+ fileOperation: {
640
+ blocks: [
641
+ {
642
+ path: "src/app.ts",
643
+ lines: [
644
+ {
645
+ kind: "remove",
646
+ text: "const color = 'red';",
647
+ oldLineNumber: 27,
648
+ },
649
+ {
650
+ kind: "add",
651
+ text: "const color = 'blue';",
652
+ newLineNumber: 27,
653
+ },
654
+ ],
655
+ },
656
+ ],
657
+ },
658
+ },
659
+ });
516
660
  });
517
661
 
518
662
  it("builds write-file previews from partial native args before the JSON is complete", () => {
@@ -577,7 +721,7 @@ it("keeps completed write-file cards in preview mode instead of falling back to
577
721
  toolName: "write_file",
578
722
  args: JSON.stringify({
579
723
  path: "games/snake.html",
580
- content: "<!DOCTYPE html>\n<canvas id=\"game\"></canvas>",
724
+ content: '<!DOCTYPE html>\n<canvas id="game"></canvas>',
581
725
  }),
582
726
  result: "Wrote 3906 bytes to games/snake.html",
583
727
  },
@@ -605,7 +749,7 @@ it("keeps completed write-file cards in preview mode instead of falling back to
605
749
  },
606
750
  {
607
751
  kind: "add",
608
- text: "<canvas id=\"game\"></canvas>",
752
+ text: '<canvas id="game"></canvas>',
609
753
  newLineNumber: 2,
610
754
  },
611
755
  ],
@@ -641,7 +785,7 @@ it("renders codex file_change results as structured diff previews", () => {
641
785
  diff: [
642
786
  "--- a/src/main.ts",
643
787
  "+++ b/src/main.ts",
644
- "@@",
788
+ "@@ -109,1 +109,1 @@",
645
789
  "-console.log('old');",
646
790
  "+console.log('new');",
647
791
  ].join("\n"),
@@ -656,7 +800,7 @@ it("renders codex file_change results as structured diff previews", () => {
656
800
  diff: [
657
801
  "--- a/src/main.ts",
658
802
  "+++ b/src/main.ts",
659
- "@@",
803
+ "@@ -109,1 +109,1 @@",
660
804
  "-console.log('old');",
661
805
  "+console.log('new');",
662
806
  ].join("\n"),
@@ -683,10 +827,12 @@ it("renders codex file_change results as structured diff previews", () => {
683
827
  {
684
828
  kind: "remove",
685
829
  text: "console.log('old');",
830
+ oldLineNumber: 109,
686
831
  },
687
832
  {
688
833
  kind: "add",
689
834
  text: "console.log('new');",
835
+ newLineNumber: 109,
690
836
  },
691
837
  ],
692
838
  },
@@ -0,0 +1,191 @@
1
+ import {
2
+ stringifyUnknown,
3
+ summarizeToolArgs,
4
+ type ToolCard,
5
+ } from "@/lib/chat-message";
6
+ import type { ChatToolPartViewModel } from "@nextclaw/agent-chat-ui";
7
+
8
+ type ToolCardViewSource = ToolCard & {
9
+ statusTone: ChatToolPartViewModel["statusTone"];
10
+ statusLabel: string;
11
+ action?: ChatToolPartViewModel["action"];
12
+ };
13
+
14
+ type SessionRequestInvocation = {
15
+ toolName: string;
16
+ toolCallId?: string;
17
+ args?: unknown;
18
+ result?: unknown;
19
+ };
20
+
21
+ type SessionRequestToolCardTexts = {
22
+ toolStatusRunningLabel: string;
23
+ toolStatusCompletedLabel: string;
24
+ toolStatusFailedLabel: string;
25
+ };
26
+
27
+ type SessionRequestResult = {
28
+ kind: string;
29
+ requestId?: string;
30
+ sessionId?: string;
31
+ isChildSession?: boolean;
32
+ title?: string;
33
+ task?: string;
34
+ status?: string;
35
+ message?: unknown;
36
+ finalResponseText?: unknown;
37
+ error?: unknown;
38
+ parentSessionId?: string;
39
+ };
40
+
41
+ function isRecord(value: unknown): value is Record<string, unknown> {
42
+ return typeof value === "object" && value !== null;
43
+ }
44
+
45
+ function readOptionalString(value: unknown): string | null {
46
+ if (typeof value !== "string") {
47
+ return null;
48
+ }
49
+ const trimmed = value.trim();
50
+ return trimmed.length > 0 ? trimmed : null;
51
+ }
52
+
53
+ function readSessionRequestResult(value: unknown): SessionRequestResult | null {
54
+ if (!isRecord(value) || value.kind !== "nextclaw.session_request") {
55
+ return null;
56
+ }
57
+ return value as SessionRequestResult;
58
+ }
59
+
60
+ function buildSessionRequestDetail(
61
+ result: SessionRequestResult,
62
+ fallbackArgs: unknown,
63
+ ): string | undefined {
64
+ const detailParts = [
65
+ readOptionalString(result.title)
66
+ ? `title: ${result.title?.trim()}`
67
+ : null,
68
+ readOptionalString(result.sessionId)
69
+ ? `session: ${result.sessionId?.trim()}`
70
+ : null,
71
+ readOptionalString(result.task)
72
+ ? `task: ${result.task?.trim()}`
73
+ : null,
74
+ ].filter((value): value is string => Boolean(value));
75
+
76
+ return detailParts.join(" · ") || summarizeToolArgs(fallbackArgs);
77
+ }
78
+
79
+ function buildSessionRequestOutput(result: SessionRequestResult): string | undefined {
80
+ const requestId = readOptionalString(result.requestId);
81
+ const sessionId = readOptionalString(result.sessionId);
82
+ const title = readOptionalString(result.title);
83
+ const task = readOptionalString(result.task);
84
+ const messageText =
85
+ typeof result.message !== "undefined"
86
+ ? stringifyUnknown(result.message).trim()
87
+ : "";
88
+ const finalResponseText =
89
+ typeof result.finalResponseText !== "undefined"
90
+ ? stringifyUnknown(result.finalResponseText).trim()
91
+ : "";
92
+ const errorText =
93
+ typeof result.error !== "undefined"
94
+ ? stringifyUnknown(result.error).trim()
95
+ : "";
96
+
97
+ const sections = [
98
+ requestId ? `Request ID: ${requestId}` : null,
99
+ sessionId ? `Session ID: ${sessionId}` : null,
100
+ typeof result.isChildSession === "boolean"
101
+ ? `Target: ${result.isChildSession ? "child" : "session"}`
102
+ : null,
103
+ title ? `Title: ${title}` : null,
104
+ task ? `Task:\n${task}` : null,
105
+ finalResponseText
106
+ ? `Final Response:\n${finalResponseText}`
107
+ : errorText
108
+ ? `Error:\n${errorText}`
109
+ : messageText
110
+ ? `Status:\n${messageText}`
111
+ : null,
112
+ ].filter((value): value is string => Boolean(value));
113
+
114
+ return sections.length > 0 ? sections.join("\n\n") : undefined;
115
+ }
116
+
117
+ export function buildSessionRequestToolCard(params: {
118
+ invocation: SessionRequestInvocation;
119
+ texts: SessionRequestToolCardTexts;
120
+ }): ToolCardViewSource | null {
121
+ if (
122
+ params.invocation.toolName !== "spawn" &&
123
+ params.invocation.toolName !== "sessions_request"
124
+ ) {
125
+ return null;
126
+ }
127
+
128
+ const sessionRequest = readSessionRequestResult(params.invocation.result);
129
+ if (!sessionRequest) {
130
+ return null;
131
+ }
132
+
133
+ const normalizedStatus = readOptionalString(sessionRequest.status)?.toLowerCase();
134
+ const detail = buildSessionRequestDetail(sessionRequest, params.invocation.args);
135
+ const output = buildSessionRequestOutput(sessionRequest);
136
+ const targetSessionId = readOptionalString(sessionRequest.sessionId);
137
+ const action =
138
+ targetSessionId
139
+ ? {
140
+ kind: "open-session" as const,
141
+ sessionId: targetSessionId,
142
+ sessionKind: sessionRequest.isChildSession === true ? ("child" as const) : ("session" as const),
143
+ ...(readOptionalString(sessionRequest.title)
144
+ ? { label: sessionRequest.title!.trim() }
145
+ : {}),
146
+ ...(readOptionalString(sessionRequest.parentSessionId)
147
+ ? { parentSessionId: sessionRequest.parentSessionId!.trim() }
148
+ : {}),
149
+ }
150
+ : undefined;
151
+
152
+ if (normalizedStatus === "failed") {
153
+ return {
154
+ kind: "result",
155
+ name: params.invocation.toolName,
156
+ detail,
157
+ text: output,
158
+ callId: params.invocation.toolCallId || undefined,
159
+ hasResult: Boolean(output),
160
+ statusTone: "error",
161
+ statusLabel: params.texts.toolStatusFailedLabel,
162
+ ...(action ? { action } : {}),
163
+ };
164
+ }
165
+
166
+ if (normalizedStatus === "completed") {
167
+ return {
168
+ kind: "result",
169
+ name: params.invocation.toolName,
170
+ detail,
171
+ text: output,
172
+ callId: params.invocation.toolCallId || undefined,
173
+ hasResult: Boolean(output),
174
+ statusTone: "success",
175
+ statusLabel: params.texts.toolStatusCompletedLabel,
176
+ ...(action ? { action } : {}),
177
+ };
178
+ }
179
+
180
+ return {
181
+ kind: "result",
182
+ name: params.invocation.toolName,
183
+ detail,
184
+ text: output,
185
+ callId: params.invocation.toolCallId || undefined,
186
+ hasResult: Boolean(output),
187
+ statusTone: "running",
188
+ statusLabel: params.texts.toolStatusRunningLabel,
189
+ ...(action ? { action } : {}),
190
+ };
191
+ }