@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.
- package/CHANGELOG.md +28 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-BcFJp_1X.js → abnfDiagram-VRR7QNED-DvTjgVMf.js} +1 -1
- package/dist/assets/{aiden0z-pptx-renderer.es-Dp2ezlQn.js → aiden0z-pptx-renderer.es-C1nfBAXR.js} +1 -1
- package/dist/assets/{api-CVjdSvJk.js → api-CuPWhNDM.js} +3 -3
- package/dist/assets/{appearance-settings-page-BOorfaUx.js → appearance-settings-page-DRjg6UP3.js} +1 -1
- package/dist/assets/{arc-CkgyPMyW.js → arc-BvhODBXv.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-CMd4cIjI.js → architectureDiagram-ZJ3FMSHR-BKO9RgNS.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-Bz3tbhet.js → blockDiagram-677ZJIJ3-DHpDXy7D.js} +1 -1
- package/dist/assets/book-open-B4ovWX14.js +1 -0
- package/dist/assets/{c4Diagram-LMCZKHZV-D9hFNAGv.js → c4Diagram-LMCZKHZV-BG_iQnDr.js} +1 -1
- package/dist/assets/channel-5T3pdR-s.js +1 -0
- package/dist/assets/{channels-list-page-DuZJvYB6.js → channels-list-page-B26GXq63.js} +2 -2
- package/dist/assets/{chat-page-8PTMgI0t.js → chat-page-QA0dpNyU.js} +1 -1
- package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-7NrjEITq.js → chunk-32BRIVSS-DR9M0LLx.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-viIcqj9q.js → chunk-52WLFC77-698iC3BB.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-Vsu4J-VM.js → chunk-5FCAYU7R-DikTj9Ey.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-C-w6CQZK.js → chunk-5HE753X5-ko0o7_q0.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-CuKgJQ4W.js → chunk-5JV3BV7I-CEHufhgo.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-CYeiWZKb.js → chunk-5TONJI2A-CGgno1cu.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DRCbfwIn.js → chunk-5VM5RSS4-DkiB4cUQ.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-BvydOcm3.js → chunk-7BUUIJ7U-DzptcytQ.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-RlppOXKo.js → chunk-BIQX33UG-DMRSQJvT.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-DAizzYwr.js → chunk-C7G6YPKG-Bw1BwwuY.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-5eChJHca.js → chunk-CQNSW5MT-C0z6eHBk.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-YHPr53fv.js → chunk-CYSBUYHQ-ByPyAouO.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-CK4D7Hz5.js → chunk-EMLP6XTP-CJlAXWZg.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-BmlDFIRW.js → chunk-EX3LRPZG-Dqq4zKJu.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-D0ksHZZ9.js → chunk-FWX5IMBZ-B0SNoCmi.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-DPkq8FCm.js → chunk-HOUHSVGY-DG_GJih_.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-FAatv5fC.js → chunk-ICXQ74PX-Drxk_6bE.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-DLkIi4F7.js → chunk-JG7HCLWE-DdLFPpnd.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DawOjNE3.js → chunk-JWPE2WC7-B8M4G76F.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-Di52TorW.js → chunk-MOJQB5TN-XP6FJHY_.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-PfyK9w1-.js → chunk-MOZMSUNE-C_6x18Qw.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-C3jdH3Ty.js → chunk-OGEWGWER-N25BzCmG.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-CecHo-f0.js → chunk-OSBZ3O6U-ASDEm7y0.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-f3IJh3sN.js → chunk-PUDLZKDR-CTlvAcn7.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-DQFeUPxZ.js → chunk-Q4XR5HBZ-Da9BWfB6.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-BhUeG0y2.js → chunk-QBLGF6JB-Bi6RpTJO.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-C3XpZ-2u.js → chunk-R7FJI6CG-DkMsSPvK.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-CAqa5klQ.js → chunk-RYQCIY6F-DZp0eU-j.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-D_WXezeD.js → chunk-U6XO7XAA-vfinDSrj.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-CZ70oOtp.js → chunk-V7JOEXUC-C9Nksccs.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-B7AHEYYk.js → chunk-VR4S4FIN-Xv6zfwXs.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-Dn9To_OI.js → chunk-WYO6CB5R-mOJNzbWl.js} +2 -2
- package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-BYEgRBk3.js → chunk-YOTPTUD7-1-aKSu4o.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-C6YtZDU0.js → chunk-ZGVPDNZ5-OJ8veMpK.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-DMuYtnHF.js → chunk-ZIRB5QZD-SR_Nq_PP.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +1 -0
- package/dist/assets/{config-split-page-UuhsVcQD.js → config-split-page-Box_-aBK.js} +1 -1
- package/dist/assets/{confirm-dialog-Bnosa8UF.js → confirm-dialog-uqcCxomz.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-Bc2Ex5Y3.js → cose-bilkent-JH36ORCC-NjVgz2Bw.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-DVysQoGz.js → cynefinDiagram-TSTJHNR4-B_17LDqD.js} +1 -1
- package/dist/assets/{dagre-CMhQ1cAG.js → dagre-CMe101gk.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-RYyWpPJZ.js → dagre-VKFMJZFB-qo8nLb3B.js} +1 -1
- package/dist/assets/{desktop-update-config-CM4lsX4R.js → desktop-update-config-DkDor159.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BD6KONdF.js → diagram-FQU43EPY-Ba3XIklC.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-CGB2ptRR.js → diagram-G47NLZAW-CG45TcUx.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-DnkOI6cU.js → diagram-NH7WQ7WH-2F2HrwV-.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DF82RZ7P.js → diagram-OA4YK3LP-CXfWH9NZ.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-Be2v7Ck9.js → diagram-WEI45ONY-CH_4yepc.js} +1 -1
- package/dist/assets/{dist-D17RZAsz.js → dist-CZLIZntJ.js} +1 -1
- package/dist/assets/{dist-_zZ8P38T.js → dist-Co2G-YjN.js} +1 -1
- package/dist/assets/doc-browser-CbCBDdap.js +1 -0
- package/dist/assets/doc-browser-DUqckL2E.js +1 -0
- package/dist/assets/{doc-browser-context-CerkiK_N.js → doc-browser-context-aeZu0FHR.js} +1 -1
- package/dist/assets/{docx-preview-BqwAjrj3.js → docx-preview-CoM4JK7_.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CP98OYk8.js → ebnfDiagram-CCIWWBDH-BwiG4Ea1.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-ZLlx8xfs.js → erDiagram-Q63AITRT-BML3hpnQ.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +1 -0
- package/dist/assets/{form-actions-Cmv23dN4.js → form-actions-C3Pa1HQb.js} +1 -1
- package/dist/assets/{ganttDiagram-NO4QXBWP-DXCDjHR2.js → ganttDiagram-NO4QXBWP-80enfWbD.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-l2HWrTJp.js → gitGraphDiagram-IHSO6WYX-CVLqXr-b.js} +1 -1
- package/dist/assets/index-Bsoscjk7.css +1 -0
- package/dist/assets/index-CCWgeWoR.js +109 -0
- package/dist/assets/info-DKCQHKI2-DNlNGH90.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-D-DjX-bn.js → infoDiagram-FWYZ7A6U-Dyvs2t0i.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-y_Xy128F.js → ishikawaDiagram-FXEZZL3T-sSnxDKfh.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-DJn5xYcQ.js → journeyDiagram-5HDEW3XC-D22V71Mf.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DJVIOR21.js → kanban-definition-HUTT4EX6-D3ds0p-g.js} +1 -1
- package/dist/assets/{line-Cos7PIOj.js → line-CbyFs065.js} +1 -1
- package/dist/assets/{linear-BtDLqtzF.js → linear-4DKtnZBS.js} +1 -1
- package/dist/assets/loader-circle-DfbWUPdI.js +1 -0
- package/dist/assets/{map-BKnQyvd0.js → map-CcZzEoz6.js} +1 -1
- package/dist/assets/mcp-marketplace-page-B2D7ciJL.js +1 -0
- package/dist/assets/mcp-marketplace-page-BmqbIQQQ.js +9 -0
- package/dist/assets/{mermaid-parser.core-XUNZhr2W.js → mermaid-parser.core-DPmMOjXD.js} +2 -2
- package/dist/assets/{mermaid.core-ySrvolIe.js → mermaid.core-BSf-ddRa.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-BypL9tei.js → mindmap-definition-LN4V7U3C-BUTwWPyx.js} +1 -1
- package/dist/assets/model-config-page-BXUoZEEZ.js +1 -0
- package/dist/assets/{navigation-link-k9rsmtC4.js → navigation-link-DngetDmN.js} +1 -1
- package/dist/assets/{ordinal-CxxVWxYU.js → ordinal-BgLYtOZE.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-D-U2KBGv.js → pegDiagram-2B236MQR-BEu_ZBjc.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-CBi3GusJ.js → pieDiagram-ENE6RG2P-CAwAwuBd.js} +1 -1
- package/dist/assets/plus-wNpOxBfP.js +1 -0
- package/dist/assets/{provider-scoped-model-input-DLSAB6a5.js → provider-scoped-model-input-C4a2TuV7.js} +1 -1
- package/dist/assets/{providers-config-page-DM5N0aeS.js → providers-config-page-CHSka1kQ.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-H0ZuvnZL.js → quadrantDiagram-ABIIQ3AL-CwE6F-_2.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-D7Y2EqSd.js → railroadDiagram-RFXS5EU6-xgLekU3B.js} +1 -1
- package/dist/assets/{react-BgRLh3ee.js → react-CPH0zySO.js} +1 -1
- package/dist/assets/{refresh-cw-axvJLmRG.js → refresh-cw-vTYZqC0b.js} +1 -1
- package/dist/assets/remote-DgfRmokf.js +1 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-BiXNX1NN.js → requirementDiagram-TGXJPOKE-CyVUzEX5.js} +1 -1
- package/dist/assets/rotate-cw-L_seGUwq.js +1 -0
- package/dist/assets/runtime-config-page-V75_zqXa.js +1 -0
- package/dist/assets/{sankeyDiagram-HTMAVEWB-qzDeLztW.js → sankeyDiagram-HTMAVEWB-JK6bFmYB.js} +1 -1
- package/dist/assets/{save-DbR4SGk8.js → save-lWb6TI69.js} +1 -1
- package/dist/assets/{search-M9lrb8id.js → search-BuUX_0KY.js} +1 -1
- package/dist/assets/{search-config-page-DmsID8FQ.js → search-config-page-rlkRJZCF.js} +1 -1
- package/dist/assets/{secrets-config-page-C10OwI0b.js → secrets-config-page-BxCEc8r5.js} +2 -2
- package/dist/assets/security-config-Cqxe13za.js +1 -0
- package/dist/assets/{select-D_YBzMdv.js → select-oalsltZV.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BtRbPQtL.js → sequenceDiagram-DBY2YBRQ-BR5IokXV.js} +1 -1
- package/dist/assets/{settings-2-zy40UwTo.js → settings-2-Bd9xiNAY.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-CMYvM3SL.js → sizeCapture-X5ZJPWSS-SxG_YnMA.js} +1 -1
- package/dist/assets/skeleton-D72EdBNa.js +1 -0
- package/dist/assets/{src-Bw-mzbQb.js → src-vgz8E_-O.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-BJ-mFeOz.js → stateDiagram-2N3HPSRC-DD4OoYhn.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-BsucVOO2.js → swimlanes-5IMT3BWC-BWkFeyCT.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +8 -0
- package/dist/assets/{tag-chip-D7QRdcph.js → tag-chip-B324d_Xk.js} +1 -1
- package/dist/assets/{timeline-definition-FHXFAJF6-Bicewlzo.js → timeline-definition-FHXFAJF6-VTRPy6KA.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-BxOOnO9x.js → vennDiagram-L72KCM5P-Bvk42Pek.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-Cl7NltMv.js → wardleyDiagram-EHGQE667-DOFjx2s3.js} +1 -1
- package/dist/assets/x-Cps-je1K.js +1 -0
- package/dist/assets/{xychartDiagram-FW5EYKEG-DiMIxQ7N.js → xychartDiagram-FW5EYKEG-DY5yRTUc.js} +1 -1
- package/dist/index.html +22 -21
- package/package.json +9 -9
- package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +43 -10
- package/src/features/apps/components/__tests__/apps-panel.test.tsx +18 -2
- package/src/features/apps/components/app-package-card.tsx +58 -4
- package/src/features/apps/components/app-packages-panel.tsx +60 -22
- package/src/features/apps/components/apps-panel.tsx +14 -3
- package/src/features/apps/hooks/use-app-packages.ts +5 -2
- package/src/features/apps/managers/__tests__/app-package-operation-settlement.manager.test.ts +5 -0
- package/src/features/apps/managers/app-package-operation-settlement.manager.ts +8 -2
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +46 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +2 -0
- package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +52 -2
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +97 -10
- package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +4 -0
- package/src/features/chat/features/ncp/hooks/use-ncp-session-queries.ts +10 -0
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +27 -0
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-child-sessions.test.tsx +51 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-token-usage.test.tsx +90 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +9 -17
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +4 -45
- package/src/features/chat/features/workspace/components/child-sessions/chat-session-child-sessions.tsx +66 -0
- package/src/features/chat/features/workspace/components/overview/chat-session-token-usage.tsx +171 -0
- package/src/features/chat/types/chat-message.types.ts +5 -0
- package/src/features/service-apps/components/__tests__/service-apps-panel.test.tsx +32 -4
- package/src/features/service-apps/components/service-app-list-item.tsx +26 -12
- package/src/features/service-apps/components/service-apps-panel.tsx +23 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-tab-strip.test.tsx +78 -0
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +71 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +78 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +97 -44
- package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +2 -0
- package/src/shared/lib/api/ncp-session-query-cache.utils.ts +4 -0
- package/src/shared/lib/api/ncp-session.types.ts +3 -0
- package/src/shared/lib/api/types.ts +1 -0
- package/src/shared/lib/api/utils/ncp-session.utils.ts +8 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +19 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +3 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -1
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +3 -0
- package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +0 -1
- package/dist/assets/channel-nBvMaVyP.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +0 -1
- package/dist/assets/doc-browser-CiLvybSC.js +0 -1
- package/dist/assets/doc-browser-z7vdH3Ez.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +0 -1
- package/dist/assets/index-CWdgBpSc.css +0 -1
- package/dist/assets/index-DlL8uKXu.js +0 -109
- package/dist/assets/info-DKCQHKI2-BIporNWb.js +0 -1
- package/dist/assets/loader-circle-vUsFtTfb.js +0 -1
- package/dist/assets/mcp-marketplace-page-BV8WB3_L.js +0 -1
- package/dist/assets/mcp-marketplace-page-DN_fqcLd.js +0 -9
- package/dist/assets/model-config-page-BxaeCMbJ.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +0 -1
- package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +0 -1
- package/dist/assets/plus-Dc760kHj.js +0 -1
- package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +0 -1
- package/dist/assets/remote-Cj1GBMaN.js +0 -1
- package/dist/assets/rotate-cw-CFByP2O8.js +0 -1
- package/dist/assets/runtime-config-page-WNzZyjMW.js +0 -1
- package/dist/assets/security-config-CvU5JxDT.js +0 -1
- package/dist/assets/skeleton-BmAtzjDM.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +0 -1
- package/dist/assets/x-D9Y9hf_r.js +0 -1
- /package/dist/assets/{array-BQVzeGRt.js → array-NADKd9aU.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-f9psyCXK.js → chunk-KEIR6QF5-BuyuzJJL.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-D1mAU_Po.js → chunk-Y2CYZVJY-D_B2ZRNt.js} +0 -0
- /package/dist/assets/{config-hints-nV5HPOvq.js → config-hints-BcfVUMij.js} +0 -0
- /package/dist/assets/{cytoscape.esm-0uDtdD0l.js → cytoscape.esm-BANTlgza.js} +0 -0
- /package/dist/assets/{defaultLocale-DSWXGgt0.js → defaultLocale-C4_L0aEs.js} +0 -0
- /package/dist/assets/{dist--ta5rX12.js → dist-DAPEIJbz.js} +0 -0
- /package/dist/assets/{graphlib-CQ6QV4yP.js → graphlib-ChqXg0dI.js} +0 -0
- /package/dist/assets/{host-capabilities-Tk038i04.js → host-capabilities-D9C4CBK5.js} +0 -0
- /package/dist/assets/{init-Y6h9jGtZ.js → init-D8wZg9xq.js} +0 -0
- /package/dist/assets/{jszip.min-8hyJpgpx.js → jszip.min-CkbBfwAQ.js} +0 -0
- /package/dist/assets/{katex-ARhY9PS1.js → katex-TZJFa0J_.js} +0 -0
- /package/dist/assets/{middleware-DXsbf9xI.js → middleware-B3k-TSmZ.js} +0 -0
- /package/dist/assets/{path-10BjdyLh.js → path-DodfxcX7.js} +0 -0
- /package/dist/assets/{rough.esm-DxgC_14r.js → rough.esm-CUaa1Rbe.js} +0 -0
- /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
|
-
<
|
|
352
|
+
<section
|
|
331
353
|
key={appPackage.id}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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) =>
|
|
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() {
|
package/src/features/apps/managers/__tests__/app-package-operation-settlement.manager.test.ts
CHANGED
|
@@ -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
|
|
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
|
|
126
|
-
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(
|
|
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(
|
|
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
|
|
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(
|
|
262
|
-
expect(mocks.stream).toHaveBeenCalledTimes(
|
|
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
|
+
}
|
package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts
CHANGED
|
@@ -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(
|