@nextclaw/ui 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -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-DJ6ZKWFY.js → api-BJioGtWS.js} +3 -3
- package/dist/assets/{appearance-settings-page-B-juoJ6F.js → appearance-settings-page-DAF1fd14.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-aDv6R1av.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-mVHYa7xW.js → channels-list-page-Dubt8r9S.js} +2 -2
- package/dist/assets/{chat-page-DsMYGNee.js → chat-page-JbwjeUo4.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-BaqLcRxk.js → config-split-page-Bs1wArsX.js} +1 -1
- package/dist/assets/{confirm-dialog-DasFoaCT.js → confirm-dialog-BFatCZUq.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-CoEvKrpB.js → desktop-update-config-DFefhK96.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-C741xUwt.js → dist-CV9wIyME.js} +1 -1
- package/dist/assets/{dist-zOKNc7QQ.js → dist-D1I3blO_.js} +1 -1
- package/dist/assets/doc-browser-BFfstBoh.js +1 -0
- package/dist/assets/doc-browser-DEdoMb3X.js +1 -0
- package/dist/assets/{doc-browser-context-BT8NWQyM.js → doc-browser-context-BcwcVwX_.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-BcukaMyR.js → form-actions-YSnn7q_U.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-L58BO782.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-CsNiT6VM.js +1 -0
- package/dist/assets/{map-BKnQyvd0.js → map-CcZzEoz6.js} +1 -1
- package/dist/assets/mcp-marketplace-page-DfUCF-L5.js +1 -0
- package/dist/assets/mcp-marketplace-page-ZTHpQTvi.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-jYCwpq4U.js +1 -0
- package/dist/assets/{navigation-link-BoMNAKyw.js → navigation-link-7oSjAC9R.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-D6UN8XN5.js +1 -0
- package/dist/assets/{provider-scoped-model-input-CB-vojwC.js → provider-scoped-model-input-C1CUHVJx.js} +1 -1
- package/dist/assets/{providers-config-page-C80nti5H.js → providers-config-page-CMvI4R1e.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-7zG0fz1I.js +8 -0
- package/dist/assets/{refresh-cw-DZ_Y89ld.js → refresh-cw-BUg7hI3u.js} +1 -1
- package/dist/assets/remote-BYPq49kD.js +1 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-BiXNX1NN.js → requirementDiagram-TGXJPOKE-CyVUzEX5.js} +1 -1
- package/dist/assets/rotate-cw-B3qmXtfJ.js +1 -0
- package/dist/assets/runtime-config-page-DYuYv5yM.js +1 -0
- package/dist/assets/{sankeyDiagram-HTMAVEWB-qzDeLztW.js → sankeyDiagram-HTMAVEWB-JK6bFmYB.js} +1 -1
- package/dist/assets/{save-BgMfxs5u.js → save-CTCYuZqu.js} +1 -1
- package/dist/assets/{search-C7ruoV7u.js → search-CfFUpaf7.js} +1 -1
- package/dist/assets/{search-config-page-BTjfOZ6q.js → search-config-page-GUtGbk6i.js} +1 -1
- package/dist/assets/{secrets-config-page-BFjiSz73.js → secrets-config-page-Delo8ab0.js} +2 -2
- package/dist/assets/security-config-DoVoel5I.js +1 -0
- package/dist/assets/{select-BMX2ooE-.js → select-MSvkWmPP.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BtRbPQtL.js → sequenceDiagram-DBY2YBRQ-BR5IokXV.js} +1 -1
- package/dist/assets/{settings-2-BNQF6hed.js → settings-2-DXUfAWgg.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-CMYvM3SL.js → sizeCapture-X5ZJPWSS-SxG_YnMA.js} +1 -1
- package/dist/assets/skeleton-DDFivi9Y.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-BL-UXlVO.js → tag-chip-u8g0b0Nf.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-DdOEUmR4.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/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +2 -0
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +2 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -0
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +46 -0
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-slash-commands.test.tsx +20 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +138 -54
- package/src/features/chat/features/conversation/hooks/use-session-conversation-slash-commands.ts +25 -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/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 +23 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +24 -1
- 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-BbirIYbj.js +0 -1
- package/dist/assets/doc-browser-Crqog5cn.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-1f4Pv2q3.css +0 -1
- package/dist/assets/index-BYWLjYFQ.js +0 -109
- package/dist/assets/info-DKCQHKI2-BIporNWb.js +0 -1
- package/dist/assets/loader-circle-LVHz84Xj.js +0 -1
- package/dist/assets/mcp-marketplace-page-2SsrbaiQ.js +0 -1
- package/dist/assets/mcp-marketplace-page-XgYLOAkK.js +0 -9
- package/dist/assets/model-config-page-DL8ljJK-.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-BeGZHC3f.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/react-DND3vEAM.js +0 -8
- package/dist/assets/remote-Codkr9SX.js +0 -1
- package/dist/assets/rotate-cw-CP3pQ4R8.js +0 -1
- package/dist/assets/runtime-config-page-Um1XDqXJ.js +0 -1
- package/dist/assets/security-config-Dx-wMb4q.js +0 -1
- package/dist/assets/skeleton-CWZ0IO2D.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-BSu1xwPK.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
package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts
CHANGED
|
@@ -3,7 +3,10 @@ import { buildNcpRequestEnvelope } from '@nextclaw/ncp-react';
|
|
|
3
3
|
import type { UiNcpSessionQueuedInputView } from '@nextclaw/client-sdk';
|
|
4
4
|
import type { NcpAgentSendEnvelope, NcpRunHandle } from '@nextclaw/ncp';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createChatComposerNodesFromDraft,
|
|
8
|
+
deriveNcpMessagePartsFromComposer,
|
|
9
|
+
} from '@/features/chat/features/input/utils/chat-composer-state.utils';
|
|
7
10
|
import { isNcpChatSendDisabled } from '@/features/chat/features/input/utils/ncp-chat-input-availability.utils';
|
|
8
11
|
import { buildChatRunMetadata } from '@/features/chat/features/session/utils/chat-run-metadata.utils';
|
|
9
12
|
import { createNcpSessionId } from '@/features/chat/features/session/utils/ncp-session-adapter.utils';
|
|
@@ -74,6 +77,7 @@ type UseSessionConversationControllerParams = {
|
|
|
74
77
|
|
|
75
78
|
type BuildSubmissionDraftParams = {
|
|
76
79
|
readonly agentIsSending: boolean;
|
|
80
|
+
readonly composerSnapshot?: ComposerDraftSnapshot;
|
|
77
81
|
readonly inputSnapshot: SessionConversationInputSnapshot;
|
|
78
82
|
readonly inputQuery: SessionConversationInputQuery;
|
|
79
83
|
readonly isRuntimeBlocked: boolean;
|
|
@@ -87,6 +91,8 @@ type SubmissionDraft = {
|
|
|
87
91
|
readonly metadata: Record<string, unknown>;
|
|
88
92
|
};
|
|
89
93
|
|
|
94
|
+
type SubmissionComposerBehavior = 'preserve' | 'reset-and-restore';
|
|
95
|
+
|
|
90
96
|
type SubmittingQueuedInput = {
|
|
91
97
|
readonly id: string;
|
|
92
98
|
readonly preview: string;
|
|
@@ -113,6 +119,7 @@ function buildInputAvailabilitySnapshot(inputQuery: SessionConversationInputQuer
|
|
|
113
119
|
function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDraft | null {
|
|
114
120
|
const {
|
|
115
121
|
agentIsSending,
|
|
122
|
+
composerSnapshot: requestedComposerSnapshot,
|
|
116
123
|
inputQuery,
|
|
117
124
|
inputSnapshot,
|
|
118
125
|
isRuntimeBlocked,
|
|
@@ -120,9 +127,16 @@ function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDra
|
|
|
120
127
|
selectedAgentId,
|
|
121
128
|
sessionKey,
|
|
122
129
|
} = params;
|
|
130
|
+
const composerSnapshot: ComposerDraftSnapshot = requestedComposerSnapshot ?? {
|
|
131
|
+
text: inputSnapshot.text,
|
|
132
|
+
nodes: [...inputSnapshot.nodes],
|
|
133
|
+
selectedSkills: [...inputSnapshot.selectedSkills],
|
|
134
|
+
skillRecords: [...inputSnapshot.skillRecords],
|
|
135
|
+
attachments: [...inputSnapshot.attachments],
|
|
136
|
+
};
|
|
123
137
|
const currentParts = deriveNcpMessagePartsFromComposer(
|
|
124
|
-
[...
|
|
125
|
-
|
|
138
|
+
[...composerSnapshot.nodes],
|
|
139
|
+
composerSnapshot.attachments,
|
|
126
140
|
);
|
|
127
141
|
if (
|
|
128
142
|
isNcpChatSendDisabled({
|
|
@@ -134,13 +148,6 @@ function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDra
|
|
|
134
148
|
) {
|
|
135
149
|
return null;
|
|
136
150
|
}
|
|
137
|
-
const composerSnapshot: ComposerDraftSnapshot = {
|
|
138
|
-
text: inputSnapshot.text,
|
|
139
|
-
nodes: [...inputSnapshot.nodes],
|
|
140
|
-
selectedSkills: [...inputSnapshot.selectedSkills],
|
|
141
|
-
skillRecords: [...inputSnapshot.skillRecords],
|
|
142
|
-
attachments: [...inputSnapshot.attachments],
|
|
143
|
-
};
|
|
144
151
|
return {
|
|
145
152
|
composerSnapshot,
|
|
146
153
|
metadata: buildChatRunMetadata({
|
|
@@ -165,11 +172,11 @@ function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDra
|
|
|
165
172
|
: sessionKey
|
|
166
173
|
? inputQuery.selectedSession?.projectRoot ?? null
|
|
167
174
|
: inputSnapshot.pendingProjectRoot,
|
|
168
|
-
requestedSkills: [...
|
|
175
|
+
requestedSkills: [...composerSnapshot.selectedSkills],
|
|
169
176
|
skillRecords: inputQuery.skillRecords.filter((record) =>
|
|
170
|
-
|
|
177
|
+
composerSnapshot.selectedSkills.includes(record.ref)
|
|
171
178
|
),
|
|
172
|
-
composerNodes: [...
|
|
179
|
+
composerNodes: [...composerSnapshot.nodes],
|
|
173
180
|
sessionMaterialization: materializationContext
|
|
174
181
|
? {
|
|
175
182
|
kind: materializationContext.kind,
|
|
@@ -288,6 +295,72 @@ function useQueuedInputActions(params: {
|
|
|
288
295
|
return { deleteQueuedInput, editQueuedInput };
|
|
289
296
|
}
|
|
290
297
|
|
|
298
|
+
function useSubmissionDraftRunner(params: {
|
|
299
|
+
readonly agent: SessionConversationAgent;
|
|
300
|
+
readonly clearSubmittingInput: (input: SubmittingQueuedInput) => void;
|
|
301
|
+
readonly materializationContext?: SessionConversationMaterializationContext | null;
|
|
302
|
+
readonly onSessionMaterialized?: (sessionKey: string) => void;
|
|
303
|
+
readonly resetComposer: () => void;
|
|
304
|
+
readonly restoreComposer: (snapshot: ComposerDraftSnapshot) => void;
|
|
305
|
+
readonly runQueue: SessionRunQueue;
|
|
306
|
+
readonly sessionKey: string | null;
|
|
307
|
+
readonly setSendError: (message: string | null) => void;
|
|
308
|
+
readonly stageSubmittingInput: (envelope: NcpAgentSendEnvelope) => SubmittingQueuedInput | null;
|
|
309
|
+
}) {
|
|
310
|
+
const {
|
|
311
|
+
agent,
|
|
312
|
+
clearSubmittingInput,
|
|
313
|
+
materializationContext,
|
|
314
|
+
onSessionMaterialized,
|
|
315
|
+
resetComposer,
|
|
316
|
+
restoreComposer,
|
|
317
|
+
runQueue,
|
|
318
|
+
sessionKey,
|
|
319
|
+
setSendError,
|
|
320
|
+
stageSubmittingInput,
|
|
321
|
+
} = params;
|
|
322
|
+
return useCallback(async (
|
|
323
|
+
draft: SubmissionDraft,
|
|
324
|
+
composerBehavior: SubmissionComposerBehavior,
|
|
325
|
+
) => {
|
|
326
|
+
const submissionSessionKey =
|
|
327
|
+
sessionKey ?? (materializationContext ? null : createNcpSessionId());
|
|
328
|
+
const envelope = buildSubmissionEnvelope(draft, submissionSessionKey);
|
|
329
|
+
if (!envelope) return;
|
|
330
|
+
const queuedSubmission = agent.isRunning ? stageSubmittingInput(envelope) : null;
|
|
331
|
+
if (composerBehavior === 'reset-and-restore') resetComposer();
|
|
332
|
+
setSendError(null);
|
|
333
|
+
try {
|
|
334
|
+
const handle = await agent.send(envelope);
|
|
335
|
+
if (queuedSubmission && handle?.runId === null) {
|
|
336
|
+
await runQueue.refreshQueuedInputs().catch(() => undefined);
|
|
337
|
+
}
|
|
338
|
+
if (queuedSubmission) clearSubmittingInput(queuedSubmission);
|
|
339
|
+
const materializedSessionKey =
|
|
340
|
+
handle?.sessionId?.trim() || agent.snapshot.activeRun?.sessionId?.trim() || null;
|
|
341
|
+
if (!sessionKey && materializedSessionKey) {
|
|
342
|
+
onSessionMaterialized?.(materializedSessionKey);
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (queuedSubmission) clearSubmittingInput(queuedSubmission);
|
|
346
|
+
if (composerBehavior === 'reset-and-restore') restoreComposer(draft.composerSnapshot);
|
|
347
|
+
setSendError(error instanceof Error ? error.message : String(error));
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}, [
|
|
351
|
+
agent,
|
|
352
|
+
clearSubmittingInput,
|
|
353
|
+
materializationContext,
|
|
354
|
+
onSessionMaterialized,
|
|
355
|
+
resetComposer,
|
|
356
|
+
restoreComposer,
|
|
357
|
+
runQueue,
|
|
358
|
+
sessionKey,
|
|
359
|
+
setSendError,
|
|
360
|
+
stageSubmittingInput,
|
|
361
|
+
]);
|
|
362
|
+
}
|
|
363
|
+
|
|
291
364
|
export function useSessionConversationController(params: UseSessionConversationControllerParams) {
|
|
292
365
|
const {
|
|
293
366
|
agent,
|
|
@@ -343,6 +416,19 @@ export function useSessionConversationController(params: UseSessionConversationC
|
|
|
343
416
|
setSendError,
|
|
344
417
|
});
|
|
345
418
|
|
|
419
|
+
const submitDraft = useSubmissionDraftRunner({
|
|
420
|
+
agent,
|
|
421
|
+
clearSubmittingInput,
|
|
422
|
+
materializationContext,
|
|
423
|
+
onSessionMaterialized,
|
|
424
|
+
resetComposer,
|
|
425
|
+
restoreComposer,
|
|
426
|
+
runQueue,
|
|
427
|
+
sessionKey,
|
|
428
|
+
setSendError,
|
|
429
|
+
stageSubmittingInput,
|
|
430
|
+
});
|
|
431
|
+
|
|
346
432
|
const send = useCallback(async () => {
|
|
347
433
|
if (primaryAction === 'continue') {
|
|
348
434
|
await continueRun();
|
|
@@ -357,58 +443,55 @@ export function useSessionConversationController(params: UseSessionConversationC
|
|
|
357
443
|
selectedAgentId,
|
|
358
444
|
sessionKey,
|
|
359
445
|
});
|
|
360
|
-
if (
|
|
361
|
-
|
|
446
|
+
if (draft) {
|
|
447
|
+
await submitDraft(draft, 'reset-and-restore');
|
|
362
448
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
449
|
+
}, [
|
|
450
|
+
agent.isSending,
|
|
451
|
+
continueRun,
|
|
452
|
+
inputQuery,
|
|
453
|
+
inputSnapshot,
|
|
454
|
+
isRuntimeBlocked,
|
|
455
|
+
materializationContext,
|
|
456
|
+
primaryAction,
|
|
457
|
+
selectedAgentId,
|
|
458
|
+
sessionKey,
|
|
459
|
+
submitDraft,
|
|
460
|
+
]);
|
|
461
|
+
|
|
462
|
+
const sendPresetMessage = useCallback(async (message: string) => {
|
|
463
|
+
const text = message.trim();
|
|
464
|
+
if (!text) {
|
|
367
465
|
return;
|
|
368
466
|
}
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} catch (error) {
|
|
388
|
-
if (queuedSubmission) {
|
|
389
|
-
clearSubmittingInput(queuedSubmission);
|
|
390
|
-
}
|
|
391
|
-
restoreComposer(draft.composerSnapshot);
|
|
392
|
-
setSendError(error instanceof Error ? error.message : String(error));
|
|
393
|
-
throw error;
|
|
467
|
+
const draft = buildSubmissionDraft({
|
|
468
|
+
agentIsSending: agent.isSending,
|
|
469
|
+
composerSnapshot: {
|
|
470
|
+
text,
|
|
471
|
+
nodes: createChatComposerNodesFromDraft(text),
|
|
472
|
+
selectedSkills: [],
|
|
473
|
+
skillRecords: [],
|
|
474
|
+
attachments: [],
|
|
475
|
+
},
|
|
476
|
+
inputSnapshot,
|
|
477
|
+
inputQuery,
|
|
478
|
+
isRuntimeBlocked,
|
|
479
|
+
materializationContext,
|
|
480
|
+
selectedAgentId,
|
|
481
|
+
sessionKey,
|
|
482
|
+
});
|
|
483
|
+
if (draft) {
|
|
484
|
+
await submitDraft(draft, 'preserve');
|
|
394
485
|
}
|
|
395
486
|
}, [
|
|
396
|
-
agent,
|
|
397
|
-
clearSubmittingInput,
|
|
398
|
-
continueRun,
|
|
487
|
+
agent.isSending,
|
|
399
488
|
inputQuery,
|
|
400
489
|
inputSnapshot,
|
|
401
490
|
isRuntimeBlocked,
|
|
402
491
|
materializationContext,
|
|
403
|
-
onSessionMaterialized,
|
|
404
|
-
resetComposer,
|
|
405
|
-
restoreComposer,
|
|
406
492
|
selectedAgentId,
|
|
407
493
|
sessionKey,
|
|
408
|
-
|
|
409
|
-
stageSubmittingInput,
|
|
410
|
-
primaryAction,
|
|
411
|
-
runQueue,
|
|
494
|
+
submitDraft,
|
|
412
495
|
]);
|
|
413
496
|
|
|
414
497
|
const stop = useCallback(async () => {
|
|
@@ -426,6 +509,7 @@ export function useSessionConversationController(params: UseSessionConversationC
|
|
|
426
509
|
isSending,
|
|
427
510
|
queuedInputs,
|
|
428
511
|
send,
|
|
512
|
+
sendPresetMessage,
|
|
429
513
|
sendDisabled,
|
|
430
514
|
primaryAction,
|
|
431
515
|
stop,
|
package/src/features/chat/features/conversation/hooks/use-session-conversation-slash-commands.ts
CHANGED
|
@@ -8,9 +8,15 @@ import type { ChatSlashCommandDescriptor } from '@/features/chat/features/input/
|
|
|
8
8
|
export function useSessionConversationSlashCommands(params: {
|
|
9
9
|
language: I18nLanguage;
|
|
10
10
|
onContextCompactingChange?: (sessionId: string, isCompacting: boolean) => void;
|
|
11
|
+
onSendPresetMessage: (message: string) => Promise<void> | void;
|
|
11
12
|
selectedSessionKey?: string | null;
|
|
12
13
|
}): readonly ChatSlashCommandDescriptor[] {
|
|
13
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
language,
|
|
16
|
+
onContextCompactingChange,
|
|
17
|
+
onSendPresetMessage,
|
|
18
|
+
selectedSessionKey,
|
|
19
|
+
} = params;
|
|
14
20
|
const presenter = usePresenter();
|
|
15
21
|
const compactingSessionIdsRef = useRef(new Set<string>());
|
|
16
22
|
const compactContext = useCallback(async (sessionId: string) => {
|
|
@@ -46,6 +52,17 @@ export function useSessionConversationSlashCommands(params: {
|
|
|
46
52
|
keywords: ['side', 'chat', 'child', 'branch', 'new'],
|
|
47
53
|
onSelect: () => presenter.chatThreadManager.openSideChatDraft(sessionId),
|
|
48
54
|
},
|
|
55
|
+
{
|
|
56
|
+
key: 'update-session-title',
|
|
57
|
+
icon: 'command',
|
|
58
|
+
title: t('chatSlashCommandUpdateSessionTitleTitle', language),
|
|
59
|
+
description: t('chatSlashCommandUpdateSessionTitleDescription', language),
|
|
60
|
+
detailLines: [t('chatSlashCommandUpdateSessionTitleDetail', language)],
|
|
61
|
+
keywords: ['update', 'rename', 'title', 'session', '更新', '重命名', '标题', '会话'],
|
|
62
|
+
onSelect: () => void onSendPresetMessage(
|
|
63
|
+
t('chatSlashCommandUpdateSessionTitlePrompt', language),
|
|
64
|
+
),
|
|
65
|
+
},
|
|
49
66
|
{
|
|
50
67
|
key: 'compact-context',
|
|
51
68
|
icon: 'list-collapse',
|
|
@@ -56,5 +73,11 @@ export function useSessionConversationSlashCommands(params: {
|
|
|
56
73
|
onSelect: () => void compactContext(sessionId),
|
|
57
74
|
},
|
|
58
75
|
];
|
|
59
|
-
}, [
|
|
76
|
+
}, [
|
|
77
|
+
compactContext,
|
|
78
|
+
language,
|
|
79
|
+
onSendPresetMessage,
|
|
80
|
+
presenter.chatThreadManager,
|
|
81
|
+
selectedSessionKey,
|
|
82
|
+
]);
|
|
60
83
|
}
|
|
@@ -237,3 +237,49 @@ describe("buildShowContentToolCard", () => {
|
|
|
237
237
|
});
|
|
238
238
|
});
|
|
239
239
|
});
|
|
240
|
+
|
|
241
|
+
describe("buildToolCard terminal outcome and timing", () => {
|
|
242
|
+
it.each([
|
|
243
|
+
[{ command: "false", ok: false, exitCode: 1 }, "error", "Failed"],
|
|
244
|
+
[{ command: "rm -rf /", blocked: true, status: "blocked" }, "error", "Failed"],
|
|
245
|
+
[{ command: "sleep 5", status: "cancelled" }, "cancelled", "Cancelled"],
|
|
246
|
+
[{ command: "pwd", status: "completed", exit_code: 0 }, "success", "Completed"],
|
|
247
|
+
] as const)("normalizes terminal result %o", (outputData, statusTone, statusLabel) => {
|
|
248
|
+
expect(buildToolCard({
|
|
249
|
+
kind: "result",
|
|
250
|
+
name: "commandExecution",
|
|
251
|
+
detail: "command: pwd",
|
|
252
|
+
hasResult: true,
|
|
253
|
+
statusTone: "success",
|
|
254
|
+
statusLabel: "Completed",
|
|
255
|
+
outputData,
|
|
256
|
+
}, TEXTS)).toMatchObject({ statusTone, statusLabel });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("only exposes standard execution timing and preserves the stable tool call identity", () => {
|
|
260
|
+
const card = buildToolCard({
|
|
261
|
+
kind: "result",
|
|
262
|
+
name: "exec",
|
|
263
|
+
detail: "command: pnpm test",
|
|
264
|
+
hasResult: true,
|
|
265
|
+
statusTone: "success",
|
|
266
|
+
statusLabel: "Completed",
|
|
267
|
+
toolCallId: "tool-timing-1",
|
|
268
|
+
outputData: { command: "pnpm test", durationMs: 999_999 },
|
|
269
|
+
execution: { durationMs: 4270 },
|
|
270
|
+
}, TEXTS);
|
|
271
|
+
expect(card).toMatchObject({
|
|
272
|
+
toolCallId: "tool-timing-1",
|
|
273
|
+
execution: { durationMs: 4270 },
|
|
274
|
+
});
|
|
275
|
+
expect(buildToolCard({
|
|
276
|
+
kind: "result",
|
|
277
|
+
name: "exec",
|
|
278
|
+
detail: "command: pnpm test",
|
|
279
|
+
hasResult: true,
|
|
280
|
+
statusTone: "success",
|
|
281
|
+
statusLabel: "Completed",
|
|
282
|
+
outputData: { command: "pnpm test", durationMs: 999_999 },
|
|
283
|
+
}, TEXTS).execution).toBeUndefined();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -179,6 +179,8 @@ function buildToolInvocationPart(
|
|
|
179
179
|
text: shouldShowRawResult && rawResult ? rawResult : undefined,
|
|
180
180
|
outputData: invocation.result,
|
|
181
181
|
callId: invocation.toolCallId || undefined,
|
|
182
|
+
toolCallId: invocation.toolCallId || undefined,
|
|
183
|
+
...(invocation.execution ? { execution: invocation.execution } : {}),
|
|
182
184
|
hasResult: statusView.hasResult,
|
|
183
185
|
statusTone: invalidArgsError ? "error" : statusView.statusTone,
|
|
184
186
|
statusLabel: invalidArgsError ? texts.toolStatusFailedLabel : statusView.statusLabel,
|
|
@@ -15,6 +15,8 @@ export type ToolCardViewSource = ToolCard & {
|
|
|
15
15
|
action?: ChatToolPartViewModel["action"];
|
|
16
16
|
fileOperation?: ChatToolPartViewModel["fileOperation"];
|
|
17
17
|
outputData?: unknown;
|
|
18
|
+
execution?: ChatToolPartViewModel["execution"];
|
|
19
|
+
toolCallId?: string;
|
|
18
20
|
panelApp?: ChatToolPartViewModel["panelApp"];
|
|
19
21
|
};
|
|
20
22
|
|
|
@@ -62,6 +64,43 @@ export function isTerminalResultRecord(
|
|
|
62
64
|
);
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
function readTerminalStatus(value: unknown): string {
|
|
68
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveTerminalResultStatus(
|
|
72
|
+
value: unknown,
|
|
73
|
+
): "success" | "error" | "cancelled" | null {
|
|
74
|
+
if (!isTerminalResultRecord(value)) return null;
|
|
75
|
+
const status = readTerminalStatus(value.status ?? value.outcome);
|
|
76
|
+
if (
|
|
77
|
+
value.cancelled === true ||
|
|
78
|
+
value.aborted === true ||
|
|
79
|
+
status === "cancelled" ||
|
|
80
|
+
status === "canceled" ||
|
|
81
|
+
status === "aborted" ||
|
|
82
|
+
status === "interrupted"
|
|
83
|
+
) {
|
|
84
|
+
return "cancelled";
|
|
85
|
+
}
|
|
86
|
+
const exitCode = readOptionalNumber(value.exitCode ?? value.exit_code);
|
|
87
|
+
if (
|
|
88
|
+
value.ok === false ||
|
|
89
|
+
value.blocked === true ||
|
|
90
|
+
value.timedOut === true ||
|
|
91
|
+
value.killed === true ||
|
|
92
|
+
status === "error" ||
|
|
93
|
+
status === "failed" ||
|
|
94
|
+
status === "blocked" ||
|
|
95
|
+
status === "declined" ||
|
|
96
|
+
status === "timed_out" ||
|
|
97
|
+
(exitCode != null && exitCode !== 0)
|
|
98
|
+
) {
|
|
99
|
+
return "error";
|
|
100
|
+
}
|
|
101
|
+
return "success";
|
|
102
|
+
}
|
|
103
|
+
|
|
65
104
|
export function extractAssetFileView(
|
|
66
105
|
value: unknown,
|
|
67
106
|
texts: ChatMessageAdapterTexts,
|
|
@@ -107,9 +146,19 @@ export function buildToolCard(
|
|
|
107
146
|
toolCard: ToolCardViewSource,
|
|
108
147
|
texts: ChatMessageAdapterTexts,
|
|
109
148
|
): ChatToolPartViewModel {
|
|
149
|
+
const terminalStatus = toolCard.statusTone === "success"
|
|
150
|
+
? resolveTerminalResultStatus(toolCard.outputData)
|
|
151
|
+
: null;
|
|
152
|
+
const statusTone = terminalStatus ?? toolCard.statusTone;
|
|
153
|
+
const statusLabel = statusTone === "error"
|
|
154
|
+
? texts.toolStatusFailedLabel
|
|
155
|
+
: statusTone === "cancelled"
|
|
156
|
+
? texts.toolStatusCancelledLabel
|
|
157
|
+
: toolCard.statusLabel;
|
|
110
158
|
return {
|
|
111
159
|
kind: toolCard.kind,
|
|
112
160
|
toolName: toolCard.name,
|
|
161
|
+
...(toolCard.toolCallId ? { toolCallId: toolCard.toolCallId } : {}),
|
|
113
162
|
...("agentId" in toolCard && toolCard.agentId
|
|
114
163
|
? { agentId: toolCard.agentId }
|
|
115
164
|
: {}),
|
|
@@ -121,9 +170,10 @@ export function buildToolCard(
|
|
|
121
170
|
: undefined,
|
|
122
171
|
output: toolCard.text,
|
|
123
172
|
outputData: toolCard.outputData,
|
|
173
|
+
...(toolCard.execution ? { execution: { ...toolCard.execution } } : {}),
|
|
124
174
|
hasResult: Boolean(toolCard.hasResult),
|
|
125
|
-
statusTone
|
|
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
|
+
}
|