@nextclaw/ui 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/assets/{abnfDiagram-VRR7QNED-BcFJp_1X.js → abnfDiagram-VRR7QNED-DvTjgVMf.js} +1 -1
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Dp2ezlQn.js → aiden0z-pptx-renderer.es-C1nfBAXR.js} +1 -1
  4. package/dist/assets/{api-DJ6ZKWFY.js → api-BJioGtWS.js} +3 -3
  5. package/dist/assets/{appearance-settings-page-B-juoJ6F.js → appearance-settings-page-DAF1fd14.js} +1 -1
  6. package/dist/assets/{arc-CkgyPMyW.js → arc-BvhODBXv.js} +1 -1
  7. package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +1 -0
  8. package/dist/assets/{architectureDiagram-ZJ3FMSHR-CMd4cIjI.js → architectureDiagram-ZJ3FMSHR-BKO9RgNS.js} +1 -1
  9. package/dist/assets/{blockDiagram-677ZJIJ3-Bz3tbhet.js → blockDiagram-677ZJIJ3-DHpDXy7D.js} +1 -1
  10. package/dist/assets/book-open-aDv6R1av.js +1 -0
  11. package/dist/assets/{c4Diagram-LMCZKHZV-D9hFNAGv.js → c4Diagram-LMCZKHZV-BG_iQnDr.js} +1 -1
  12. package/dist/assets/channel-5T3pdR-s.js +1 -0
  13. package/dist/assets/{channels-list-page-mVHYa7xW.js → channels-list-page-Dubt8r9S.js} +2 -2
  14. package/dist/assets/{chat-page-DsMYGNee.js → chat-page-JbwjeUo4.js} +1 -1
  15. package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +1 -0
  16. package/dist/assets/{chunk-32BRIVSS-7NrjEITq.js → chunk-32BRIVSS-DR9M0LLx.js} +1 -1
  17. package/dist/assets/{chunk-52WLFC77-viIcqj9q.js → chunk-52WLFC77-698iC3BB.js} +1 -1
  18. package/dist/assets/{chunk-5FCAYU7R-Vsu4J-VM.js → chunk-5FCAYU7R-DikTj9Ey.js} +1 -1
  19. package/dist/assets/{chunk-5HE753X5-C-w6CQZK.js → chunk-5HE753X5-ko0o7_q0.js} +1 -1
  20. package/dist/assets/{chunk-5JV3BV7I-CuKgJQ4W.js → chunk-5JV3BV7I-CEHufhgo.js} +1 -1
  21. package/dist/assets/{chunk-5TONJI2A-CYeiWZKb.js → chunk-5TONJI2A-CGgno1cu.js} +1 -1
  22. package/dist/assets/{chunk-5VM5RSS4-DRCbfwIn.js → chunk-5VM5RSS4-DkiB4cUQ.js} +1 -1
  23. package/dist/assets/{chunk-7BUUIJ7U-BvydOcm3.js → chunk-7BUUIJ7U-DzptcytQ.js} +1 -1
  24. package/dist/assets/{chunk-BIQX33UG-RlppOXKo.js → chunk-BIQX33UG-DMRSQJvT.js} +1 -1
  25. package/dist/assets/{chunk-C7G6YPKG-DAizzYwr.js → chunk-C7G6YPKG-Bw1BwwuY.js} +1 -1
  26. package/dist/assets/{chunk-CQNSW5MT-5eChJHca.js → chunk-CQNSW5MT-C0z6eHBk.js} +1 -1
  27. package/dist/assets/{chunk-CYSBUYHQ-YHPr53fv.js → chunk-CYSBUYHQ-ByPyAouO.js} +1 -1
  28. package/dist/assets/{chunk-EMLP6XTP-CK4D7Hz5.js → chunk-EMLP6XTP-CJlAXWZg.js} +1 -1
  29. package/dist/assets/{chunk-EX3LRPZG-BmlDFIRW.js → chunk-EX3LRPZG-Dqq4zKJu.js} +1 -1
  30. package/dist/assets/{chunk-FWX5IMBZ-D0ksHZZ9.js → chunk-FWX5IMBZ-B0SNoCmi.js} +2 -2
  31. package/dist/assets/{chunk-HOUHSVGY-DPkq8FCm.js → chunk-HOUHSVGY-DG_GJih_.js} +1 -1
  32. package/dist/assets/{chunk-ICXQ74PX-FAatv5fC.js → chunk-ICXQ74PX-Drxk_6bE.js} +1 -1
  33. package/dist/assets/{chunk-JG7HCLWE-DLkIi4F7.js → chunk-JG7HCLWE-DdLFPpnd.js} +1 -1
  34. package/dist/assets/{chunk-JWPE2WC7-DawOjNE3.js → chunk-JWPE2WC7-B8M4G76F.js} +1 -1
  35. package/dist/assets/{chunk-MOJQB5TN-Di52TorW.js → chunk-MOJQB5TN-XP6FJHY_.js} +1 -1
  36. package/dist/assets/{chunk-MOZMSUNE-PfyK9w1-.js → chunk-MOZMSUNE-C_6x18Qw.js} +1 -1
  37. package/dist/assets/{chunk-OGEWGWER-C3jdH3Ty.js → chunk-OGEWGWER-N25BzCmG.js} +1 -1
  38. package/dist/assets/{chunk-OSBZ3O6U-CecHo-f0.js → chunk-OSBZ3O6U-ASDEm7y0.js} +1 -1
  39. package/dist/assets/{chunk-PUDLZKDR-f3IJh3sN.js → chunk-PUDLZKDR-CTlvAcn7.js} +1 -1
  40. package/dist/assets/{chunk-Q4XR5HBZ-DQFeUPxZ.js → chunk-Q4XR5HBZ-Da9BWfB6.js} +1 -1
  41. package/dist/assets/{chunk-QBLGF6JB-BhUeG0y2.js → chunk-QBLGF6JB-Bi6RpTJO.js} +1 -1
  42. package/dist/assets/{chunk-R7FJI6CG-C3XpZ-2u.js → chunk-R7FJI6CG-DkMsSPvK.js} +1 -1
  43. package/dist/assets/{chunk-RYQCIY6F-CAqa5klQ.js → chunk-RYQCIY6F-DZp0eU-j.js} +1 -1
  44. package/dist/assets/{chunk-U6XO7XAA-D_WXezeD.js → chunk-U6XO7XAA-vfinDSrj.js} +1 -1
  45. package/dist/assets/{chunk-V7JOEXUC-CZ70oOtp.js → chunk-V7JOEXUC-C9Nksccs.js} +1 -1
  46. package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +1 -0
  47. package/dist/assets/{chunk-VR4S4FIN-B7AHEYYk.js → chunk-VR4S4FIN-Xv6zfwXs.js} +1 -1
  48. package/dist/assets/{chunk-WYO6CB5R-Dn9To_OI.js → chunk-WYO6CB5R-mOJNzbWl.js} +2 -2
  49. package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +1 -0
  50. package/dist/assets/{chunk-YOTPTUD7-BYEgRBk3.js → chunk-YOTPTUD7-1-aKSu4o.js} +1 -1
  51. package/dist/assets/{chunk-ZGVPDNZ5-C6YtZDU0.js → chunk-ZGVPDNZ5-OJ8veMpK.js} +1 -1
  52. package/dist/assets/{chunk-ZIRB5QZD-DMuYtnHF.js → chunk-ZIRB5QZD-SR_Nq_PP.js} +1 -1
  53. package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +1 -0
  54. package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +1 -0
  55. package/dist/assets/{config-split-page-BaqLcRxk.js → config-split-page-Bs1wArsX.js} +1 -1
  56. package/dist/assets/{confirm-dialog-DasFoaCT.js → confirm-dialog-BFatCZUq.js} +1 -1
  57. package/dist/assets/{cose-bilkent-JH36ORCC-Bc2Ex5Y3.js → cose-bilkent-JH36ORCC-NjVgz2Bw.js} +1 -1
  58. package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +1 -0
  59. package/dist/assets/{cynefinDiagram-TSTJHNR4-DVysQoGz.js → cynefinDiagram-TSTJHNR4-B_17LDqD.js} +1 -1
  60. package/dist/assets/{dagre-CMhQ1cAG.js → dagre-CMe101gk.js} +1 -1
  61. package/dist/assets/{dagre-VKFMJZFB-RYyWpPJZ.js → dagre-VKFMJZFB-qo8nLb3B.js} +1 -1
  62. package/dist/assets/{desktop-update-config-CoEvKrpB.js → desktop-update-config-DFefhK96.js} +1 -1
  63. package/dist/assets/{diagram-FQU43EPY-BD6KONdF.js → diagram-FQU43EPY-Ba3XIklC.js} +1 -1
  64. package/dist/assets/{diagram-G47NLZAW-CGB2ptRR.js → diagram-G47NLZAW-CG45TcUx.js} +1 -1
  65. package/dist/assets/{diagram-NH7WQ7WH-DnkOI6cU.js → diagram-NH7WQ7WH-2F2HrwV-.js} +1 -1
  66. package/dist/assets/{diagram-OA4YK3LP-DF82RZ7P.js → diagram-OA4YK3LP-CXfWH9NZ.js} +1 -1
  67. package/dist/assets/{diagram-WEI45ONY-Be2v7Ck9.js → diagram-WEI45ONY-CH_4yepc.js} +1 -1
  68. package/dist/assets/{dist-C741xUwt.js → dist-CV9wIyME.js} +1 -1
  69. package/dist/assets/{dist-zOKNc7QQ.js → dist-D1I3blO_.js} +1 -1
  70. package/dist/assets/doc-browser-BFfstBoh.js +1 -0
  71. package/dist/assets/doc-browser-DEdoMb3X.js +1 -0
  72. package/dist/assets/{doc-browser-context-BT8NWQyM.js → doc-browser-context-BcwcVwX_.js} +1 -1
  73. package/dist/assets/{docx-preview-BqwAjrj3.js → docx-preview-CoM4JK7_.js} +1 -1
  74. package/dist/assets/{ebnfDiagram-CCIWWBDH-CP98OYk8.js → ebnfDiagram-CCIWWBDH-BwiG4Ea1.js} +1 -1
  75. package/dist/assets/{erDiagram-Q63AITRT-ZLlx8xfs.js → erDiagram-Q63AITRT-BML3hpnQ.js} +1 -1
  76. package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +1 -0
  77. package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +1 -0
  78. package/dist/assets/{form-actions-BcukaMyR.js → form-actions-YSnn7q_U.js} +1 -1
  79. package/dist/assets/{ganttDiagram-NO4QXBWP-DXCDjHR2.js → ganttDiagram-NO4QXBWP-80enfWbD.js} +1 -1
  80. package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +1 -0
  81. package/dist/assets/{gitGraphDiagram-IHSO6WYX-l2HWrTJp.js → gitGraphDiagram-IHSO6WYX-CVLqXr-b.js} +1 -1
  82. package/dist/assets/index-Bsoscjk7.css +1 -0
  83. package/dist/assets/index-L58BO782.js +109 -0
  84. package/dist/assets/info-DKCQHKI2-DNlNGH90.js +1 -0
  85. package/dist/assets/{infoDiagram-FWYZ7A6U-D-DjX-bn.js → infoDiagram-FWYZ7A6U-Dyvs2t0i.js} +1 -1
  86. package/dist/assets/{ishikawaDiagram-FXEZZL3T-y_Xy128F.js → ishikawaDiagram-FXEZZL3T-sSnxDKfh.js} +1 -1
  87. package/dist/assets/{journeyDiagram-5HDEW3XC-DJn5xYcQ.js → journeyDiagram-5HDEW3XC-D22V71Mf.js} +1 -1
  88. package/dist/assets/{kanban-definition-HUTT4EX6-DJVIOR21.js → kanban-definition-HUTT4EX6-D3ds0p-g.js} +1 -1
  89. package/dist/assets/{line-Cos7PIOj.js → line-CbyFs065.js} +1 -1
  90. package/dist/assets/{linear-BtDLqtzF.js → linear-4DKtnZBS.js} +1 -1
  91. package/dist/assets/loader-circle-CsNiT6VM.js +1 -0
  92. package/dist/assets/{map-BKnQyvd0.js → map-CcZzEoz6.js} +1 -1
  93. package/dist/assets/mcp-marketplace-page-DfUCF-L5.js +1 -0
  94. package/dist/assets/mcp-marketplace-page-ZTHpQTvi.js +9 -0
  95. package/dist/assets/{mermaid-parser.core-XUNZhr2W.js → mermaid-parser.core-DPmMOjXD.js} +2 -2
  96. package/dist/assets/{mermaid.core-ySrvolIe.js → mermaid.core-BSf-ddRa.js} +3 -3
  97. package/dist/assets/{mindmap-definition-LN4V7U3C-BypL9tei.js → mindmap-definition-LN4V7U3C-BUTwWPyx.js} +1 -1
  98. package/dist/assets/model-config-page-jYCwpq4U.js +1 -0
  99. package/dist/assets/{navigation-link-BoMNAKyw.js → navigation-link-7oSjAC9R.js} +1 -1
  100. package/dist/assets/{ordinal-CxxVWxYU.js → ordinal-BgLYtOZE.js} +1 -1
  101. package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +1 -0
  102. package/dist/assets/{pegDiagram-2B236MQR-D-U2KBGv.js → pegDiagram-2B236MQR-BEu_ZBjc.js} +1 -1
  103. package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +1 -0
  104. package/dist/assets/{pieDiagram-ENE6RG2P-CBi3GusJ.js → pieDiagram-ENE6RG2P-CAwAwuBd.js} +1 -1
  105. package/dist/assets/plus-D6UN8XN5.js +1 -0
  106. package/dist/assets/{provider-scoped-model-input-CB-vojwC.js → provider-scoped-model-input-C1CUHVJx.js} +1 -1
  107. package/dist/assets/{providers-config-page-C80nti5H.js → providers-config-page-CMvI4R1e.js} +1 -1
  108. package/dist/assets/{quadrantDiagram-ABIIQ3AL-H0ZuvnZL.js → quadrantDiagram-ABIIQ3AL-CwE6F-_2.js} +1 -1
  109. package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +1 -0
  110. package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +1 -0
  111. package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +1 -0
  112. package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +1 -0
  113. package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +1 -0
  114. package/dist/assets/{railroadDiagram-RFXS5EU6-D7Y2EqSd.js → railroadDiagram-RFXS5EU6-xgLekU3B.js} +1 -1
  115. package/dist/assets/react-7zG0fz1I.js +8 -0
  116. package/dist/assets/{refresh-cw-DZ_Y89ld.js → refresh-cw-BUg7hI3u.js} +1 -1
  117. package/dist/assets/remote-BYPq49kD.js +1 -0
  118. package/dist/assets/{requirementDiagram-TGXJPOKE-BiXNX1NN.js → requirementDiagram-TGXJPOKE-CyVUzEX5.js} +1 -1
  119. package/dist/assets/rotate-cw-B3qmXtfJ.js +1 -0
  120. package/dist/assets/runtime-config-page-DYuYv5yM.js +1 -0
  121. package/dist/assets/{sankeyDiagram-HTMAVEWB-qzDeLztW.js → sankeyDiagram-HTMAVEWB-JK6bFmYB.js} +1 -1
  122. package/dist/assets/{save-BgMfxs5u.js → save-CTCYuZqu.js} +1 -1
  123. package/dist/assets/{search-C7ruoV7u.js → search-CfFUpaf7.js} +1 -1
  124. package/dist/assets/{search-config-page-BTjfOZ6q.js → search-config-page-GUtGbk6i.js} +1 -1
  125. package/dist/assets/{secrets-config-page-BFjiSz73.js → secrets-config-page-Delo8ab0.js} +2 -2
  126. package/dist/assets/security-config-DoVoel5I.js +1 -0
  127. package/dist/assets/{select-BMX2ooE-.js → select-MSvkWmPP.js} +1 -1
  128. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BtRbPQtL.js → sequenceDiagram-DBY2YBRQ-BR5IokXV.js} +1 -1
  129. package/dist/assets/{settings-2-BNQF6hed.js → settings-2-DXUfAWgg.js} +1 -1
  130. package/dist/assets/{sizeCapture-X5ZJPWSS-CMYvM3SL.js → sizeCapture-X5ZJPWSS-SxG_YnMA.js} +1 -1
  131. package/dist/assets/skeleton-DDFivi9Y.js +1 -0
  132. package/dist/assets/{src-Bw-mzbQb.js → src-vgz8E_-O.js} +1 -1
  133. package/dist/assets/{stateDiagram-2N3HPSRC-BJ-mFeOz.js → stateDiagram-2N3HPSRC-DD4OoYhn.js} +1 -1
  134. package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +1 -0
  135. package/dist/assets/{swimlanes-5IMT3BWC-BsucVOO2.js → swimlanes-5IMT3BWC-BWkFeyCT.js} +2 -2
  136. package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +8 -0
  137. package/dist/assets/{tag-chip-BL-UXlVO.js → tag-chip-u8g0b0Nf.js} +1 -1
  138. package/dist/assets/{timeline-definition-FHXFAJF6-Bicewlzo.js → timeline-definition-FHXFAJF6-VTRPy6KA.js} +1 -1
  139. package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +1 -0
  140. package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +1 -0
  141. package/dist/assets/{vennDiagram-L72KCM5P-BxOOnO9x.js → vennDiagram-L72KCM5P-Bvk42Pek.js} +1 -1
  142. package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +1 -0
  143. package/dist/assets/{wardleyDiagram-EHGQE667-Cl7NltMv.js → wardleyDiagram-EHGQE667-DOFjx2s3.js} +1 -1
  144. package/dist/assets/x-DdOEUmR4.js +1 -0
  145. package/dist/assets/{xychartDiagram-FW5EYKEG-DiMIxQ7N.js → xychartDiagram-FW5EYKEG-DY5yRTUc.js} +1 -1
  146. package/dist/index.html +22 -21
  147. package/package.json +9 -9
  148. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +2 -0
  149. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +2 -0
  150. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -0
  151. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +46 -0
  152. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-slash-commands.test.tsx +20 -0
  153. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +138 -54
  154. package/src/features/chat/features/conversation/hooks/use-session-conversation-slash-commands.ts +25 -2
  155. package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +46 -0
  156. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +2 -0
  157. package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +52 -2
  158. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +97 -10
  159. package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +4 -0
  160. package/src/features/chat/features/ncp/hooks/use-ncp-session-queries.ts +10 -0
  161. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +27 -0
  162. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -1
  163. package/src/features/chat/features/workspace/components/__tests__/chat-session-child-sessions.test.tsx +51 -0
  164. package/src/features/chat/features/workspace/components/__tests__/chat-session-token-usage.test.tsx +90 -0
  165. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +9 -17
  166. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +4 -45
  167. package/src/features/chat/features/workspace/components/child-sessions/chat-session-child-sessions.tsx +66 -0
  168. package/src/features/chat/features/workspace/components/overview/chat-session-token-usage.tsx +171 -0
  169. package/src/features/chat/types/chat-message.types.ts +5 -0
  170. package/src/shared/components/doc-browser/__tests__/doc-browser-tab-strip.test.tsx +78 -0
  171. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +71 -3
  172. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +78 -1
  173. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +97 -44
  174. package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +2 -0
  175. package/src/shared/lib/api/ncp-session-query-cache.utils.ts +4 -0
  176. package/src/shared/lib/api/ncp-session.types.ts +3 -0
  177. package/src/shared/lib/api/types.ts +1 -0
  178. package/src/shared/lib/api/utils/ncp-session.utils.ts +8 -0
  179. package/src/shared/lib/i18n/locales/en-US/chat.json +23 -0
  180. package/src/shared/lib/i18n/locales/zh-CN/chat.json +24 -1
  181. package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +0 -1
  182. package/dist/assets/channel-nBvMaVyP.js +0 -1
  183. package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +0 -1
  184. package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +0 -1
  185. package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +0 -1
  186. package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +0 -1
  187. package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +0 -1
  188. package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +0 -1
  189. package/dist/assets/doc-browser-BbirIYbj.js +0 -1
  190. package/dist/assets/doc-browser-Crqog5cn.js +0 -1
  191. package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +0 -1
  192. package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +0 -1
  193. package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +0 -1
  194. package/dist/assets/index-1f4Pv2q3.css +0 -1
  195. package/dist/assets/index-BYWLjYFQ.js +0 -109
  196. package/dist/assets/info-DKCQHKI2-BIporNWb.js +0 -1
  197. package/dist/assets/loader-circle-LVHz84Xj.js +0 -1
  198. package/dist/assets/mcp-marketplace-page-2SsrbaiQ.js +0 -1
  199. package/dist/assets/mcp-marketplace-page-XgYLOAkK.js +0 -9
  200. package/dist/assets/model-config-page-DL8ljJK-.js +0 -1
  201. package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +0 -1
  202. package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +0 -1
  203. package/dist/assets/plus-BeGZHC3f.js +0 -1
  204. package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +0 -1
  205. package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +0 -1
  206. package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +0 -1
  207. package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +0 -1
  208. package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +0 -1
  209. package/dist/assets/react-DND3vEAM.js +0 -8
  210. package/dist/assets/remote-Codkr9SX.js +0 -1
  211. package/dist/assets/rotate-cw-CP3pQ4R8.js +0 -1
  212. package/dist/assets/runtime-config-page-Um1XDqXJ.js +0 -1
  213. package/dist/assets/security-config-Dx-wMb4q.js +0 -1
  214. package/dist/assets/skeleton-CWZ0IO2D.js +0 -1
  215. package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +0 -1
  216. package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +0 -8
  217. package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +0 -1
  218. package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +0 -1
  219. package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +0 -1
  220. package/dist/assets/x-BSu1xwPK.js +0 -1
  221. /package/dist/assets/{array-BQVzeGRt.js → array-NADKd9aU.js} +0 -0
  222. /package/dist/assets/{chunk-KEIR6QF5-f9psyCXK.js → chunk-KEIR6QF5-BuyuzJJL.js} +0 -0
  223. /package/dist/assets/{chunk-Y2CYZVJY-D1mAU_Po.js → chunk-Y2CYZVJY-D_B2ZRNt.js} +0 -0
  224. /package/dist/assets/{config-hints-nV5HPOvq.js → config-hints-BcfVUMij.js} +0 -0
  225. /package/dist/assets/{cytoscape.esm-0uDtdD0l.js → cytoscape.esm-BANTlgza.js} +0 -0
  226. /package/dist/assets/{defaultLocale-DSWXGgt0.js → defaultLocale-C4_L0aEs.js} +0 -0
  227. /package/dist/assets/{dist--ta5rX12.js → dist-DAPEIJbz.js} +0 -0
  228. /package/dist/assets/{graphlib-CQ6QV4yP.js → graphlib-ChqXg0dI.js} +0 -0
  229. /package/dist/assets/{host-capabilities-Tk038i04.js → host-capabilities-D9C4CBK5.js} +0 -0
  230. /package/dist/assets/{init-Y6h9jGtZ.js → init-D8wZg9xq.js} +0 -0
  231. /package/dist/assets/{jszip.min-8hyJpgpx.js → jszip.min-CkbBfwAQ.js} +0 -0
  232. /package/dist/assets/{katex-ARhY9PS1.js → katex-TZJFa0J_.js} +0 -0
  233. /package/dist/assets/{middleware-DXsbf9xI.js → middleware-B3k-TSmZ.js} +0 -0
  234. /package/dist/assets/{path-10BjdyLh.js → path-DodfxcX7.js} +0 -0
  235. /package/dist/assets/{rough.esm-DxgC_14r.js → rough.esm-CUaa1Rbe.js} +0 -0
  236. /package/dist/assets/{xlsx-DrDuDNAa.js → xlsx-CxByi7bR.js} +0 -0
