@nextclaw/ui 0.17.0 → 0.17.2

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 (241) hide show
  1. package/CHANGELOG.md +28 -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-CVjdSvJk.js → api-CuPWhNDM.js} +3 -3
  5. package/dist/assets/{appearance-settings-page-BOorfaUx.js → appearance-settings-page-DRjg6UP3.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-B4ovWX14.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-DuZJvYB6.js → channels-list-page-B26GXq63.js} +2 -2
  14. package/dist/assets/{chat-page-8PTMgI0t.js → chat-page-QA0dpNyU.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-UuhsVcQD.js → config-split-page-Box_-aBK.js} +1 -1
  56. package/dist/assets/{confirm-dialog-Bnosa8UF.js → confirm-dialog-uqcCxomz.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-CM4lsX4R.js → desktop-update-config-DkDor159.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-D17RZAsz.js → dist-CZLIZntJ.js} +1 -1
  69. package/dist/assets/{dist-_zZ8P38T.js → dist-Co2G-YjN.js} +1 -1
  70. package/dist/assets/doc-browser-CbCBDdap.js +1 -0
  71. package/dist/assets/doc-browser-DUqckL2E.js +1 -0
  72. package/dist/assets/{doc-browser-context-CerkiK_N.js → doc-browser-context-aeZu0FHR.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-Cmv23dN4.js → form-actions-C3Pa1HQb.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-CCWgeWoR.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-DfbWUPdI.js +1 -0
  92. package/dist/assets/{map-BKnQyvd0.js → map-CcZzEoz6.js} +1 -1
  93. package/dist/assets/mcp-marketplace-page-B2D7ciJL.js +1 -0
  94. package/dist/assets/mcp-marketplace-page-BmqbIQQQ.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-BXUoZEEZ.js +1 -0
  99. package/dist/assets/{navigation-link-k9rsmtC4.js → navigation-link-DngetDmN.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-wNpOxBfP.js +1 -0
  106. package/dist/assets/{provider-scoped-model-input-DLSAB6a5.js → provider-scoped-model-input-C4a2TuV7.js} +1 -1
  107. package/dist/assets/{providers-config-page-DM5N0aeS.js → providers-config-page-CHSka1kQ.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-BgRLh3ee.js → react-CPH0zySO.js} +1 -1
  116. package/dist/assets/{refresh-cw-axvJLmRG.js → refresh-cw-vTYZqC0b.js} +1 -1
  117. package/dist/assets/remote-DgfRmokf.js +1 -0
  118. package/dist/assets/{requirementDiagram-TGXJPOKE-BiXNX1NN.js → requirementDiagram-TGXJPOKE-CyVUzEX5.js} +1 -1
  119. package/dist/assets/rotate-cw-L_seGUwq.js +1 -0
  120. package/dist/assets/runtime-config-page-V75_zqXa.js +1 -0
  121. package/dist/assets/{sankeyDiagram-HTMAVEWB-qzDeLztW.js → sankeyDiagram-HTMAVEWB-JK6bFmYB.js} +1 -1
  122. package/dist/assets/{save-DbR4SGk8.js → save-lWb6TI69.js} +1 -1
  123. package/dist/assets/{search-M9lrb8id.js → search-BuUX_0KY.js} +1 -1
  124. package/dist/assets/{search-config-page-DmsID8FQ.js → search-config-page-rlkRJZCF.js} +1 -1
  125. package/dist/assets/{secrets-config-page-C10OwI0b.js → secrets-config-page-BxCEc8r5.js} +2 -2
  126. package/dist/assets/security-config-Cqxe13za.js +1 -0
  127. package/dist/assets/{select-D_YBzMdv.js → select-oalsltZV.js} +1 -1
  128. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BtRbPQtL.js → sequenceDiagram-DBY2YBRQ-BR5IokXV.js} +1 -1
  129. package/dist/assets/{settings-2-zy40UwTo.js → settings-2-Bd9xiNAY.js} +1 -1
  130. package/dist/assets/{sizeCapture-X5ZJPWSS-CMYvM3SL.js → sizeCapture-X5ZJPWSS-SxG_YnMA.js} +1 -1
  131. package/dist/assets/skeleton-D72EdBNa.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-D7QRdcph.js → tag-chip-B324d_Xk.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-Cps-je1K.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/apps/components/__tests__/app-packages-panel.test.tsx +43 -10
  149. package/src/features/apps/components/__tests__/apps-panel.test.tsx +18 -2
  150. package/src/features/apps/components/app-package-card.tsx +58 -4
  151. package/src/features/apps/components/app-packages-panel.tsx +60 -22
  152. package/src/features/apps/components/apps-panel.tsx +14 -3
  153. package/src/features/apps/hooks/use-app-packages.ts +5 -2
  154. package/src/features/apps/managers/__tests__/app-package-operation-settlement.manager.test.ts +5 -0
  155. package/src/features/apps/managers/app-package-operation-settlement.manager.ts +8 -2
  156. package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +46 -0
  157. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +2 -0
  158. package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +52 -2
  159. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +97 -10
  160. package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +4 -0
  161. package/src/features/chat/features/ncp/hooks/use-ncp-session-queries.ts +10 -0
  162. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +27 -0
  163. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -1
  164. package/src/features/chat/features/workspace/components/__tests__/chat-session-child-sessions.test.tsx +51 -0
  165. package/src/features/chat/features/workspace/components/__tests__/chat-session-token-usage.test.tsx +90 -0
  166. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +9 -17
  167. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +4 -45
  168. package/src/features/chat/features/workspace/components/child-sessions/chat-session-child-sessions.tsx +66 -0
  169. package/src/features/chat/features/workspace/components/overview/chat-session-token-usage.tsx +171 -0
  170. package/src/features/chat/types/chat-message.types.ts +5 -0
  171. package/src/features/service-apps/components/__tests__/service-apps-panel.test.tsx +32 -4
  172. package/src/features/service-apps/components/service-app-list-item.tsx +26 -12
  173. package/src/features/service-apps/components/service-apps-panel.tsx +23 -1
  174. package/src/shared/components/doc-browser/__tests__/doc-browser-tab-strip.test.tsx +78 -0
  175. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +71 -3
  176. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +78 -1
  177. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +97 -44
  178. package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +2 -0
  179. package/src/shared/lib/api/ncp-session-query-cache.utils.ts +4 -0
  180. package/src/shared/lib/api/ncp-session.types.ts +3 -0
  181. package/src/shared/lib/api/types.ts +1 -0
  182. package/src/shared/lib/api/utils/ncp-session.utils.ts +8 -0
  183. package/src/shared/lib/i18n/locales/en-US/chat.json +19 -0
  184. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +3 -0
  185. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -1
  186. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +3 -0
  187. package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +0 -1
  188. package/dist/assets/channel-nBvMaVyP.js +0 -1
  189. package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +0 -1
  190. package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +0 -1
  191. package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +0 -1
  192. package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +0 -1
  193. package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +0 -1
  194. package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +0 -1
  195. package/dist/assets/doc-browser-CiLvybSC.js +0 -1
  196. package/dist/assets/doc-browser-z7vdH3Ez.js +0 -1
  197. package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +0 -1
  198. package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +0 -1
  199. package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +0 -1
  200. package/dist/assets/index-CWdgBpSc.css +0 -1
  201. package/dist/assets/index-DlL8uKXu.js +0 -109
  202. package/dist/assets/info-DKCQHKI2-BIporNWb.js +0 -1
  203. package/dist/assets/loader-circle-vUsFtTfb.js +0 -1
  204. package/dist/assets/mcp-marketplace-page-BV8WB3_L.js +0 -1
  205. package/dist/assets/mcp-marketplace-page-DN_fqcLd.js +0 -9
  206. package/dist/assets/model-config-page-BxaeCMbJ.js +0 -1
  207. package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +0 -1
  208. package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +0 -1
  209. package/dist/assets/plus-Dc760kHj.js +0 -1
  210. package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +0 -1
  211. package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +0 -1
  212. package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +0 -1
  213. package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +0 -1
  214. package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +0 -1
  215. package/dist/assets/remote-Cj1GBMaN.js +0 -1
  216. package/dist/assets/rotate-cw-CFByP2O8.js +0 -1
  217. package/dist/assets/runtime-config-page-WNzZyjMW.js +0 -1
  218. package/dist/assets/security-config-CvU5JxDT.js +0 -1
  219. package/dist/assets/skeleton-BmAtzjDM.js +0 -1
  220. package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +0 -1
  221. package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +0 -8
  222. package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +0 -1
  223. package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +0 -1
  224. package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +0 -1
  225. package/dist/assets/x-D9Y9hf_r.js +0 -1
  226. /package/dist/assets/{array-BQVzeGRt.js → array-NADKd9aU.js} +0 -0
  227. /package/dist/assets/{chunk-KEIR6QF5-f9psyCXK.js → chunk-KEIR6QF5-BuyuzJJL.js} +0 -0
  228. /package/dist/assets/{chunk-Y2CYZVJY-D1mAU_Po.js → chunk-Y2CYZVJY-D_B2ZRNt.js} +0 -0
  229. /package/dist/assets/{config-hints-nV5HPOvq.js → config-hints-BcfVUMij.js} +0 -0
  230. /package/dist/assets/{cytoscape.esm-0uDtdD0l.js → cytoscape.esm-BANTlgza.js} +0 -0
  231. /package/dist/assets/{defaultLocale-DSWXGgt0.js → defaultLocale-C4_L0aEs.js} +0 -0
  232. /package/dist/assets/{dist--ta5rX12.js → dist-DAPEIJbz.js} +0 -0
  233. /package/dist/assets/{graphlib-CQ6QV4yP.js → graphlib-ChqXg0dI.js} +0 -0
  234. /package/dist/assets/{host-capabilities-Tk038i04.js → host-capabilities-D9C4CBK5.js} +0 -0
  235. /package/dist/assets/{init-Y6h9jGtZ.js → init-D8wZg9xq.js} +0 -0
  236. /package/dist/assets/{jszip.min-8hyJpgpx.js → jszip.min-CkbBfwAQ.js} +0 -0
  237. /package/dist/assets/{katex-ARhY9PS1.js → katex-TZJFa0J_.js} +0 -0
  238. /package/dist/assets/{middleware-DXsbf9xI.js → middleware-B3k-TSmZ.js} +0 -0
  239. /package/dist/assets/{path-10BjdyLh.js → path-DodfxcX7.js} +0 -0
  240. /package/dist/assets/{rough.esm-DxgC_14r.js → rough.esm-CUaa1Rbe.js} +0 -0
  241. /package/dist/assets/{xlsx-DrDuDNAa.js → xlsx-CxByi7bR.js} +0 -0
@@ -1,5 +1,5 @@
1
- import { useMemo, useRef, useState, type FormEvent } from 'react';
2
- import type { AppPackageOperationView, AppPackageView } from '@nextclaw/client-sdk';
1
+ import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
2
+ import type { AppDataEntry, AppPackageOperationView, AppPackageView } from '@nextclaw/client-sdk';
3
3
  import { LoaderCircle, PackagePlus, RefreshCw, ShieldCheck, Store } from 'lucide-react';
4
4
  import { AppArtwork } from '@/features/apps/components/app-artwork';
5
5
  import type { PanelAppEntryView } from '@/shared/lib/api';
@@ -34,8 +34,10 @@ import { Skeleton } from '@/shared/components/ui/skeleton';
34
34
  import { t } from '@/shared/lib/i18n';
35
35
 
36
36
  export function AppPackagesPanel({
37
+ focusedPackageId,
37
38
  onOpenPanelApp,
38
39
  }: {
40
+ focusedPackageId?: string;
39
41
  onOpenPanelApp: (entry: PanelAppEntryView) => void;
40
42
  }) {
41
43
  const appPackages = useAppPackages();
@@ -56,11 +58,19 @@ export function AppPackagesPanel({
56
58
  [appPackageOperations.data?.entries],
57
59
  );
58
60
 
61
+ useEffect(() => {
62
+ if (!focusedPackageId || appPackages.isLoading) return;
63
+ const target = document.getElementById(`app-package-${focusedPackageId}`);
64
+ target?.scrollIntoView({ block: 'nearest' });
65
+ target?.focus({ preventScroll: true });
66
+ }, [appPackages.data, appPackages.isLoading, focusedPackageId]);
67
+
59
68
  useAppPackageOperationSettlement({
60
69
  isLoaded: Boolean(appPackageOperations.data),
61
70
  operations,
62
71
  refetchPackages: appPackages.refetch,
63
72
  refetchPanels: panelApps.refetch,
73
+ refetchAppData: appData.refetch,
64
74
  settlementManager: presenter.appPackageOperationSettlementManager,
65
75
  });
66
76
 
@@ -167,7 +177,11 @@ export function AppPackagesPanel({
167
177
  ) : null}
168
178
 
169
179
  <AppPackageLibrary
180
+ appDataEntries={appData.data?.entries ?? []}
181
+ appDataLoading={appData.isLoading}
182
+ appDataUnavailable={appData.isError}
170
183
  error={getPackageLoadError(appPackages.error, panelApps.error)}
184
+ focusedPackageId={focusedPackageId}
171
185
  isLoading={appPackages.isLoading || panelApps.isLoading}
172
186
  isPending={lifecycle.isPending}
173
187
  mutationAppId={'appId' in (lifecycle.variables ?? {})
@@ -249,7 +263,11 @@ export function AppPackagesPanel({
249
263
  }
250
264
 
251
265
  function AppPackageLibrary({
266
+ appDataEntries,
267
+ appDataLoading,
268
+ appDataUnavailable,
252
269
  error,
270
+ focusedPackageId,
253
271
  isLoading,
254
272
  isPending,
255
273
  mutationAppId,
@@ -260,7 +278,11 @@ function AppPackageLibrary({
260
278
  panelApps,
261
279
  operations,
262
280
  }: {
281
+ appDataEntries: AppDataEntry[];
282
+ appDataLoading: boolean;
283
+ appDataUnavailable: boolean;
263
284
  error?: string;
285
+ focusedPackageId?: string;
264
286
  isLoading: boolean;
265
287
  isPending: boolean;
266
288
  mutationAppId?: string;
@@ -327,27 +349,43 @@ function AppPackageLibrary({
327
349
  : undefined;
328
350
 
329
351
  return (
330
- <AppPackageCard
352
+ <section
331
353
  key={appPackage.id}
332
- appPackage={appPackage}
333
- panelApps={panelApps.filter((entry) => entry.packageId === appPackage.id)}
334
- isPending={isPending && mutationAppId === appPackage.id}
335
- operation={visibleOperation}
336
- onEnable={() => onMutate({ action: 'enable', appId: appPackage.id })}
337
- onDisable={() => onMutate({ action: 'disable', appId: appPackage.id })}
338
- onUpdate={() => onMutate({ action: 'update', appId: appPackage.id })}
339
- onRollback={(version) => onMutate({
340
- action: 'rollback',
341
- appId: appPackage.id,
342
- version,
343
- })}
344
- onUninstall={(purgeData) => onMutate({
345
- action: 'uninstall',
346
- appId: appPackage.id,
347
- purgeData,
348
- })}
349
- onOpenPanelApp={onOpenPanelApp}
350
- />
354
+ id={`app-package-${appPackage.id}`}
355
+ aria-label={appPackage.name}
356
+ tabIndex={-1}
357
+ className={focusedPackageId === appPackage.id
358
+ ? 'rounded-xl outline-none ring-2 ring-primary/35 ring-offset-2 ring-offset-background'
359
+ : 'rounded-xl outline-none'}
360
+ >
361
+ <AppPackageCard
362
+ appPackage={appPackage}
363
+ storageUsage={appDataEntries.find((entry) =>
364
+ entry.source === 'package' &&
365
+ entry.lifecycle === 'active' &&
366
+ entry.appId === appPackage.id &&
367
+ entry.instanceId === appPackage.instanceId)?.usage ?? appPackage.storageUsage}
368
+ storageUsageLoading={appDataLoading}
369
+ storageUsageUnavailable={appDataUnavailable}
370
+ panelApps={panelApps.filter((entry) => entry.packageId === appPackage.id)}
371
+ isPending={isPending && mutationAppId === appPackage.id}
372
+ operation={visibleOperation}
373
+ onEnable={() => onMutate({ action: 'enable', appId: appPackage.id })}
374
+ onDisable={() => onMutate({ action: 'disable', appId: appPackage.id })}
375
+ onUpdate={() => onMutate({ action: 'update', appId: appPackage.id })}
376
+ onRollback={(version) => onMutate({
377
+ action: 'rollback',
378
+ appId: appPackage.id,
379
+ version,
380
+ })}
381
+ onUninstall={(purgeData) => onMutate({
382
+ action: 'uninstall',
383
+ appId: appPackage.id,
384
+ purgeData,
385
+ })}
386
+ onOpenPanelApp={onOpenPanelApp}
387
+ />
388
+ </section>
351
389
  );
352
390
  })}
353
391
  </div>
@@ -1,3 +1,4 @@
1
+ import { useState } from 'react';
1
2
  import { AppWindow, Boxes, Server } from 'lucide-react';
2
3
  import type { PanelAppEntryView } from '@/shared/lib/api';
3
4
  import { AppPackagesPanel } from '@/features/apps/components/app-packages-panel';
@@ -17,10 +18,20 @@ export function AppsPanel({
17
18
  onActiveTabChange: (tab: AppsPanelTab) => void;
18
19
  onOpenPanelApp: (entry: PanelAppEntryView) => void;
19
20
  }) {
21
+ const [focusedPackageId, setFocusedPackageId] = useState<string>();
22
+ const changeTab = (tab: AppsPanelTab) => {
23
+ setFocusedPackageId(undefined);
24
+ onActiveTabChange(tab);
25
+ };
26
+ const managePackage = (packageId: string) => {
27
+ setFocusedPackageId(packageId);
28
+ onActiveTabChange('apps');
29
+ };
30
+
20
31
  return (
21
32
  <div className="flex h-full min-h-0 flex-col bg-card text-card-foreground">
22
33
  <nav className="shrink-0 border-b border-border/70 px-4 py-3" aria-label={t('appsTitle')}>
23
- <Tabs value={activeTab} onValueChange={(value) => onActiveTabChange(value as AppsPanelTab)}>
34
+ <Tabs value={activeTab} onValueChange={(value) => changeTab(value as AppsPanelTab)}>
24
35
  <TabsList className="grid h-auto w-full max-w-[390px] grid-cols-3 rounded-lg bg-muted/70 p-0.5">
25
36
  <TabsTrigger value="apps" className="min-w-0 gap-1.5 px-2 py-1.5 text-xs">
26
37
  <Boxes className="h-3.5 w-3.5 shrink-0" />
@@ -39,11 +50,11 @@ export function AppsPanel({
39
50
  </nav>
40
51
  <div className="min-h-0 flex-1">
41
52
  {activeTab === 'apps' ? (
42
- <AppPackagesPanel onOpenPanelApp={onOpenPanelApp} />
53
+ <AppPackagesPanel focusedPackageId={focusedPackageId} onOpenPanelApp={onOpenPanelApp} />
43
54
  ) : activeTab === 'panel-apps' ? (
44
55
  <PanelAppsList onOpenPanelApp={onOpenPanelApp} />
45
56
  ) : (
46
- <ServiceAppsPanel />
57
+ <ServiceAppsPanel onManagePackage={managePackage} />
47
58
  )}
48
59
  </div>
49
60
  </div>
@@ -31,7 +31,7 @@ export type AppPackageMutationInput =
31
31
  export function useAppPackages() {
32
32
  return useQuery({
33
33
  queryKey: APP_PACKAGES_QUERY_KEY,
34
- queryFn: () => nextclawClient.appPackages.list(),
34
+ queryFn: () => nextclawClient.appPackages.list({ includeStorageUsage: false }),
35
35
  staleTime: 0,
36
36
  });
37
37
  }
@@ -57,6 +57,7 @@ export function useAppPackageOperationSettlement({
57
57
  operations,
58
58
  refetchPackages,
59
59
  refetchPanels,
60
+ refetchAppData,
60
61
  settlementManager,
61
62
  }: {
62
63
  isLoaded: boolean;
@@ -65,6 +66,7 @@ export function useAppPackageOperationSettlement({
65
66
  refetchPanels: () => Promise<{
66
67
  data?: { entries: Array<{ id: string; packageId?: string }> };
67
68
  }>;
69
+ refetchAppData: () => Promise<unknown>;
68
70
  settlementManager: AppPackageOperationSettlementManager;
69
71
  }) {
70
72
  useEffect(() => {
@@ -73,8 +75,9 @@ export function useAppPackageOperationSettlement({
73
75
  operations,
74
76
  refetchPackages,
75
77
  refetchPanels,
78
+ refetchAppData,
76
79
  });
77
- }, [isLoaded, operations, refetchPackages, refetchPanels, settlementManager]);
80
+ }, [isLoaded, operations, refetchPackages, refetchPanels, refetchAppData, settlementManager]);
78
81
  }
79
82
 
80
83
  export function useAppPackageMutation() {
@@ -36,6 +36,7 @@ describe('AppPackageOperationSettlementManager', () => {
36
36
  it('ignores historical terminal operations and settles each new operation once', async () => {
37
37
  const reloadByDedupeKeys = vi.fn();
38
38
  const refetchPackages = vi.fn(async () => undefined);
39
+ const refetchAppData = vi.fn(async () => undefined);
39
40
  const refetchPanels = vi.fn(async () => ({
40
41
  data: {
41
42
  entries: [
@@ -54,6 +55,7 @@ describe('AppPackageOperationSettlementManager', () => {
54
55
  operations: [historical],
55
56
  refetchPackages,
56
57
  refetchPanels,
58
+ refetchAppData,
57
59
  });
58
60
  expect(mocks.success).not.toHaveBeenCalled();
59
61
 
@@ -63,17 +65,20 @@ describe('AppPackageOperationSettlementManager', () => {
63
65
  operations: [completed, historical],
64
66
  refetchPackages,
65
67
  refetchPanels,
68
+ refetchAppData,
66
69
  });
67
70
  manager.settle({
68
71
  isLoaded: true,
69
72
  operations: [completed, historical],
70
73
  refetchPackages,
71
74
  refetchPanels,
75
+ refetchAppData,
72
76
  });
73
77
 
74
78
  await vi.waitFor(() => {
75
79
  expect(refetchPackages).toHaveBeenCalledTimes(1);
76
80
  expect(refetchPanels).toHaveBeenCalledTimes(1);
81
+ expect(refetchAppData).toHaveBeenCalledTimes(1);
77
82
  expect(reloadByDedupeKeys).toHaveBeenCalledWith(['panel-app:todos']);
78
83
  });
79
84
  expect(mocks.success).toHaveBeenCalledTimes(1);
@@ -19,11 +19,13 @@ export class AppPackageOperationSettlementManager {
19
19
  operations,
20
20
  refetchPackages,
21
21
  refetchPanels,
22
+ refetchAppData,
22
23
  }: {
23
24
  isLoaded: boolean;
24
25
  operations: AppPackageOperationView[];
25
26
  refetchPackages: () => Promise<unknown>;
26
27
  refetchPanels: RefetchPanels;
28
+ refetchAppData: () => Promise<unknown>;
27
29
  }): void => {
28
30
  if (!isLoaded) return;
29
31
  const terminal = operations.filter(
@@ -37,7 +39,7 @@ export class AppPackageOperationSettlementManager {
37
39
  terminal.forEach((operation) => {
38
40
  if (this.knownTerminalOperationIds.has(operation.id)) return;
39
41
  this.knownTerminalOperationIds.add(operation.id);
40
- this.settleOperation(operation, refetchPackages, refetchPanels);
42
+ this.settleOperation(operation, refetchPackages, refetchPanels, refetchAppData);
41
43
  });
42
44
  };
43
45
 
@@ -45,10 +47,13 @@ export class AppPackageOperationSettlementManager {
45
47
  operation: AppPackageOperationView,
46
48
  refetchPackages: () => Promise<unknown>,
47
49
  refetchPanels: RefetchPanels,
50
+ refetchAppData: () => Promise<unknown>,
48
51
  ): void => {
49
52
  if (operation.status === 'succeeded') {
50
53
  toast.success(operationSuccessLabel(operation));
51
- void Promise.all([refetchPackages(), refetchPanels()]).then(([, panelsResult]) => {
54
+ void refetchPackages();
55
+ void refetchAppData();
56
+ void refetchPanels().then((panelsResult) => {
52
57
  if (operation.action !== 'rollback' && operation.action !== 'update') return;
53
58
  this.docBrowserManager.reloadByDedupeKeys(
54
59
  panelsResult.data?.entries
@@ -61,6 +66,7 @@ export class AppPackageOperationSettlementManager {
61
66
  if (operation.status === 'failed' || operation.status === 'interrupted') {
62
67
  void refetchPackages();
63
68
  void refetchPanels();
69
+ void refetchAppData();
64
70
  toast.error(operation.error ?? t('appPackagesActionFailed'));
65
71
  }
66
72
  };
@@ -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
+ }
@@ -35,6 +35,33 @@ it('preserves NCP extension parts for the chat presentation adapter', () => {
35
35
  ]);
36
36
  });
37
37
 
38
+ it('preserves standard tool execution timing without reading opaque result timing', () => {
39
+ const adapted = adaptNcpMessageToUiMessage({
40
+ id: 'ncp-message-timing-1',
41
+ sessionId: 'ncp-session-1',
42
+ role: 'assistant',
43
+ status: 'streaming',
44
+ timestamp: '2026-08-14T00:00:00.000Z',
45
+ parts: [{
46
+ type: 'tool-invocation',
47
+ toolCallId: 'tool-timing-1',
48
+ toolName: 'exec',
49
+ state: 'call',
50
+ args: { command: 'pnpm test' },
51
+ result: { durationMs: 999_999 },
52
+ execution: { startedAt: '2026-08-14T00:00:01.000Z' },
53
+ }],
54
+ });
55
+
56
+ expect(adapted.parts[0]).toMatchObject({
57
+ type: 'tool-invocation',
58
+ toolInvocation: {
59
+ toolCallId: 'tool-timing-1',
60
+ execution: { startedAt: '2026-08-14T00:00:01.000Z' },
61
+ },
62
+ });
63
+ });
64
+
38
65
  describe('adaptNcpSessionSummary', () => {
39
66
  it('maps session metadata into shared session entry fields', () => {
40
67
  const adapted = adaptNcpSessionSummary(