@@ -3,7 +3,10 @@ import { buildNcpRequestEnvelope } from '@nextclaw/ncp-react';
3
3
  import type { UiNcpSessionQueuedInputView } from '@nextclaw/client-sdk';
4
4
  import type { NcpAgentSendEnvelope, NcpRunHandle } from '@nextclaw/ncp';
5
5
 
6
- import { deriveNcpMessagePartsFromComposer } from '@/features/chat/features/input/utils/chat-composer-state.utils';
6
+ import {
7
+ createChatComposerNodesFromDraft,
8
+ deriveNcpMessagePartsFromComposer,
9
+ } from '@/features/chat/features/input/utils/chat-composer-state.utils';
7
10
  import { isNcpChatSendDisabled } from '@/features/chat/features/input/utils/ncp-chat-input-availability.utils';
8
11
  import { buildChatRunMetadata } from '@/features/chat/features/session/utils/chat-run-metadata.utils';
9
12
  import { createNcpSessionId } from '@/features/chat/features/session/utils/ncp-session-adapter.utils';
@@ -74,6 +77,7 @@ type UseSessionConversationControllerParams = {
74
77
 
75
78
  type BuildSubmissionDraftParams = {
76
79
  readonly agentIsSending: boolean;
80
+ readonly composerSnapshot?: ComposerDraftSnapshot;
77
81
  readonly inputSnapshot: SessionConversationInputSnapshot;
78
82
  readonly inputQuery: SessionConversationInputQuery;
79
83
  readonly isRuntimeBlocked: boolean;
@@ -87,6 +91,8 @@ type SubmissionDraft = {
87
91
  readonly metadata: Record<string, unknown>;
88
92
  };
89
93
 
94
+ type SubmissionComposerBehavior = 'preserve' | 'reset-and-restore';
95
+
90
96
  type SubmittingQueuedInput = {
91
97
  readonly id: string;
92
98
  readonly preview: string;
@@ -113,6 +119,7 @@ function buildInputAvailabilitySnapshot(inputQuery: SessionConversationInputQuer
113
119
  function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDraft | null {
114
120
  const {
115
121
  agentIsSending,
122
+ composerSnapshot: requestedComposerSnapshot,
116
123
  inputQuery,
117
124
  inputSnapshot,
118
125
  isRuntimeBlocked,
@@ -120,9 +127,16 @@ function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDra
120
127
  selectedAgentId,
121
128
  sessionKey,
122
129
  } = params;
130
+ const composerSnapshot: ComposerDraftSnapshot = requestedComposerSnapshot ?? {
131
+ text: inputSnapshot.text,
132
+ nodes: [...inputSnapshot.nodes],
133
+ selectedSkills: [...inputSnapshot.selectedSkills],
134
+ skillRecords: [...inputSnapshot.skillRecords],
135
+ attachments: [...inputSnapshot.attachments],
136
+ };
123
137
  const currentParts = deriveNcpMessagePartsFromComposer(
124
- [...inputSnapshot.nodes],
125
- inputSnapshot.attachments,
138
+ [...composerSnapshot.nodes],
139
+ composerSnapshot.attachments,
126
140
  );
127
141
  if (
128
142
  isNcpChatSendDisabled({
@@ -134,13 +148,6 @@ function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDra
134
148
  ) {
135
149
  return null;
136
150
  }
137
- const composerSnapshot: ComposerDraftSnapshot = {
138
- text: inputSnapshot.text,
139
- nodes: [...inputSnapshot.nodes],
140
- selectedSkills: [...inputSnapshot.selectedSkills],
141
- skillRecords: [...inputSnapshot.skillRecords],
142
- attachments: [...inputSnapshot.attachments],
143
- };
144
151
  return {
145
152
  composerSnapshot,
146
153
  metadata: buildChatRunMetadata({
@@ -165,11 +172,11 @@ function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDra
165
172
  : sessionKey
166
173
  ? inputQuery.selectedSession?.projectRoot ?? null
167
174
  : inputSnapshot.pendingProjectRoot,
168
- requestedSkills: [...inputSnapshot.selectedSkills],
175
+ requestedSkills: [...composerSnapshot.selectedSkills],
169
176
  skillRecords: inputQuery.skillRecords.filter((record) =>
170
- inputSnapshot.selectedSkills.includes(record.ref)
177
+ composerSnapshot.selectedSkills.includes(record.ref)
171
178
  ),
172
- composerNodes: [...inputSnapshot.nodes],
179
+ composerNodes: [...composerSnapshot.nodes],
173
180
  sessionMaterialization: materializationContext
174
181
  ? {
175
182
  kind: materializationContext.kind,
@@ -288,6 +295,72 @@ function useQueuedInputActions(params: {
288
295
  return { deleteQueuedInput, editQueuedInput };
289
296
  }
290
297
 
298
+ function useSubmissionDraftRunner(params: {
299
+ readonly agent: SessionConversationAgent;
300
+ readonly clearSubmittingInput: (input: SubmittingQueuedInput) => void;
301
+ readonly materializationContext?: SessionConversationMaterializationContext | null;
302
+ readonly onSessionMaterialized?: (sessionKey: string) => void;
303
+ readonly resetComposer: () => void;
304
+ readonly restoreComposer: (snapshot: ComposerDraftSnapshot) => void;
305
+ readonly runQueue: SessionRunQueue;
306
+ readonly sessionKey: string | null;
307
+ readonly setSendError: (message: string | null) => void;
308
+ readonly stageSubmittingInput: (envelope: NcpAgentSendEnvelope) => SubmittingQueuedInput | null;
309
+ }) {
310
+ const {
311
+ agent,
312
+ clearSubmittingInput,
313
+ materializationContext,
314
+ onSessionMaterialized,
315
+ resetComposer,
316
+ restoreComposer,
317
+ runQueue,
318
+ sessionKey,
319
+ setSendError,
320
+ stageSubmittingInput,
321
+ } = params;
322
+ return useCallback(async (
323
+ draft: SubmissionDraft,
324
+ composerBehavior: SubmissionComposerBehavior,
325
+ ) => {
326
+ const submissionSessionKey =
327
+ sessionKey ?? (materializationContext ? null : createNcpSessionId());
328
+ const envelope = buildSubmissionEnvelope(draft, submissionSessionKey);
329
+ if (!envelope) return;
330
+ const queuedSubmission = agent.isRunning ? stageSubmittingInput(envelope) : null;
331
+ if (composerBehavior === 'reset-and-restore') resetComposer();
332
+ setSendError(null);
333
+ try {
334
+ const handle = await agent.send(envelope);
335
+ if (queuedSubmission && handle?.runId === null) {
336
+ await runQueue.refreshQueuedInputs().catch(() => undefined);
337
+ }
338
+ if (queuedSubmission) clearSubmittingInput(queuedSubmission);
339
+ const materializedSessionKey =
340
+ handle?.sessionId?.trim() || agent.snapshot.activeRun?.sessionId?.trim() || null;
341
+ if (!sessionKey && materializedSessionKey) {
342
+ onSessionMaterialized?.(materializedSessionKey);
343
+ }
344
+ } catch (error) {
345
+ if (queuedSubmission) clearSubmittingInput(queuedSubmission);
346
+ if (composerBehavior === 'reset-and-restore') restoreComposer(draft.composerSnapshot);
347
+ setSendError(error instanceof Error ? error.message : String(error));
348
+ throw error;
349
+ }
350
+ }, [
351
+ agent,
352
+ clearSubmittingInput,
353
+ materializationContext,
354
+ onSessionMaterialized,
355
+ resetComposer,
356
+ restoreComposer,
357
+ runQueue,
358
+ sessionKey,
359
+ setSendError,
360
+ stageSubmittingInput,
361
+ ]);
362
+ }
363
+
291
364
  export function useSessionConversationController(params: UseSessionConversationControllerParams) {
292
365
  const {
293
366
  agent,
@@ -343,6 +416,19 @@ export function useSessionConversationController(params: UseSessionConversationC
343
416
  setSendError,
344
417
  });
345
418
 
419
+ const submitDraft = useSubmissionDraftRunner({
420
+ agent,
421
+ clearSubmittingInput,
422
+ materializationContext,
423
+ onSessionMaterialized,
424
+ resetComposer,
425
+ restoreComposer,
426
+ runQueue,
427
+ sessionKey,
428
+ setSendError,
429
+ stageSubmittingInput,
430
+ });
431
+
346
432
  const send = useCallback(async () => {
347
433
  if (primaryAction === 'continue') {
348
434
  await continueRun();
@@ -357,58 +443,55 @@ export function useSessionConversationController(params: UseSessionConversationC
357
443
  selectedAgentId,
358
444
  sessionKey,
359
445
  });
360
- if (!draft) {
361
- return;
446
+ if (draft) {
447
+ await submitDraft(draft, 'reset-and-restore');
362
448
  }
363
- const submissionSessionKey =
364
- sessionKey ?? (materializationContext ? null : createNcpSessionId());
365
- const envelope = buildSubmissionEnvelope(draft, submissionSessionKey);
366
- if (!envelope) {
449
+ }, [
450
+ agent.isSending,
451
+ continueRun,
452
+ inputQuery,
453
+ inputSnapshot,
454
+ isRuntimeBlocked,
455
+ materializationContext,
456
+ primaryAction,
457
+ selectedAgentId,
458
+ sessionKey,
459
+ submitDraft,
460
+ ]);
461
+
462
+ const sendPresetMessage = useCallback(async (message: string) => {
463
+ const text = message.trim();
464
+ if (!text) {
367
465
  return;
368
466
  }
369
- const queuedSubmission = agent.isRunning ? stageSubmittingInput(envelope) : null;
370
- resetComposer();
371
- setSendError(null);
372
- try {
373
- const handle = await agent.send(envelope);
374
- if (queuedSubmission && handle?.runId === null) {
375
- await runQueue.refreshQueuedInputs().catch(() => undefined);
376
- }
377
- if (queuedSubmission) {
378
- clearSubmittingInput(queuedSubmission);
379
- }
380
- const materializedSessionKey =
381
- handle?.sessionId?.trim() ||
382
- agent.snapshot.activeRun?.sessionId?.trim() ||
383
- null;
384
- if (!sessionKey && materializedSessionKey) {
385
- onSessionMaterialized?.(materializedSessionKey);
386
- }
387
- } catch (error) {
388
- if (queuedSubmission) {
389
- clearSubmittingInput(queuedSubmission);
390
- }
391
- restoreComposer(draft.composerSnapshot);
392
- setSendError(error instanceof Error ? error.message : String(error));
393
- throw error;
467
+ const draft = buildSubmissionDraft({
468
+ agentIsSending: agent.isSending,
469
+ composerSnapshot: {
470
+ text,
471
+ nodes: createChatComposerNodesFromDraft(text),
472
+ selectedSkills: [],
473
+ skillRecords: [],
474
+ attachments: [],
475
+ },
476
+ inputSnapshot,
477
+ inputQuery,
478
+ isRuntimeBlocked,
479
+ materializationContext,
480
+ selectedAgentId,
481
+ sessionKey,
482
+ });
483
+ if (draft) {
484
+ await submitDraft(draft, 'preserve');
394
485
  }
395
486
  }, [
396
- agent,
397
- clearSubmittingInput,
398
- continueRun,
487
+ agent.isSending,
399
488
  inputQuery,
400
489
  inputSnapshot,
401
490
  isRuntimeBlocked,
402
491
  materializationContext,
403
- onSessionMaterialized,
404
- resetComposer,
405
- restoreComposer,
406
492
  selectedAgentId,
407
493
  sessionKey,
408
- setSendError,
409
- stageSubmittingInput,
410
- primaryAction,
411
- runQueue,
494
+ submitDraft,
412
495
  ]);
413
496
 
414
497
  const stop = useCallback(async () => {
@@ -426,6 +509,7 @@ export function useSessionConversationController(params: UseSessionConversationC
426
509
  isSending,
427
510
  queuedInputs,
428
511
  send,
512
+ sendPresetMessage,
429
513
  sendDisabled,
430
514
  primaryAction,
431
515
  stop,
@@ -8,9 +8,15 @@ import type { ChatSlashCommandDescriptor } from '@/features/chat/features/input/
8
8
  export function useSessionConversationSlashCommands(params: {
9
9
  language: I18nLanguage;
10
10
  onContextCompactingChange?: (sessionId: string, isCompacting: boolean) => void;
11
+ onSendPresetMessage: (message: string) => Promise<void> | void;
11
12
  selectedSessionKey?: string | null;
12
13
  }): readonly ChatSlashCommandDescriptor[] {
13
- const { language, onContextCompactingChange, selectedSessionKey } = params;
14
+ const {
15
+ language,
16
+ onContextCompactingChange,
17
+ onSendPresetMessage,
18
+ selectedSessionKey,
19
+ } = params;
14
20
  const presenter = usePresenter();
15
21
  const compactingSessionIdsRef = useRef(new Set<string>());
16
22
  const compactContext = useCallback(async (sessionId: string) => {
@@ -46,6 +52,17 @@ export function useSessionConversationSlashCommands(params: {
46
52
  keywords: ['side', 'chat', 'child', 'branch', 'new'],
47
53
  onSelect: () => presenter.chatThreadManager.openSideChatDraft(sessionId),
48
54
  },
55
+ {
56
+ key: 'update-session-title',
57
+ icon: 'command',
58
+ title: t('chatSlashCommandUpdateSessionTitleTitle', language),
59
+ description: t('chatSlashCommandUpdateSessionTitleDescription', language),
60
+ detailLines: [t('chatSlashCommandUpdateSessionTitleDetail', language)],
61
+ keywords: ['update', 'rename', 'title', 'session', '更新', '重命名', '标题', '会话'],
62
+ onSelect: () => void onSendPresetMessage(
63
+ t('chatSlashCommandUpdateSessionTitlePrompt', language),
64
+ ),
65
+ },
49
66
  {
50
67
  key: 'compact-context',
51
68
  icon: 'list-collapse',
@@ -56,5 +73,11 @@ export function useSessionConversationSlashCommands(params: {
56
73
  onSelect: () => void compactContext(sessionId),
57
74
  },
58
75
  ];
59
- }, [compactContext, language, presenter.chatThreadManager, selectedSessionKey]);
76
+ }, [
77
+ compactContext,
78
+ language,
79
+ onSendPresetMessage,
80
+ presenter.chatThreadManager,
81
+ selectedSessionKey,
82
+ ]);
60
83
  }
@@ -237,3 +237,49 @@ describe("buildShowContentToolCard", () => {
237
237
  });
238
238
  });
239
239
  });
240
+
241
+ describe("buildToolCard terminal outcome and timing", () => {
242
+ it.each([
243
+ [{ command: "false", ok: false, exitCode: 1 }, "error", "Failed"],
244
+ [{ command: "rm -rf /", blocked: true, status: "blocked" }, "error", "Failed"],
245
+ [{ command: "sleep 5", status: "cancelled" }, "cancelled", "Cancelled"],
246
+ [{ command: "pwd", status: "completed", exit_code: 0 }, "success", "Completed"],
247
+ ] as const)("normalizes terminal result %o", (outputData, statusTone, statusLabel) => {
248
+ expect(buildToolCard({
249
+ kind: "result",
250
+ name: "commandExecution",
251
+ detail: "command: pwd",
252
+ hasResult: true,
253
+ statusTone: "success",
254
+ statusLabel: "Completed",
255
+ outputData,
256
+ }, TEXTS)).toMatchObject({ statusTone, statusLabel });
257
+ });
258
+
259
+ it("only exposes standard execution timing and preserves the stable tool call identity", () => {
260
+ const card = buildToolCard({
261
+ kind: "result",
262
+ name: "exec",
263
+ detail: "command: pnpm test",
264
+ hasResult: true,
265
+ statusTone: "success",
266
+ statusLabel: "Completed",
267
+ toolCallId: "tool-timing-1",
268
+ outputData: { command: "pnpm test", durationMs: 999_999 },
269
+ execution: { durationMs: 4270 },
270
+ }, TEXTS);
271
+ expect(card).toMatchObject({
272
+ toolCallId: "tool-timing-1",
273
+ execution: { durationMs: 4270 },
274
+ });
275
+ expect(buildToolCard({
276
+ kind: "result",
277
+ name: "exec",
278
+ detail: "command: pnpm test",
279
+ hasResult: true,
280
+ statusTone: "success",
281
+ statusLabel: "Completed",
282
+ outputData: { command: "pnpm test", durationMs: 999_999 },
283
+ }, TEXTS).execution).toBeUndefined();
284
+ });
285
+ });
@@ -179,6 +179,8 @@ function buildToolInvocationPart(
179
179
  text: shouldShowRawResult && rawResult ? rawResult : undefined,
180
180
  outputData: invocation.result,
181
181
  callId: invocation.toolCallId || undefined,
182
+ toolCallId: invocation.toolCallId || undefined,
183
+ ...(invocation.execution ? { execution: invocation.execution } : {}),
182
184
  hasResult: statusView.hasResult,
183
185
  statusTone: invalidArgsError ? "error" : statusView.statusTone,
184
186
  statusLabel: invalidArgsError ? texts.toolStatusFailedLabel : statusView.statusLabel,
@@ -15,6 +15,8 @@ export type ToolCardViewSource = ToolCard & {
15
15
  action?: ChatToolPartViewModel["action"];
16
16
  fileOperation?: ChatToolPartViewModel["fileOperation"];
17
17
  outputData?: unknown;
18
+ execution?: ChatToolPartViewModel["execution"];
19
+ toolCallId?: string;
18
20
  panelApp?: ChatToolPartViewModel["panelApp"];
19
21
  };
20
22
 
@@ -62,6 +64,43 @@ export function isTerminalResultRecord(
62
64
  );
63
65
  }
64
66
 
67
+ function readTerminalStatus(value: unknown): string {
68
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
69
+ }
70
+
71
+ export function resolveTerminalResultStatus(
72
+ value: unknown,
73
+ ): "success" | "error" | "cancelled" | null {
74
+ if (!isTerminalResultRecord(value)) return null;
75
+ const status = readTerminalStatus(value.status ?? value.outcome);
76
+ if (
77
+ value.cancelled === true ||
78
+ value.aborted === true ||
79
+ status === "cancelled" ||
80
+ status === "canceled" ||
81
+ status === "aborted" ||
82
+ status === "interrupted"
83
+ ) {
84
+ return "cancelled";
85
+ }
86
+ const exitCode = readOptionalNumber(value.exitCode ?? value.exit_code);
87
+ if (
88
+ value.ok === false ||
89
+ value.blocked === true ||
90
+ value.timedOut === true ||
91
+ value.killed === true ||
92
+ status === "error" ||
93
+ status === "failed" ||
94
+ status === "blocked" ||
95
+ status === "declined" ||
96
+ status === "timed_out" ||
97
+ (exitCode != null && exitCode !== 0)
98
+ ) {
99
+ return "error";
100
+ }
101
+ return "success";
102
+ }
103
+
65
104
  export function extractAssetFileView(
66
105
  value: unknown,
67
106
  texts: ChatMessageAdapterTexts,
@@ -107,9 +146,19 @@ export function buildToolCard(
107
146
  toolCard: ToolCardViewSource,
108
147
  texts: ChatMessageAdapterTexts,
109
148
  ): ChatToolPartViewModel {
149
+ const terminalStatus = toolCard.statusTone === "success"
150
+ ? resolveTerminalResultStatus(toolCard.outputData)
151
+ : null;
152
+ const statusTone = terminalStatus ?? toolCard.statusTone;
153
+ const statusLabel = statusTone === "error"
154
+ ? texts.toolStatusFailedLabel
155
+ : statusTone === "cancelled"
156
+ ? texts.toolStatusCancelledLabel
157
+ : toolCard.statusLabel;
110
158
  return {
111
159
  kind: toolCard.kind,
112
160
  toolName: toolCard.name,
161
+ ...(toolCard.toolCallId ? { toolCallId: toolCard.toolCallId } : {}),
113
162
  ...("agentId" in toolCard && toolCard.agentId
114
163
  ? { agentId: toolCard.agentId }
115
164
  : {}),
@@ -121,9 +170,10 @@ export function buildToolCard(
121
170
  : undefined,
122
171
  output: toolCard.text,
123
172
  outputData: toolCard.outputData,
173
+ ...(toolCard.execution ? { execution: { ...toolCard.execution } } : {}),
124
174
  hasResult: Boolean(toolCard.hasResult),
125
- statusTone: toolCard.statusTone,
126
- statusLabel: toolCard.statusLabel,
175
+ statusTone,
176
+ statusLabel,
127
177
  titleLabel:
128
178
  toolCard.kind === "call" ? texts.toolCallLabel : texts.toolResultLabel,
129
179
  outputLabel: texts.toolOutputLabel,
@@ -85,7 +85,10 @@ describe("useHydratedNcpAgent", () => {
85
85
  await waitFor(() => {
86
86
  expect(result.current.isHydrating).toBe(false);
87
87
  });
88
- expect(mocks.stream).toHaveBeenCalledWith({ sessionId: "session-a" });
88
+ expect(mocks.stream).toHaveBeenCalledWith(
89
+ { sessionId: "session-a" },
90
+ expect.objectContaining({ onOpen: expect.any(Function) }),
91
+ );
89
92
 
90
93
  rerender({ sessionId: "session-b" });
91
94
 
@@ -94,7 +97,10 @@ describe("useHydratedNcpAgent", () => {
94
97
  await waitFor(() => {
95
98
  expect(result.current.isHydrating).toBe(false);
96
99
  });
97
- expect(mocks.stream).toHaveBeenCalledWith({ sessionId: "session-b" });
100
+ expect(mocks.stream).toHaveBeenCalledWith(
101
+ { sessionId: "session-b" },
102
+ expect.objectContaining({ onOpen: expect.any(Function) }),
103
+ );
98
104
  expect(mocks.stream).toHaveBeenCalledTimes(2);
99
105
  });
100
106
 
@@ -207,12 +213,13 @@ describe("useHydratedNcpAgent", () => {
207
213
  expect(result.current.isHydrating).toBe(false);
208
214
  });
209
215
  expect(loadSeed).not.toHaveBeenCalled();
210
- expect(mocks.stream).toHaveBeenCalledWith({
211
- sessionId: "session-materialized",
212
- });
216
+ expect(mocks.stream).toHaveBeenCalledWith(
217
+ { sessionId: "session-materialized" },
218
+ expect.objectContaining({ onOpen: expect.any(Function) }),
219
+ );
213
220
  });
214
221
 
215
- it("reloads missed state and reconnects after the live stream disconnects", async () => {
222
+ it("reloads missed state and remains usable after repeated live stream disconnects", async () => {
216
223
  vi.useFakeTimers();
217
224
  try {
218
225
  const connected = new Promise<void>(() => {});
@@ -220,9 +227,15 @@ describe("useHydratedNcpAgent", () => {
220
227
  stop: mocks.stop.mockResolvedValue(undefined),
221
228
  stream: mocks.stream
222
229
  .mockRejectedValueOnce(new Error("stream disconnected"))
230
+ .mockRejectedValueOnce(new Error("stream disconnected again"))
223
231
  .mockImplementationOnce(() => connected),
224
232
  subscribe: vi.fn(() => () => {}),
225
- send: mocks.send,
233
+ send: mocks.send.mockResolvedValue({
234
+ sessionId: "session-recovery",
235
+ userMessageId: "user-after-recovery",
236
+ assistantMessageId: null,
237
+ runId: null,
238
+ }),
226
239
  } as unknown as NcpAgentClientEndpoint;
227
240
  const recoveredMessage = {
228
241
  id: "message-recovered",
@@ -235,6 +248,7 @@ describe("useHydratedNcpAgent", () => {
235
248
  const loadSeed = vi
236
249
  .fn()
237
250
  .mockResolvedValueOnce({ messages: [], status: "running" })
251
+ .mockResolvedValueOnce({ messages: [], status: "running" })
238
252
  .mockResolvedValueOnce({
239
253
  messages: [recoveredMessage],
240
254
  status: "idle",
@@ -254,15 +268,22 @@ describe("useHydratedNcpAgent", () => {
254
268
  await act(async () => {
255
269
  await vi.runOnlyPendingTimersAsync();
256
270
  });
271
+ await vi.waitFor(() => expect(mocks.stream).toHaveBeenCalledTimes(2));
272
+ await act(async () => {
273
+ await vi.runOnlyPendingTimersAsync();
274
+ });
257
275
  await vi.waitFor(() => {
258
276
  expect(result.current.visibleMessages).toEqual([recoveredMessage]);
259
277
  });
260
278
 
261
- expect(loadSeed).toHaveBeenCalledTimes(2);
262
- expect(mocks.stream).toHaveBeenCalledTimes(2);
263
- expect(mocks.send).not.toHaveBeenCalled();
279
+ expect(loadSeed).toHaveBeenCalledTimes(3);
280
+ expect(mocks.stream).toHaveBeenCalledTimes(3);
264
281
  expect(result.current.hydrateError).toBeNull();
265
282
  expect(result.current.isRunning).toBe(false);
283
+ await act(async () => {
284
+ await result.current.send("after recovery");
285
+ });
286
+ expect(mocks.send).toHaveBeenCalledOnce();
266
287
  } finally {
267
288
  vi.useRealTimers();
268
289
  }
@@ -306,3 +327,69 @@ describe("useHydratedNcpAgent", () => {
306
327
  }
307
328
  });
308
329
  });
330
+
331
+ describe("useHydratedNcpAgent stream reconciliation", () => {
332
+ it("reconciles history after the stream opens without overwriting newer live completion", async () => {
333
+ let subscriber: NcpEndpointSubscriber | null = null;
334
+ let resolveReconcile: ((seed: { messages: readonly NcpMessage[]; status: "running" }) => void) | null = null;
335
+ const reconcileSeed = new Promise<{ messages: readonly NcpMessage[]; status: "running" }>((resolve) => {
336
+ resolveReconcile = resolve;
337
+ });
338
+ const client = {
339
+ stop: mocks.stop.mockResolvedValue(undefined),
340
+ stream: mocks.stream.mockImplementation((_payload, observer) => {
341
+ observer?.onOpen?.();
342
+ return new Promise<void>(() => {});
343
+ }),
344
+ subscribe: vi.fn((nextSubscriber: NcpEndpointSubscriber) => {
345
+ subscriber = nextSubscriber;
346
+ return () => {};
347
+ }),
348
+ } as unknown as NcpAgentClientEndpoint;
349
+ const loadSeed = vi
350
+ .fn()
351
+ .mockResolvedValueOnce({ messages: [], status: "running" })
352
+ .mockImplementationOnce(() => reconcileSeed);
353
+ const finalMessage: NcpMessage = {
354
+ id: "assistant-gap",
355
+ sessionId: "session-gap",
356
+ role: "assistant",
357
+ status: "final",
358
+ parts: [{ type: "text", text: "Recovered without refresh" }],
359
+ timestamp: "2026-08-14T00:00:00.000Z",
360
+ };
361
+ const { result } = renderHook(() =>
362
+ useHydratedNcpAgent({
363
+ sessionId: "session-gap",
364
+ client,
365
+ loadSeed,
366
+ }),
367
+ );
368
+
369
+ await waitFor(() => expect(loadSeed).toHaveBeenCalledTimes(2));
370
+ act(() => {
371
+ subscriber?.({
372
+ type: NcpEventType.MessageSent,
373
+ payload: { sessionId: "session-gap", message: finalMessage },
374
+ });
375
+ subscriber?.({
376
+ type: NcpEventType.RunFinished,
377
+ payload: { sessionId: "session-gap", runId: "run-gap" },
378
+ });
379
+ });
380
+ await waitFor(() => expect(result.current.visibleMessages).toEqual([finalMessage]));
381
+
382
+ await act(async () => {
383
+ resolveReconcile?.({
384
+ messages: [{ ...finalMessage, status: "streaming", parts: [{ type: "text", text: "Recovered" }] }],
385
+ status: "running",
386
+ });
387
+ await Promise.resolve();
388
+ });
389
+
390
+ await waitFor(() => {
391
+ expect(result.current.visibleMessages).toEqual([finalMessage]);
392
+ expect(result.current.isRunning).toBe(false);
393
+ });
394
+ });
395
+ });
@@ -19,6 +19,8 @@ import { nextclawClient } from "@/shared/lib/api";
19
19
 
20
20
  const NCP_AGENT_UNAVAILABLE_DURING_STARTUP =
21
21
  "ncp agent unavailable during startup";
22
+ const NCP_AGENT_STREAM_OPEN_TIMEOUT_MS = 15_000;
23
+ const NCP_AGENT_STREAM_IDLE_TIMEOUT_MS = 70_000;
22
24
 
23
25
  export { fetchNcpSessionConversationSeed } from "@/features/chat/features/ncp/hooks/use-ncp-session-message-history";
24
26
 
@@ -42,6 +44,8 @@ export function createNcpSessionConversationClient(): NcpHttpAgentClientEndpoint
42
44
  baseUrl: API_BASE,
43
45
  basePath: "/api/ncp/agent",
44
46
  fetchImpl: createNcpAppClientFetch(),
47
+ streamOpenTimeoutMs: NCP_AGENT_STREAM_OPEN_TIMEOUT_MS,
48
+ streamIdleTimeoutMs: NCP_AGENT_STREAM_IDLE_TIMEOUT_MS,
45
49
  });
46
50
  }
47
51
 
@@ -1,6 +1,7 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
2
  import {
3
3
  fetchNcpSessionSkills,
4
+ fetchNcpSessionTokenUsage,
4
5
  fetchNcpSessions
5
6
  } from '@/shared/lib/api';
6
7
 
@@ -30,3 +31,12 @@ export function useNcpSessionSkills(params: {
30
31
  ...ncpSessionQueryDefaults
31
32
  });
32
33
  }
34
+
35
+ export function useNcpSessionTokenUsage(sessionId: string | null) {
36
+ return useQuery({
37
+ queryKey: ['ncp-session-token-usage', sessionId],
38
+ queryFn: () => fetchNcpSessionTokenUsage(sessionId as string),
39
+ enabled: Boolean(sessionId),
40
+ ...ncpSessionQueryDefaults
41
+ });
42
+ }