@nextclaw/ui 0.27.0 → 0.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/.vite/manifest.json +1246 -1175
  3. package/dist/assets/{abnfDiagram-VRR7QNED-DmeCRAkd.js → abnfDiagram-VRR7QNED-BT0_ycaq.js} +1 -1
  4. package/dist/assets/account-panel-svU9I6E6.js +1 -0
  5. package/dist/assets/agents-CtI89gpb.js +1 -0
  6. package/dist/assets/agents-DWygCmAK.js +1 -0
  7. package/dist/assets/{aiden0z-pptx-renderer.es-ChX5DHqP.js → aiden0z-pptx-renderer.es-ClCaf_Wh.js} +1 -1
  8. package/dist/assets/api-CI6_8Qt1.js +20 -0
  9. package/dist/assets/app-presenter-provider-_UK6sArj.js +28 -0
  10. package/dist/assets/appearance-settings-page-BhedtMx9.js +1 -0
  11. package/dist/assets/apps-C4c4NXqB.js +1 -0
  12. package/dist/assets/{arc-Cu6Ols44.js → arc-cmSdpq1Z.js} +1 -1
  13. package/dist/assets/architecture-TIHT7OUA-BImoGh10.js +1 -0
  14. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DrNB0eLE.js → architectureDiagram-ZJ3FMSHR-aYglVeoS.js} +1 -1
  15. package/dist/assets/{blockDiagram-677ZJIJ3-08rHt6DJ.js → blockDiagram-677ZJIJ3-Di_PBl5q.js} +1 -1
  16. package/dist/assets/{book-open-ClbJmmxP.js → book-open-i_BjUBqL.js} +1 -1
  17. package/dist/assets/{c4Diagram-LMCZKHZV-CCVgRG-u.js → c4Diagram-LMCZKHZV-CfuxybOL.js} +1 -1
  18. package/dist/assets/{card-BdC01NFF.js → card-DokH7OBp.js} +1 -1
  19. package/dist/assets/channel-BDQry0ob.js +1 -0
  20. package/dist/assets/{channels-list-page-BrIvJlop.js → channels-list-page-B7G9lHUm.js} +2 -2
  21. package/dist/assets/chunk-2Q5K7J3B-DkgSFfIy.js +1 -0
  22. package/dist/assets/{chunk-32BRIVSS-BeCaI-JN.js → chunk-32BRIVSS-CCGc4qg4.js} +1 -1
  23. package/dist/assets/{chunk-52WLFC77-ZXIcYPYs.js → chunk-52WLFC77-aJoxU2Ow.js} +1 -1
  24. package/dist/assets/{chunk-5FCAYU7R-DDwTPcy8.js → chunk-5FCAYU7R-CGwtZSax.js} +1 -1
  25. package/dist/assets/{chunk-5HE753X5-WygrMYPL.js → chunk-5HE753X5-DqaMSMeS.js} +1 -1
  26. package/dist/assets/{chunk-5JV3BV7I-o8T3KZgk.js → chunk-5JV3BV7I-DnabINcd.js} +1 -1
  27. package/dist/assets/{chunk-5TONJI2A-BKFbUT7J.js → chunk-5TONJI2A-BhpKv1k2.js} +1 -1
  28. package/dist/assets/{chunk-5VM5RSS4-DYxCRV86.js → chunk-5VM5RSS4-DvkRPit_.js} +1 -1
  29. package/dist/assets/{chunk-7BUUIJ7U-CvSeJlKZ.js → chunk-7BUUIJ7U-CgDQOLnC.js} +1 -1
  30. package/dist/assets/{chunk-BIQX33UG-BhH3RHCY.js → chunk-BIQX33UG-9FEv5b7T.js} +1 -1
  31. package/dist/assets/{chunk-C7G6YPKG-BTL0-xL3.js → chunk-C7G6YPKG-CNGFAFYL.js} +1 -1
  32. package/dist/assets/{chunk-CQNSW5MT-DWAU4QaD.js → chunk-CQNSW5MT-DovTZx0M.js} +1 -1
  33. package/dist/assets/{chunk-CYSBUYHQ-B7ZBMXTx.js → chunk-CYSBUYHQ-DpLEgYa1.js} +1 -1
  34. package/dist/assets/{chunk-EMLP6XTP-D92HvnHE.js → chunk-EMLP6XTP-D4NuN01W.js} +1 -1
  35. package/dist/assets/{chunk-EX3LRPZG-DfOGKwW8.js → chunk-EX3LRPZG-D8Jf4kc2.js} +1 -1
  36. package/dist/assets/{chunk-FWX5IMBZ-DUuJUrXJ.js → chunk-FWX5IMBZ-BdHymAs0.js} +2 -2
  37. package/dist/assets/{chunk-HOUHSVGY-i3MDyUY5.js → chunk-HOUHSVGY-Dd_Ycn9Z.js} +1 -1
  38. package/dist/assets/{chunk-ICXQ74PX-F_pmNX6t.js → chunk-ICXQ74PX-BRPUu3ff.js} +1 -1
  39. package/dist/assets/{chunk-JG7HCLWE-DygOelrU.js → chunk-JG7HCLWE-DnCasQja.js} +1 -1
  40. package/dist/assets/{chunk-JWPE2WC7-BpiZMeRl.js → chunk-JWPE2WC7-C3CkbEj1.js} +1 -1
  41. package/dist/assets/{chunk-MOJQB5TN-CV10sB1I.js → chunk-MOJQB5TN-Pi--K4-1.js} +1 -1
  42. package/dist/assets/{chunk-MOZMSUNE-Cf1WZNm0.js → chunk-MOZMSUNE-3VTspvlh.js} +1 -1
  43. package/dist/assets/{chunk-OGEWGWER-DuSw54VR.js → chunk-OGEWGWER-BetLLyJt.js} +1 -1
  44. package/dist/assets/{chunk-OSBZ3O6U-_rt_XKSZ.js → chunk-OSBZ3O6U-D1VcupAj.js} +1 -1
  45. package/dist/assets/{chunk-PUDLZKDR-BFPaAGNW.js → chunk-PUDLZKDR-Cy4G8SjV.js} +1 -1
  46. package/dist/assets/{chunk-Q4XR5HBZ-GE0v_E5y.js → chunk-Q4XR5HBZ-UYxXzi7_.js} +1 -1
  47. package/dist/assets/{chunk-QBLGF6JB-BK_FpnlJ.js → chunk-QBLGF6JB-CHiGeRY1.js} +1 -1
  48. package/dist/assets/{chunk-R7FJI6CG-CpYh6rGo.js → chunk-R7FJI6CG-C5kVJus8.js} +1 -1
  49. package/dist/assets/{chunk-RYQCIY6F-htnxL_Cl.js → chunk-RYQCIY6F-C6cuuEBn.js} +1 -1
  50. package/dist/assets/{chunk-U6XO7XAA-wDbE4BZ7.js → chunk-U6XO7XAA-CM0EK7Y0.js} +1 -1
  51. package/dist/assets/{chunk-V7JOEXUC-DDRSVFTB.js → chunk-V7JOEXUC-DWJZIsME.js} +1 -1
  52. package/dist/assets/chunk-VAUOI2AC-CsTIB8qD.js +1 -0
  53. package/dist/assets/{chunk-VR4S4FIN-COdhHR1o.js → chunk-VR4S4FIN-CY5KYeEi.js} +1 -1
  54. package/dist/assets/{chunk-WYO6CB5R-DPDGhyF6.js → chunk-WYO6CB5R-B30N3ZP8.js} +3 -3
  55. package/dist/assets/chunk-XXDRQBXY-DK6dz0Q3.js +1 -0
  56. package/dist/assets/{chunk-YOTPTUD7-D8ir9Nx5.js → chunk-YOTPTUD7-CBu82JpO.js} +1 -1
  57. package/dist/assets/{chunk-ZGVPDNZ5-BuNKD836.js → chunk-ZGVPDNZ5-BN5NX5qA.js} +1 -1
  58. package/dist/assets/{chunk-ZIRB5QZD-DyHzq8jH.js → chunk-ZIRB5QZD-DcDXK29L.js} +1 -1
  59. package/dist/assets/{circle-dashed-nDaMRmP-.js → circle-dashed-Cx8CIEbg.js} +1 -1
  60. package/dist/assets/classDiagram-OUVF2IWQ-BYygHCrp.js +1 -0
  61. package/dist/assets/classDiagram-v2-EOCWNBFH-BgQ0XKYz.js +1 -0
  62. package/dist/assets/{client-DlFB5dW5.js → client-CUJUci3r.js} +1 -1
  63. package/dist/assets/{config-split-page-C06fU9OQ.js → config-split-page-hY1xug28.js} +1 -1
  64. package/dist/assets/{confirm-dialog-DpcmlXhd.js → confirm-dialog-B-ejV666.js} +1 -1
  65. package/dist/assets/{context-menu-DKIEaY7f.js → context-menu-dGVv8klA.js} +9 -9
  66. package/dist/assets/{cose-bilkent-JH36ORCC-B1I1_P6a.js → cose-bilkent-JH36ORCC-CmeJVoTw.js} +1 -1
  67. package/dist/assets/{createLucideIcon-Lnt-4LMC.js → createLucideIcon-reFMl-W-.js} +1 -1
  68. package/dist/assets/cron-DkYSmE-f.js +1 -0
  69. package/dist/assets/cynefin-VYW2F7L2-DKbotNWS.js +1 -0
  70. package/dist/assets/{cynefinDiagram-TSTJHNR4-DcOnhnCo.js → cynefinDiagram-TSTJHNR4-DGOUMgPP.js} +1 -1
  71. package/dist/assets/{dagre-nSYhf9yY.js → dagre-CMxSkbJJ.js} +1 -1
  72. package/dist/assets/{dagre-VKFMJZFB-hAX89lg3.js → dagre-VKFMJZFB-3YUBEXwo.js} +1 -1
  73. package/dist/assets/{desktop-capabilities-page-DPORyZAu.js → desktop-capabilities-page-CYJTQ3nC.js} +1 -1
  74. package/dist/assets/{desktop-update-config-BMTeWZmn.js → desktop-update-config-guKAozXO.js} +1 -1
  75. package/dist/assets/{diagram-FQU43EPY-C1mRMD2k.js → diagram-FQU43EPY-Br1g--ww.js} +1 -1
  76. package/dist/assets/{diagram-G47NLZAW-D2XBn5al.js → diagram-G47NLZAW-D1wJCBgr.js} +1 -1
  77. package/dist/assets/{diagram-NH7WQ7WH-3LmRsmkC.js → diagram-NH7WQ7WH-Cgg2auLH.js} +1 -1
  78. package/dist/assets/{diagram-OA4YK3LP-CFWn-osC.js → diagram-OA4YK3LP-R-wHiWqh.js} +1 -1
  79. package/dist/assets/{diagram-WEI45ONY-BqNRbUvU.js → diagram-WEI45ONY-CsNnKV0P.js} +1 -1
  80. package/dist/assets/{dialog-BNXSPMX_.js → dialog-DLOwdjT1.js} +1 -1
  81. package/dist/assets/{dist-Cuiqqp1O.js → dist-ZaFFmAGl.js} +1 -1
  82. package/dist/assets/doc-browser-B0zg0EXA.js +1 -0
  83. package/dist/assets/doc-browser-context-CTmOmuqa.js +1 -0
  84. package/dist/assets/{docx-preview-CunAATyC.js → docx-preview-kPn_EtAr.js} +1 -1
  85. package/dist/assets/{download-f_sPcoLZ.js → download-DovI3Yvf.js} +1 -1
  86. package/dist/assets/{ebnfDiagram-CCIWWBDH-CYb8IwqD.js → ebnfDiagram-CCIWWBDH-DCyhIPdi.js} +1 -1
  87. package/dist/assets/{erDiagram-Q63AITRT-H47mUoeK.js → erDiagram-Q63AITRT-D0ZJYAUf.js} +1 -1
  88. package/dist/assets/eventmodeling-45OFAUF4-SIhbIFIw.js +1 -0
  89. package/dist/assets/{extensions-B-0xQz3L.js → extensions-BZ3qrM8M.js} +1 -1
  90. package/dist/assets/{external-link-CbVlnlNy.js → external-link-DUD3AbsF.js} +1 -1
  91. package/dist/assets/flowDiagram-23GEKE2U-DdAjkU7I.js +1 -0
  92. package/dist/assets/{form-actions-BoTqELHB.js → form-actions-B8VniHts.js} +1 -1
  93. package/dist/assets/{ganttDiagram-NO4QXBWP-hSX45zfV.js → ganttDiagram-NO4QXBWP-CiXDcXZD.js} +1 -1
  94. package/dist/assets/gitGraph-TEB2WS4Q-DnMNpESZ.js +1 -0
  95. package/dist/assets/{gitGraphDiagram-IHSO6WYX-Br0UW2qg.js → gitGraphDiagram-IHSO6WYX-BSiIV48S.js} +1 -1
  96. package/dist/assets/{i18n-B0V5NkFj.js → i18n-IGQbjBqS.js} +2 -2
  97. package/dist/assets/inbox-CJBCLB1X.js +1 -0
  98. package/dist/assets/{inbox-Bna8iUy0.js → inbox-ClAG7SI_.js} +1 -1
  99. package/dist/assets/info-DKCQHKI2-BSm-69p_.js +1 -0
  100. package/dist/assets/{infoDiagram-FWYZ7A6U-DQmP_67w.js → infoDiagram-FWYZ7A6U-CCIgBCVq.js} +1 -1
  101. package/dist/assets/{ishikawaDiagram-FXEZZL3T-BHAV2oes.js → ishikawaDiagram-FXEZZL3T-nQ_baN5u.js} +1 -1
  102. package/dist/assets/{journeyDiagram-5HDEW3XC-CFHax4_d.js → journeyDiagram-5HDEW3XC-ABueIPzj.js} +1 -1
  103. package/dist/assets/{kanban-definition-HUTT4EX6-B4ZA9rp5.js → kanban-definition-HUTT4EX6-8FKS3fmQ.js} +1 -1
  104. package/dist/assets/katex-DPgHtix3.js +1 -0
  105. package/dist/assets/keyboard-shortcuts-settings-page-B5oxyPS2.js +1 -0
  106. package/dist/assets/{line-BboM0HLF.js → line-Biw8iEUz.js} +1 -1
  107. package/dist/assets/{linear-4K_M1oYM.js → linear-D7CWUHTF.js} +1 -1
  108. package/dist/assets/login-page-BxSMwUsc.js +1 -0
  109. package/dist/assets/{login-page-JNr5JCEw.js → login-page-DYaozpzz.js} +1 -1
  110. package/dist/assets/main-D_zmSVS1.css +1 -0
  111. package/dist/assets/main-gFUvLF57.js +2 -0
  112. package/dist/assets/main-i18n-catalog.config-itg3Gvzp.js +1 -0
  113. package/dist/assets/{map-26GpO1wn.js → map-CDu47gTf.js} +1 -1
  114. package/dist/assets/marketplace-DsAf_rnt.js +1 -0
  115. package/dist/assets/mcp-marketplace-page-BqUz09cj.js +1 -0
  116. package/dist/assets/{mermaid-parser.core-BTJFnvRC.js → mermaid-parser.core-Cc9wjl82.js} +2 -2
  117. package/dist/assets/{mermaid.core-B7ufoXgq.js → mermaid.core-GWlaVWz1.js} +3 -3
  118. package/dist/assets/{mindmap-definition-LN4V7U3C-BHyru6KE.js → mindmap-definition-LN4V7U3C-oqnKsz-R.js} +1 -1
  119. package/dist/assets/{model-config-page-C-wbM6Fe.js → model-config-page-Cduyz2Hy.js} +1 -1
  120. package/dist/assets/{navigation-link-DFLSVIut.js → navigation-link-D-JSWFxj.js} +1 -1
  121. package/dist/assets/{ordinal-BqFe2ePF.js → ordinal-DImbxqBz.js} +1 -1
  122. package/dist/assets/packet-7NZHBO7P-1YQvC6mT.js +1 -0
  123. package/dist/assets/panel-app-i18n-catalog.config-BfLHr1tc.js +1 -0
  124. package/dist/assets/panel-apps-Bg5Fzm3T.js +1 -0
  125. package/dist/assets/panelStandalone-oHxbXvYt.js +2 -0
  126. package/dist/assets/{pegDiagram-2B236MQR-PVTatU5j.js → pegDiagram-2B236MQR-DYAB8CEz.js} +1 -1
  127. package/dist/assets/pie-RZYD4A2V-BGoNOSwu.js +1 -0
  128. package/dist/assets/{pieDiagram-ENE6RG2P-CGc-QCc7.js → pieDiagram-ENE6RG2P-DGsH0Kk5.js} +1 -1
  129. package/dist/assets/plus-DUdl68Eo.js +1 -0
  130. package/dist/assets/{privacy-settings-page-CPdEiouE.js → privacy-settings-page-DG9E8N-F.js} +1 -1
  131. package/dist/assets/projects-DvHyoKIF.js +1 -0
  132. package/dist/assets/{provider-models-XRArsply.js → provider-models-COzjYl29.js} +1 -1
  133. package/dist/assets/{provider-scoped-model-input-C8JI-hus.js → provider-scoped-model-input-Cvy74uj8.js} +1 -1
  134. package/dist/assets/{providers-config-page-fyMlVgvq.js → providers-config-page-7rCwVnhh.js} +1 -1
  135. package/dist/assets/{puzzle-Bk3ud1KI.js → puzzle-C5Wfpjk_.js} +1 -1
  136. package/dist/assets/{pwa-Cx6Fnox4.js → pwa-CAg_9zFs.js} +1 -1
  137. package/dist/assets/{quadrantDiagram-ABIIQ3AL-DGL36riv.js → quadrantDiagram-ABIIQ3AL-BegGcT3l.js} +1 -1
  138. package/dist/assets/radar-I7S5WNFK-uPku9RRq.js +1 -0
  139. package/dist/assets/railroad-3IZDKUUU-DHnmbuIo.js +1 -0
  140. package/dist/assets/railroad-abnf-AHOZXSZD-DiZs--Iw.js +1 -0
  141. package/dist/assets/railroad-ebnf-EBAXGLYW-Nr53gnNE.js +1 -0
  142. package/dist/assets/railroad-peg-LSFZ7HO6-Cm_PuwJS.js +1 -0
  143. package/dist/assets/{railroadDiagram-RFXS5EU6-CyNwvhSb.js → railroadDiagram-RFXS5EU6-B3yJ2yxE.js} +1 -1
  144. package/dist/assets/{refresh-cw-Ca2napNx.js → refresh-cw-OhwaPDn1.js} +1 -1
  145. package/dist/assets/remote-Dhr4K1oK.js +1 -0
  146. package/dist/assets/{requirementDiagram-TGXJPOKE-C31NMKJ0.js → requirementDiagram-TGXJPOKE-DtEyoEXG.js} +1 -1
  147. package/dist/assets/{rotate-cw-DXg7sNUE.js → rotate-cw-aBponvp7.js} +1 -1
  148. package/dist/assets/runtime-config-page-FI2pk3HH.js +1 -0
  149. package/dist/assets/{sankeyDiagram-HTMAVEWB-CwrQ_7PA.js → sankeyDiagram-HTMAVEWB-h9AYS2UB.js} +1 -1
  150. package/dist/assets/{save-DNigywZ9.js → save-394QCMXn.js} +1 -1
  151. package/dist/assets/{search-U9C0oTQJ.js → search-BG2qAlNZ.js} +1 -1
  152. package/dist/assets/{search-config-page-Bdx_RR1q.js → search-config-page-D4LeiB9y.js} +1 -1
  153. package/dist/assets/{secrets-config-page-CvMIcsfJ.js → secrets-config-page-BzGcr5Vw.js} +2 -2
  154. package/dist/assets/security-config-vD_V0BQF.js +1 -0
  155. package/dist/assets/{select-DxwDeKUR.js → select-xUXYV0Gr.js} +1 -1
  156. package/dist/assets/{sequenceDiagram-DBY2YBRQ-EFD482_y.js → sequenceDiagram-DBY2YBRQ-D-uxCkob.js} +1 -1
  157. package/dist/assets/service-apps-panel-BTqXn0pg.js +1 -0
  158. package/dist/assets/{settings-2-BrBUdRoY.js → settings-2-CWOGRRaA.js} +1 -1
  159. package/dist/assets/{sizeCapture-X5ZJPWSS-TDvULTE1.js → sizeCapture-X5ZJPWSS-DBzZytQo.js} +1 -1
  160. package/dist/assets/{src-CI2AvEMj.js → src-zrGbZsfc.js} +1 -1
  161. package/dist/assets/{stateDiagram-2N3HPSRC-JdPsIYHo.js → stateDiagram-2N3HPSRC-DyDy7AHq.js} +1 -1
  162. package/dist/assets/stateDiagram-v2-6OUMAXLB-CtkpO6aA.js +1 -0
  163. package/dist/assets/{status-dot-BbGsrkIX.js → status-dot-CYifnSnt.js} +1 -1
  164. package/dist/assets/{swimlanes-5IMT3BWC-BnxpKeK_.js → swimlanes-5IMT3BWC-BzOduS38.js} +2 -2
  165. package/dist/assets/swimlanesDiagram-G3AALYLV-BSdi6mvi.js +8 -0
  166. package/dist/assets/{tag-chip-BDbxcf6P.js → tag-chip-CjM6lyeh.js} +1 -1
  167. package/dist/assets/theme-provider-BToFZGZG.js +1 -0
  168. package/dist/assets/{timeline-definition-FHXFAJF6-2IL22-uC.js → timeline-definition-FHXFAJF6-CmgnEF2t.js} +1 -1
  169. package/dist/assets/{trash-2-DwSkRfeB.js → trash-2-DvJ6duOW.js} +1 -1
  170. package/dist/assets/treeView-QDETBFTQ-DBoqypPR.js +1 -0
  171. package/dist/assets/treemap-6X3UGDF4-BMeL3P0q.js +1 -0
  172. package/dist/assets/use-config-COusaY5p.js +1 -0
  173. package/dist/assets/{use-desktop-capabilities-D7odgaTB.js → use-desktop-capabilities-B-u6ojjW.js} +1 -1
  174. package/dist/assets/{use-viewport-layout-BANU5E1t.js → use-viewport-layout-IKKGpjzF.js} +1 -1
  175. package/dist/assets/{vennDiagram-L72KCM5P-Cjf6n5Va.js → vennDiagram-L72KCM5P-DwS7D2kg.js} +1 -1
  176. package/dist/assets/wardley-OPB4EBWU-Dqxe7zN6.js +1 -0
  177. package/dist/assets/{wardleyDiagram-EHGQE667-yv4qKbBO.js → wardleyDiagram-EHGQE667-B4X8OOL4.js} +1 -1
  178. package/dist/assets/{x-BqGj6_Vq.js → x-E9VB4HQx.js} +1 -1
  179. package/dist/assets/{xychartDiagram-FW5EYKEG-Bmp8Ck8X.js → xychartDiagram-FW5EYKEG-BjRQ_0nx.js} +1 -1
  180. package/dist/index.html +41 -40
  181. package/dist/panel-standalone.html +16 -16
  182. package/package.json +6 -6
  183. package/src/features/agents/components/__tests__/agents-page.test.tsx +8 -5
  184. package/src/features/agents/components/agent-details-dialog.tsx +19 -15
  185. package/src/features/agents/components/agents-page.tsx +27 -38
  186. package/src/features/app-data/components/app-retained-data-section.tsx +2 -2
  187. package/src/features/apps/components/app-marketplace-catalog.tsx +5 -5
  188. package/src/features/apps/components/app-marketplace-detail.tsx +11 -2
  189. package/src/features/apps/components/app-packages-panel.tsx +4 -4
  190. package/src/features/apps/components/apps-panel.tsx +3 -1
  191. package/src/features/chat/components/providers/chat-runtime.provider.tsx +1 -1
  192. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +0 -1
  193. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +13 -2
  194. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +8 -1
  195. package/src/features/chat/features/conversation/components/session-conversation-input-top-slot.tsx +44 -0
  196. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +4 -38
  197. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +127 -10
  198. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +12 -2
  199. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +88 -73
  200. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-query.ts +5 -15
  201. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +53 -13
  202. package/src/features/chat/features/conversation/hooks/use-session-submission-recovery.ts +83 -0
  203. package/src/features/chat/features/conversation/types/session-conversation-input.types.ts +38 -0
  204. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +1 -29
  205. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +1 -8
  206. package/src/features/chat/features/message/components/__tests__/chat-message-list-skill-preview.container.test.tsx +14 -1
  207. package/src/features/chat/features/message/hooks/use-chat-inline-token-actions.ts +7 -2
  208. package/src/features/chat/features/session/utils/__tests__/chat-session-route.utils.test.ts +26 -0
  209. package/src/features/chat/features/session/utils/chat-session-preference-governance.utils.ts +14 -8
  210. package/src/features/chat/features/session/utils/chat-session-route.utils.ts +9 -12
  211. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +14 -2
  212. package/src/features/chat/features/workspace/components/__tests__/session-cron-job-content.test.tsx +5 -1
  213. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +1 -1
  214. package/src/features/chat/features/workspace/components/session-cron-job-content.tsx +6 -22
  215. package/src/features/chat/index.ts +1 -0
  216. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +37 -47
  217. package/src/features/chat/pages/ncp-chat-page.tsx +3 -3
  218. package/src/features/chat/stores/__tests__/chat-composer-draft.store.test.ts +67 -1
  219. package/src/features/chat/stores/chat-composer-draft.store.ts +230 -29
  220. package/src/features/cron/components/__tests__/cron-config.test.tsx +8 -2
  221. package/src/features/cron/components/cron-config.tsx +6 -17
  222. package/src/features/cron/components/cron-job-detail-dialog.tsx +10 -9
  223. package/src/features/cron/components/cron-job-resource.tsx +51 -0
  224. package/src/features/cron/index.ts +1 -0
  225. package/src/features/inbox/pages/inbox-page.tsx +8 -5
  226. package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +1 -0
  227. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +1 -0
  228. package/src/features/marketplace/components/mcp/__tests__/mcp-marketplace-page.test.tsx +2 -0
  229. package/src/features/marketplace/components/mcp/mcp-marketplace-card.tsx +2 -2
  230. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +23 -1
  231. package/src/features/marketplace/hooks/use-marketplace-item-detail.ts +11 -0
  232. package/src/features/panel-apps/index.ts +1 -0
  233. package/src/features/projects/components/work/project-work-item-drawer.tsx +7 -10
  234. package/src/features/projects/pages/project-home-page.tsx +18 -23
  235. package/src/features/projects/pages/projects-page.test.tsx +4 -1
  236. package/src/features/right-panel-resources/components/__tests__/system-object-resource.test.tsx +74 -6
  237. package/src/features/right-panel-resources/components/native-object-resource.tsx +134 -0
  238. package/src/features/right-panel-resources/components/page-resource-icon.tsx +1 -1
  239. package/src/features/right-panel-resources/components/system-object-resource.tsx +66 -1
  240. package/src/features/right-panel-resources/components/workspace-file-resource.tsx +9 -0
  241. package/src/features/right-panel-resources/configs/right-panel-resource-routes.config.ts +11 -21
  242. package/src/features/right-panel-resources/managers/__tests__/page-resource.manager.test.ts +14 -4
  243. package/src/features/right-panel-resources/managers/page-resource.manager.ts +38 -21
  244. package/src/features/right-panel-resources/utils/__tests__/right-panel-resource-route-resolver.utils.test.ts +12 -0
  245. package/src/features/service-apps/components/service-apps-panel.tsx +5 -2
  246. package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +16 -0
  247. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +2 -2
  248. package/src/shared/components/doc-browser/__tests__/resource-link-interceptor.test.tsx +29 -1
  249. package/src/shared/components/doc-browser/stores/doc-browser.store.ts +13 -7
  250. package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +2 -1
  251. package/src/shared/components/ui/skeleton.tsx +16 -2
  252. package/src/shared/lib/i18n/locales/en-US/chat.json +8 -0
  253. package/src/shared/lib/i18n/locales/zh-CN/chat.json +8 -0
  254. package/src/shared/lib/transport/__tests__/local-transport.service.test.ts +24 -0
  255. package/src/shared/lib/transport/local-transport.service.ts +8 -6
  256. package/dist/assets/account-panel-BYxGT4m4.js +0 -1
  257. package/dist/assets/api-CG22BskB.js +0 -20
  258. package/dist/assets/app-presenter-provider-CJFjhVYI.js +0 -28
  259. package/dist/assets/appearance-settings-page-BcXr0vHp.js +0 -1
  260. package/dist/assets/architecture-TIHT7OUA-CLjV_xu4.js +0 -1
  261. package/dist/assets/channel-BgpbPyEH.js +0 -1
  262. package/dist/assets/chunk-2Q5K7J3B-DjhMPqAC.js +0 -1
  263. package/dist/assets/chunk-VAUOI2AC-GYimZYx9.js +0 -1
  264. package/dist/assets/chunk-XXDRQBXY-KKCf0HV5.js +0 -1
  265. package/dist/assets/classDiagram-OUVF2IWQ-iRMPRR8M.js +0 -1
  266. package/dist/assets/classDiagram-v2-EOCWNBFH-BguFy_wj.js +0 -1
  267. package/dist/assets/cynefin-VYW2F7L2-CtbjXuCW.js +0 -1
  268. package/dist/assets/doc-browser-CDEapjmj.js +0 -1
  269. package/dist/assets/doc-browser-context-0QDWYorh.js +0 -1
  270. package/dist/assets/eventmodeling-45OFAUF4-Djd6id_u.js +0 -1
  271. package/dist/assets/flowDiagram-23GEKE2U-DUrsM_ZL.js +0 -1
  272. package/dist/assets/gitGraph-TEB2WS4Q-CU6P_2Cm.js +0 -1
  273. package/dist/assets/info-DKCQHKI2-Bj2P_7RC.js +0 -1
  274. package/dist/assets/katex-BprZ-Wwb.js +0 -1
  275. package/dist/assets/keyboard-shortcuts-settings-page-BluWKrSo.js +0 -1
  276. package/dist/assets/login-page-BMHW0Hh0.js +0 -1
  277. package/dist/assets/main-BCX_Untx.css +0 -1
  278. package/dist/assets/main-C5kbmGUV.js +0 -2
  279. package/dist/assets/main-i18n-catalog.config-DO4wNT7T.js +0 -1
  280. package/dist/assets/mcp-marketplace-page-CV-PHKTN.js +0 -1
  281. package/dist/assets/packet-7NZHBO7P-ti5ge2jr.js +0 -1
  282. package/dist/assets/panel-app-i18n-catalog.config-Bo52dPuF.js +0 -1
  283. package/dist/assets/panelStandalone-PuuMF65O.js +0 -2
  284. package/dist/assets/pie-RZYD4A2V-Bnep8TyV.js +0 -1
  285. package/dist/assets/plus-CywSlIbG.js +0 -1
  286. package/dist/assets/projects-D_cd_t4G.js +0 -1
  287. package/dist/assets/radar-I7S5WNFK-BYg-icAP.js +0 -1
  288. package/dist/assets/railroad-3IZDKUUU-_CHO-Fi1.js +0 -1
  289. package/dist/assets/railroad-abnf-AHOZXSZD-hbQDevHe.js +0 -1
  290. package/dist/assets/railroad-ebnf-EBAXGLYW-v4RZkOfi.js +0 -1
  291. package/dist/assets/railroad-peg-LSFZ7HO6-bC9BqkI5.js +0 -1
  292. package/dist/assets/remote-D5MBQNZE.js +0 -1
  293. package/dist/assets/runtime-config-page--ivCxBT4.js +0 -1
  294. package/dist/assets/security-config-CQmMqQ8v.js +0 -1
  295. package/dist/assets/service-apps-panel-CiYO1Be4.js +0 -1
  296. package/dist/assets/stateDiagram-v2-6OUMAXLB-4_aAQfB_.js +0 -1
  297. package/dist/assets/swimlanesDiagram-G3AALYLV-wCqfwsw-.js +0 -8
  298. package/dist/assets/theme-provider-D98XGa-c.js +0 -1
  299. package/dist/assets/treeView-QDETBFTQ-BAyOb14Z.js +0 -1
  300. package/dist/assets/treemap-6X3UGDF4-DgPDM0zS.js +0 -1
  301. package/dist/assets/use-config-D3cNzk2k.js +0 -1
  302. package/dist/assets/wardley-OPB4EBWU-eCElh8z5.js +0 -1
  303. /package/dist/assets/{array-DstOKG9P.js → array-DsNX3jRj.js} +0 -0
  304. /package/dist/assets/{chunk-KEIR6QF5-0TA8nwgf.js → chunk-KEIR6QF5-BOAXXeyv.js} +0 -0
  305. /package/dist/assets/{chunk-Y2CYZVJY-rfGCG6pM.js → chunk-Y2CYZVJY-CHjSjwoV.js} +0 -0
  306. /package/dist/assets/{config-hints-BfGoD7-o.js → config-hints-CaB3I7KP.js} +0 -0
  307. /package/dist/assets/{cytoscape.esm-DdIcNEda.js → cytoscape.esm-j4J9a91l.js} +0 -0
  308. /package/dist/assets/{defaultLocale-CzXix2iJ.js → defaultLocale-Bmxjp-V_.js} +0 -0
  309. /package/dist/assets/{dist-D5dwMwKA.js → dist-DwLj1Wg5.js} +0 -0
  310. /package/dist/assets/{graphlib-BKOe0eFe.js → graphlib-CC0HTn1v.js} +0 -0
  311. /package/dist/assets/{host-capabilities-CnSVTaot.js → host-capabilities-B107nwS3.js} +0 -0
  312. /package/dist/assets/{init-OCFb3zgY.js → init-Biz9Rus1.js} +0 -0
  313. /package/dist/assets/{jszip.min-B2uhSxvV.js → jszip.min-DQvBVV_R.js} +0 -0
  314. /package/dist/assets/{katex-dBrYsE7B.js → katex-BzT-4KBu.js} +0 -0
  315. /package/dist/assets/{middleware-CAXxl-af.js → middleware-B--6mAUM.js} +0 -0
  316. /package/dist/assets/{navigation-history-CzErJuPG.js → navigation-history-CCnxsAKR.js} +0 -0
  317. /package/dist/assets/{panel-app-CSQmm_7E.js → panel-app-BBTg0tVp.js} +0 -0
  318. /package/dist/assets/{path-T5eoIRSJ.js → path-4azsBPZv.js} +0 -0
  319. /package/dist/assets/{rough.esm-DmGXmZEB.js → rough.esm-w1lBArN6.js} +0 -0
  320. /package/dist/assets/{xlsx-CoYacou7.js → xlsx--SB9KlFl.js} +0 -0
@@ -2,8 +2,13 @@ import { act, renderHook, waitFor } from '@testing-library/react';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
  import { NextClawClientError, type UiNcpSessionQueuedInputView } from '@nextclaw/client-sdk';
4
4
  import type { NcpAgentSendEnvelope, NcpMessage, NcpRunHandle } from '@nextclaw/ncp';
5
+ import { NcpHttpSendError } from '@nextclaw/ncp-http-agent-client';
6
+
7
+ const fetchNcpSessionMessages = vi.hoisted(() => vi.fn());
8
+ vi.mock('@/shared/lib/api', () => ({ fetchNcpSessionMessages }));
5
9
 
6
10
  import { useSessionConversationController } from '@/features/chat/features/conversation/hooks/use-session-conversation-controller';
11
+ import type { ChatComposerSubmission } from '@/features/chat/stores/chat-composer-draft.store';
7
12
 
8
13
  type TestAgentSend = (envelope: NcpAgentSendEnvelope) => Promise<NcpRunHandle | null>;
9
14
 
@@ -107,6 +112,7 @@ function createControllerParams(params: {
107
112
  composerFocusRequestId: 0,
108
113
  nodes: [createTextNode('next task')],
109
114
  pendingProjectRoot: null,
115
+ pendingSubmission: null as ChatComposerSubmission | null,
110
116
  pendingSessionType: 'default',
111
117
  selectedModel: undefined,
112
118
  selectedSessionType: 'default',
@@ -126,13 +132,58 @@ function createControllerParams(params: {
126
132
  },
127
133
  selectedAgentId: 'main',
128
134
  sessionKey: 'session-1',
129
- resetComposer: vi.fn(),
130
135
  restoreComposer: vi.fn(),
136
+ beginSubmission: vi.fn(),
137
+ acceptSubmission: vi.fn(),
138
+ failSubmission: vi.fn(),
139
+ restoreSubmission: vi.fn(),
140
+ discardSubmission: vi.fn(),
131
141
  onSessionMaterialized: vi.fn(),
132
142
  setSendError: vi.fn(),
133
143
  };
134
144
  }
135
145
 
146
+ describe('useSessionConversationController HTTP message identity', () => {
147
+ it.each(['send', 'sendSteering', 'sendPresetMessage'] as const)(
148
+ 'supports %s over HTTP without randomUUID and keeps submission identities distinct',
149
+ async (action) => {
150
+ const params = createControllerParams({ isRunning: false });
151
+ const { result } = renderHook(() => useSessionConversationController(params));
152
+ const getRandomValues = globalThis.crypto.getRandomValues.bind(globalThis.crypto);
153
+ const now = vi.spyOn(Date, 'now').mockReturnValue(1780000000000);
154
+ vi.stubGlobal('crypto', { getRandomValues });
155
+ try {
156
+ await act(async () => {
157
+ if (action === 'sendPresetMessage') {
158
+ await result.current.sendPresetMessage('Update this session title');
159
+ await result.current.sendPresetMessage('Update this session title again');
160
+ } else {
161
+ await result.current[action]();
162
+ await result.current[action]();
163
+ }
164
+ });
165
+ } finally {
166
+ now.mockRestore();
167
+ vi.unstubAllGlobals();
168
+ }
169
+
170
+ expect(params.agent.send).toHaveBeenCalledTimes(2);
171
+ const envelopes = params.agent.send.mock.calls.map(([envelope]) => envelope);
172
+ expect(envelopes[0].message.id).not.toBe(envelopes[1].message.id);
173
+ for (const envelope of envelopes) {
174
+ expect(envelope.message.id).toMatch(/^user-/);
175
+ expect(envelope.idempotencyKey).toBe(envelope.message.id);
176
+ expect(envelope.sessionId).toBe('session-1');
177
+ if (action !== 'sendPresetMessage') {
178
+ expect(params.beginSubmission).toHaveBeenCalledWith(envelope, expect.any(Object));
179
+ expect(params.acceptSubmission).toHaveBeenCalledWith(envelope.message.id);
180
+ }
181
+ }
182
+ if (action === 'sendPresetMessage') expect(params.beginSubmission).not.toHaveBeenCalled();
183
+ },
184
+ );
185
+ });
186
+
136
187
  describe('useSessionConversationController backend run queue', () => {
137
188
  it('preallocates one stable session identity for the first root message', async () => {
138
189
  const send = vi.fn<TestAgentSend>(async (envelope) => {
@@ -162,6 +213,7 @@ describe('useSessionConversationController backend run queue', () => {
162
213
  const envelope = send.mock.calls[0]?.[0];
163
214
  expect(envelope?.sessionId).toMatch(/^ncp-/);
164
215
  expect(envelope?.message.sessionId).toBe(envelope?.sessionId);
216
+ expect(envelope?.idempotencyKey).toBe(envelope?.message.id);
165
217
  expect(params.onSessionMaterialized).toHaveBeenCalledWith(envelope?.sessionId);
166
218
  });
167
219
 
@@ -182,7 +234,21 @@ describe('useSessionConversationController backend run queue', () => {
182
234
  parts: [{ type: 'text', text: 'next task' }],
183
235
  }),
184
236
  });
185
- expect(params.resetComposer).toHaveBeenCalledTimes(1);
237
+ expect(params.beginSubmission).toHaveBeenCalledTimes(1);
238
+ });
239
+
240
+ it('attempts a send when a nonempty draft has a stale blocked runtime status', async () => {
241
+ const send = createSendMock();
242
+ const params = { ...createControllerParams({ isRunning: false, send }), isRuntimeBlocked: true };
243
+ const { result } = renderHook(() => useSessionConversationController(params));
244
+
245
+ expect(result.current.sendDisabled).toBe(false);
246
+ await act(async () => {
247
+ await result.current.send();
248
+ });
249
+
250
+ expect(send).toHaveBeenCalledTimes(1);
251
+ expect(params.setSendError).toHaveBeenLastCalledWith(null);
186
252
  });
187
253
 
188
254
  it('uses prefer-steer for command/control-enter submission without changing the send button path', async () => {
@@ -241,7 +307,7 @@ describe('useSessionConversationController backend run queue', () => {
241
307
  }),
242
308
  });
243
309
  expect(envelope?.metadata).not.toHaveProperty('ui_inline_tokens');
244
- expect(params.resetComposer).not.toHaveBeenCalled();
310
+ expect(params.beginSubmission).not.toHaveBeenCalled();
245
311
  expect(params.restoreComposer).not.toHaveBeenCalled();
246
312
  });
247
313
 
@@ -257,7 +323,7 @@ describe('useSessionConversationController backend run queue', () => {
257
323
  await expect(result.current.sendPresetMessage('Update title')).rejects.toThrow(sendError);
258
324
  });
259
325
 
260
- expect(params.resetComposer).not.toHaveBeenCalled();
326
+ expect(params.beginSubmission).not.toHaveBeenCalled();
261
327
  expect(params.restoreComposer).not.toHaveBeenCalled();
262
328
  expect(params.setSendError).toHaveBeenLastCalledWith(sendError.message);
263
329
  });
@@ -275,7 +341,7 @@ describe('useSessionConversationController backend run queue', () => {
275
341
  submission = result.current.send();
276
342
  });
277
343
 
278
- expect(params.resetComposer).toHaveBeenCalledTimes(1);
344
+ expect(params.beginSubmission).toHaveBeenCalledTimes(1);
279
345
  expect(result.current.queuedInputs).toEqual([
280
346
  expect.objectContaining({
281
347
  isSubmitting: true,
@@ -306,13 +372,64 @@ describe('useSessionConversationController backend run queue', () => {
306
372
  });
307
373
 
308
374
  expect(result.current.queuedInputs).toEqual([]);
309
- expect(params.restoreComposer).toHaveBeenCalledWith(expect.objectContaining({
310
- text: 'next task',
311
- }));
312
- expect(params.setSendError).toHaveBeenLastCalledWith(sendError.message);
375
+ expect(params.failSubmission).toHaveBeenCalledWith(expect.any(String), 'uncertain', sendError.message);
313
376
  expect(params.runQueue.refreshQueuedInputs).not.toHaveBeenCalled();
314
377
  });
315
378
 
379
+ it('shows a send failure and restores the draft when the client returns no acceptance handle', async () => {
380
+ const send = createSendMock(null);
381
+ const params = createControllerParams({ isRunning: false, send });
382
+ const { result } = renderHook(() => useSessionConversationController(params));
383
+
384
+ await act(async () => {
385
+ await expect(result.current.send()).rejects.toThrow('Failed to send message');
386
+ });
387
+
388
+ expect(params.failSubmission).toHaveBeenCalledWith(expect.any(String), 'rejected', 'Failed to send message');
389
+ expect(params.onSessionMaterialized).not.toHaveBeenCalled();
390
+ });
391
+
392
+ it('classifies an explicit HTTP rejection so the draft can be restored', async () => {
393
+ const error = new NcpHttpSendError('invalid request', true);
394
+ const send = vi.fn<TestAgentSend>(async () => { throw error; });
395
+ const params = createControllerParams({ isRunning: false, send });
396
+ const { result } = renderHook(() => useSessionConversationController(params));
397
+ await act(async () => {
398
+ await expect(result.current.send()).rejects.toThrow(error);
399
+ });
400
+ expect(params.failSubmission).toHaveBeenCalledWith(expect.any(String), 'rejected', 'invalid request');
401
+ });
402
+
403
+ it('rechecks the original message before retrying with the same identity', async () => {
404
+ fetchNcpSessionMessages.mockResolvedValue({ messages: [] });
405
+ const params = createControllerParams({ isRunning: false });
406
+ const envelope: NcpAgentSendEnvelope = {
407
+ sessionId: 'session-1', idempotencyKey: 'user-original',
408
+ message: {
409
+ id: 'user-original', sessionId: 'session-1', role: 'user', status: 'final',
410
+ timestamp: '2026-09-24T00:00:00.000Z', parts: [{ type: 'text', text: 'next task' }],
411
+ },
412
+ };
413
+ params.inputSnapshot.pendingSubmission = {
414
+ envelope,
415
+ composer: {
416
+ text: 'next task', nodes: [createTextNode('next task')],
417
+ selectedSkills: [], skillRecords: [], attachments: [],
418
+ },
419
+ status: 'uncertain',
420
+ };
421
+ const { result } = renderHook(() => useSessionConversationController(params));
422
+ expect(result.current.sendDisabled).toBe(true);
423
+ await act(async () => {
424
+ await result.current.send();
425
+ await result.current.retryPendingSubmission();
426
+ });
427
+ expect(fetchNcpSessionMessages).toHaveBeenCalledWith('session-1', { limit: 100 });
428
+ expect(params.agent.send).toHaveBeenCalledTimes(1);
429
+ expect(params.agent.send).toHaveBeenCalledWith(envelope);
430
+ expect(params.acceptSubmission).toHaveBeenCalledWith('user-original');
431
+ });
432
+
316
433
  it('keeps an existing session bound to its agent instead of the global draft selection', async () => {
317
434
  const send = createSendMock();
318
435
  const params = createControllerParams({ isRunning: false, send });
@@ -406,7 +523,7 @@ describe('useSessionConversationController backend run queue', () => {
406
523
 
407
524
  expect(params.agent.continueRun).toHaveBeenCalledWith({ sessionId: 'session-1' });
408
525
  expect(params.agent.send).not.toHaveBeenCalled();
409
- expect(params.resetComposer).not.toHaveBeenCalled();
526
+ expect(params.beginSubmission).not.toHaveBeenCalled();
410
527
  });
411
528
 
412
529
  it('keeps continuation available but restores send as primary when the user types', () => {
@@ -36,7 +36,7 @@ const THINKING_MODEL_OPTIONS: ChatModelOption[] = [
36
36
  describe('useSessionConversationInputState session preferences', () => {
37
37
  beforeEach(() => {
38
38
  window.localStorage.clear();
39
- useChatComposerDraftStore.setState({ drafts: {} });
39
+ useChatComposerDraftStore.setState({ drafts: {}, submissions: {}, recentSubmittedNodes: {} });
40
40
  useChatThreadStore.getState().setSnapshot({ draftProjectRoot: null });
41
41
  });
42
42
 
@@ -213,7 +213,17 @@ describe('useSessionConversationInputState session preferences', () => {
213
213
  selectedSkills: [],
214
214
  skillRecords: [],
215
215
  });
216
- result.current.inputActions.resetComposer();
216
+ result.current.inputActions.beginSubmission({
217
+ sessionId: 'session-sent',
218
+ message: {
219
+ id: 'user-sent', sessionId: 'session-sent', role: 'user', status: 'final',
220
+ timestamp: '2026-09-24T00:00:00.000Z',
221
+ parts: [{ type: 'text', text: '已经发送的消息' }],
222
+ },
223
+ }, {
224
+ text: '已经发送的消息', nodes: [], selectedSkills: [], skillRecords: [], attachments: [],
225
+ });
226
+ result.current.inputActions.acceptSubmission('user-sent');
217
227
  });
218
228
  rerender({ sessionKey: 'session-unsent' });
219
229
  act(() => {
@@ -5,12 +5,14 @@ import type {
5
5
  UiNcpSessionQueuedInputView,
6
6
  } from '@nextclaw/client-sdk';
7
7
  import type { NcpAgentSendEnvelope, NcpRunHandle } from '@nextclaw/ncp';
8
+ import { NcpHttpSendError } from '@nextclaw/ncp-http-agent-client';
8
9
 
9
10
  import {
10
11
  createChatComposerNodesFromDraft,
11
12
  deriveNcpMessagePartsFromComposer,
12
13
  } from '@/features/chat/features/input/utils/chat-composer-state.utils';
13
14
  import { isNcpChatSendDisabled } from '@/features/chat/features/input/utils/ncp-chat-input-availability.utils';
15
+ import { t } from '@/shared/lib/i18n';
14
16
  import { buildChatRunMetadata } from '@/features/chat/features/session/utils/chat-run-metadata.utils';
15
17
  import { createNcpSessionId } from '@/features/chat/features/session/utils/ncp-session-adapter.utils';
16
18
  import type { SessionQueuedInputAttachmentPreview } from '@/features/chat/features/conversation/utils/session-queued-input.utils';
@@ -25,6 +27,7 @@ import {
25
27
  useSessionConversationRecoveryActions,
26
28
  type SessionConversationRecoveryAgent,
27
29
  } from './use-session-conversation-recovery-actions';
30
+ import { useSessionSubmissionRecovery } from './use-session-submission-recovery';
28
31
 
29
32
  type SessionConversationInputQuery = ReturnType<typeof useSessionConversationInputQuery>;
30
33
  type ComposerDraftSnapshot = Pick<
@@ -80,8 +83,12 @@ type UseSessionConversationControllerParams = {
80
83
  readonly selectedAgentId: string;
81
84
  readonly sessionKey: string | null;
82
85
  readonly onSessionMaterialized?: (sessionKey: string) => void;
83
- readonly resetComposer: () => void;
84
86
  readonly restoreComposer: (snapshot: ComposerDraftSnapshot) => void;
87
+ readonly beginSubmission: (envelope: NcpAgentSendEnvelope, composer: ComposerDraftSnapshot) => void;
88
+ readonly acceptSubmission: (messageId: string) => void;
89
+ readonly failSubmission: (messageId: string, status: 'uncertain' | 'rejected', message: string) => void;
90
+ readonly restoreSubmission: (messageId: string) => void;
91
+ readonly discardSubmission: (messageId: string) => void;
85
92
  readonly setSendError: (message: string | null) => void;
86
93
  };
87
94
 
@@ -90,7 +97,6 @@ type BuildSubmissionDraftParams = {
90
97
  readonly composerSnapshot?: ComposerDraftSnapshot;
91
98
  readonly inputSnapshot: SessionConversationInputSnapshot;
92
99
  readonly inputQuery: SessionConversationInputQuery;
93
- readonly isRuntimeBlocked: boolean;
94
100
  readonly materializationContext?: SessionConversationMaterializationContext | null;
95
101
  readonly selectedAgentId: string;
96
102
  readonly sessionKey: string | null;
@@ -111,21 +117,12 @@ const resolveModelForSend = (value: string | null | undefined): string | undefin
111
117
  return trimmed || undefined;
112
118
  };
113
119
 
114
- function buildInputAvailabilitySnapshot(inputQuery: SessionConversationInputQuery) {
115
- return {
116
- isProviderStateResolved: inputQuery.isProviderStateResolved,
117
- modelOptions: inputQuery.modelOptions,
118
- sessionTypeUnavailable: inputQuery.sessionTypeState.sessionTypeUnavailable,
119
- };
120
- }
121
-
122
120
  function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDraft | null {
123
121
  const {
124
122
  agentIsSending,
125
123
  composerSnapshot: requestedComposerSnapshot,
126
124
  inputQuery,
127
125
  inputSnapshot,
128
- isRuntimeBlocked,
129
126
  materializationContext,
130
127
  selectedAgentId,
131
128
  sessionKey,
@@ -142,10 +139,9 @@ function buildSubmissionDraft(params: BuildSubmissionDraftParams): SubmissionDra
142
139
  composerSnapshot.attachments,
143
140
  );
144
141
  if (
142
+ inputSnapshot.pendingSubmission ||
145
143
  isNcpChatSendDisabled({
146
- snapshot: buildInputAvailabilitySnapshot(inputQuery),
147
144
  hasSendableDraft: hasSendableMessagePart(currentParts),
148
- isRuntimeBlocked,
149
145
  }) ||
150
146
  agentIsSending
151
147
  ) {
@@ -195,8 +191,12 @@ function buildSubmissionEnvelope(
195
191
  sessionKey: string | null,
196
192
  delivery: NcpAgentSendEnvelope['delivery'] = 'queue',
197
193
  ): NcpAgentSendEnvelope | null {
194
+ // getRandomValues is also available on HTTP origins, unlike randomUUID.
195
+ const messageIdBytes = crypto.getRandomValues(new Uint8Array(16));
196
+ const messageId = `user-${Array.from(messageIdBytes, (byte) => byte.toString(16).padStart(2, '0')).join('')}`;
198
197
  const envelope = buildNcpRequestEnvelope({
199
198
  sessionId: sessionKey ?? undefined,
199
+ messageId,
200
200
  text: draft.composerSnapshot.text.trim(),
201
201
  attachments: [...draft.composerSnapshot.attachments],
202
202
  parts: deriveNcpMessagePartsFromComposer(
@@ -205,7 +205,7 @@ function buildSubmissionEnvelope(
205
205
  ),
206
206
  metadata: draft.metadata,
207
207
  });
208
- return envelope ? { ...envelope, delivery } : null;
208
+ return envelope ? { ...envelope, delivery, idempotencyKey: envelope.message.id } : null;
209
209
  }
210
210
 
211
211
  function useSubmissionDraftRunner(params: {
@@ -213,8 +213,9 @@ function useSubmissionDraftRunner(params: {
213
213
  readonly clearSubmittingInput: (input: SubmittingQueuedInput) => void;
214
214
  readonly materializationContext?: SessionConversationMaterializationContext | null;
215
215
  readonly onSessionMaterialized?: (sessionKey: string) => void;
216
- readonly resetComposer: () => void;
217
- readonly restoreComposer: (snapshot: ComposerDraftSnapshot) => void;
216
+ readonly beginSubmission: (envelope: NcpAgentSendEnvelope, composer: ComposerDraftSnapshot) => void;
217
+ readonly acceptSubmission: (messageId: string) => void;
218
+ readonly failSubmission: (messageId: string, status: 'uncertain' | 'rejected', message: string) => void;
218
219
  readonly runQueue: SessionRunQueue;
219
220
  readonly sessionKey: string | null;
220
221
  readonly setSendError: (message: string | null) => void;
@@ -225,8 +226,9 @@ function useSubmissionDraftRunner(params: {
225
226
  clearSubmittingInput,
226
227
  materializationContext,
227
228
  onSessionMaterialized,
228
- resetComposer,
229
- restoreComposer,
229
+ beginSubmission,
230
+ acceptSubmission,
231
+ failSubmission,
230
232
  runQueue,
231
233
  sessionKey,
232
234
  setSendError,
@@ -244,10 +246,16 @@ function useSubmissionDraftRunner(params: {
244
246
  const queuedSubmission = agent.isRunning && delivery === 'queue'
245
247
  ? stageSubmittingInput(envelope)
246
248
  : null;
247
- if (composerBehavior === 'reset-and-restore') resetComposer();
249
+ if (composerBehavior === 'reset-and-restore') beginSubmission(envelope, draft.composerSnapshot);
248
250
  setSendError(null);
251
+ let definitelyRejected = false;
249
252
  try {
250
253
  const handle = await agent.send(envelope);
254
+ if (!handle) {
255
+ definitelyRejected = true;
256
+ throw new Error(t('chatSendFailed'));
257
+ }
258
+ if (composerBehavior === 'reset-and-restore') acceptSubmission(envelope.message.id);
251
259
  if (handle?.delivery === 'steered') {
252
260
  await runQueue.refreshPendingInputs().catch(() => undefined);
253
261
  } else if (handle?.delivery === 'queued') {
@@ -261,8 +269,12 @@ function useSubmissionDraftRunner(params: {
261
269
  }
262
270
  } catch (error) {
263
271
  if (queuedSubmission) clearSubmittingInput(queuedSubmission);
264
- if (composerBehavior === 'reset-and-restore') restoreComposer(draft.composerSnapshot);
265
- setSendError(error instanceof Error ? error.message : String(error));
272
+ const message = error instanceof Error ? error.message : String(error);
273
+ if (composerBehavior === 'reset-and-restore') {
274
+ failSubmission(envelope.message.id,
275
+ definitelyRejected || (error instanceof NcpHttpSendError && error.sendRejected) ? 'rejected' : 'uncertain',
276
+ message);
277
+ } else setSendError(message);
266
278
  throw error;
267
279
  }
268
280
  }, [
@@ -270,8 +282,9 @@ function useSubmissionDraftRunner(params: {
270
282
  clearSubmittingInput,
271
283
  materializationContext,
272
284
  onSessionMaterialized,
273
- resetComposer,
274
- restoreComposer,
285
+ beginSubmission,
286
+ acceptSubmission,
287
+ failSubmission,
275
288
  runQueue,
276
289
  sessionKey,
277
290
  setSendError,
@@ -283,7 +296,6 @@ function useSteeringSubmission(params: {
283
296
  readonly agent: SessionConversationAgent;
284
297
  readonly inputSnapshot: SessionConversationInputSnapshot;
285
298
  readonly inputQuery: SessionConversationInputQuery;
286
- readonly isRuntimeBlocked: boolean;
287
299
  readonly materializationContext?: SessionConversationMaterializationContext | null;
288
300
  readonly selectedAgentId: string;
289
301
  readonly sessionKey: string | null;
@@ -297,7 +309,6 @@ function useSteeringSubmission(params: {
297
309
  agent,
298
310
  inputQuery,
299
311
  inputSnapshot,
300
- isRuntimeBlocked,
301
312
  materializationContext,
302
313
  selectedAgentId,
303
314
  sessionKey,
@@ -308,7 +319,6 @@ function useSteeringSubmission(params: {
308
319
  agentIsSending: agent.isSending,
309
320
  inputSnapshot,
310
321
  inputQuery,
311
- isRuntimeBlocked,
312
322
  materializationContext,
313
323
  selectedAgentId,
314
324
  sessionKey,
@@ -318,7 +328,6 @@ function useSteeringSubmission(params: {
318
328
  agent.isSending,
319
329
  inputQuery,
320
330
  inputSnapshot,
321
- isRuntimeBlocked,
322
331
  materializationContext,
323
332
  selectedAgentId,
324
333
  sessionKey,
@@ -331,7 +340,6 @@ function usePrimarySubmission(params: {
331
340
  readonly continueRun: () => Promise<void>;
332
341
  readonly inputSnapshot: SessionConversationInputSnapshot;
333
342
  readonly inputQuery: SessionConversationInputQuery;
334
- readonly isRuntimeBlocked: boolean;
335
343
  readonly materializationContext?: SessionConversationMaterializationContext | null;
336
344
  readonly primaryAction: 'continue' | 'send';
337
345
  readonly selectedAgentId: string;
@@ -346,7 +354,6 @@ function usePrimarySubmission(params: {
346
354
  continueRun,
347
355
  inputQuery,
348
356
  inputSnapshot,
349
- isRuntimeBlocked,
350
357
  materializationContext,
351
358
  primaryAction,
352
359
  selectedAgentId,
@@ -359,7 +366,6 @@ function usePrimarySubmission(params: {
359
366
  agentIsSending: agent.isSending,
360
367
  inputSnapshot,
361
368
  inputQuery,
362
- isRuntimeBlocked,
363
369
  materializationContext,
364
370
  selectedAgentId,
365
371
  sessionKey,
@@ -370,7 +376,6 @@ function usePrimarySubmission(params: {
370
376
  continueRun,
371
377
  inputQuery,
372
378
  inputSnapshot,
373
- isRuntimeBlocked,
374
379
  materializationContext,
375
380
  primaryAction,
376
381
  selectedAgentId,
@@ -379,6 +384,32 @@ function usePrimarySubmission(params: {
379
384
  ]);
380
385
  }
381
386
 
387
+ function usePresetSubmission(params: {
388
+ readonly agent: SessionConversationAgent;
389
+ readonly inputSnapshot: SessionConversationInputSnapshot;
390
+ readonly inputQuery: SessionConversationInputQuery;
391
+ readonly materializationContext?: SessionConversationMaterializationContext | null;
392
+ readonly selectedAgentId: string;
393
+ readonly sessionKey: string | null;
394
+ readonly submitDraft: (draft: SubmissionDraft, behavior: SubmissionComposerBehavior) => Promise<void>;
395
+ }) {
396
+ const { agent, inputSnapshot, inputQuery, materializationContext, selectedAgentId, sessionKey, submitDraft } = params;
397
+ return useCallback(async (message: string) => {
398
+ const text = message.trim();
399
+ if (!text) return;
400
+ const draft = buildSubmissionDraft({
401
+ agentIsSending: agent.isSending,
402
+ composerSnapshot: {
403
+ text,
404
+ nodes: createChatComposerNodesFromDraft(text),
405
+ selectedSkills: [], skillRecords: [], attachments: [],
406
+ },
407
+ inputSnapshot, inputQuery, materializationContext, selectedAgentId, sessionKey,
408
+ });
409
+ if (draft) await submitDraft(draft, 'preserve');
410
+ }, [agent.isSending, inputQuery, inputSnapshot, materializationContext, selectedAgentId, sessionKey, submitDraft]);
411
+ }
412
+
382
413
  export function useSessionConversationController(params: UseSessionConversationControllerParams) {
383
414
  const {
384
415
  agent,
@@ -390,8 +421,12 @@ export function useSessionConversationController(params: UseSessionConversationC
390
421
  selectedAgentId,
391
422
  sessionKey,
392
423
  onSessionMaterialized,
393
- resetComposer,
394
424
  restoreComposer,
425
+ beginSubmission,
426
+ acceptSubmission,
427
+ failSubmission,
428
+ restoreSubmission,
429
+ discardSubmission,
395
430
  setSendError,
396
431
  } = params;
397
432
  const {
@@ -421,12 +456,10 @@ export function useSessionConversationController(params: UseSessionConversationC
421
456
  );
422
457
  const primaryAction: 'continue' | 'send' =
423
458
  canContinue && !hasSendableDraft ? 'continue' : 'send';
424
- const sendDisabled = primaryAction === 'continue'
459
+ const sendDisabled = inputSnapshot.pendingSubmission ? true : primaryAction === 'continue'
425
460
  ? false
426
461
  : isNcpChatSendDisabled({
427
- snapshot: buildInputAvailabilitySnapshot(inputQuery),
428
462
  hasSendableDraft,
429
- isRuntimeBlocked,
430
463
  }) || agent.isSending;
431
464
 
432
465
  const { continueRun, editMessage } = useSessionConversationRecoveryActions({
@@ -440,8 +473,9 @@ export function useSessionConversationController(params: UseSessionConversationC
440
473
  clearSubmittingInput,
441
474
  materializationContext,
442
475
  onSessionMaterialized,
443
- resetComposer,
444
- restoreComposer,
476
+ beginSubmission,
477
+ acceptSubmission,
478
+ failSubmission,
445
479
  runQueue,
446
480
  sessionKey,
447
481
  setSendError,
@@ -453,7 +487,6 @@ export function useSessionConversationController(params: UseSessionConversationC
453
487
  continueRun,
454
488
  inputQuery,
455
489
  inputSnapshot,
456
- isRuntimeBlocked,
457
490
  materializationContext,
458
491
  primaryAction,
459
492
  selectedAgentId,
@@ -465,52 +498,33 @@ export function useSessionConversationController(params: UseSessionConversationC
465
498
  agent,
466
499
  inputQuery,
467
500
  inputSnapshot,
468
- isRuntimeBlocked,
469
501
  materializationContext,
470
502
  selectedAgentId,
471
503
  sessionKey,
472
504
  submitDraft,
473
505
  });
474
506
 
475
- const sendPresetMessage = useCallback(async (message: string) => {
476
- const text = message.trim();
477
- if (!text) {
478
- return;
479
- }
480
- const draft = buildSubmissionDraft({
481
- agentIsSending: agent.isSending,
482
- composerSnapshot: {
483
- text,
484
- nodes: createChatComposerNodesFromDraft(text),
485
- selectedSkills: [],
486
- skillRecords: [],
487
- attachments: [],
488
- },
489
- inputSnapshot,
490
- inputQuery,
491
- isRuntimeBlocked,
492
- materializationContext,
493
- selectedAgentId,
494
- sessionKey,
495
- });
496
- if (draft) {
497
- await submitDraft(draft, 'preserve');
498
- }
499
- }, [
500
- agent.isSending,
501
- inputQuery,
502
- inputSnapshot,
503
- isRuntimeBlocked,
504
- materializationContext,
505
- selectedAgentId,
506
- sessionKey,
507
- submitDraft,
508
- ]);
507
+ const sendPresetMessage = usePresetSubmission({
508
+ agent, inputSnapshot, inputQuery, materializationContext,
509
+ selectedAgentId, sessionKey, submitDraft,
510
+ });
509
511
 
510
512
  const stop = useCallback(async () => {
511
513
  await agent.abort();
512
514
  }, [agent]);
513
515
 
516
+ const submissionRecovery = useSessionSubmissionRecovery({
517
+ agent,
518
+ submission: inputSnapshot.pendingSubmission,
519
+ sessionKey,
520
+ onSessionMaterialized,
521
+ acceptSubmission,
522
+ failSubmission,
523
+ restoreSubmission,
524
+ discardSubmission,
525
+ setSendError,
526
+ });
527
+
514
528
  return {
515
529
  canContinue,
516
530
  canStopGeneration: agent.isRunning,
@@ -525,6 +539,7 @@ export function useSessionConversationController(params: UseSessionConversationC
525
539
  send,
526
540
  sendSteering,
527
541
  sendPresetMessage,
542
+ ...submissionRecovery,
528
543
  sendDisabled,
529
544
  primaryAction,
530
545
  stop,
@@ -12,7 +12,7 @@ import {
12
12
  filterNcpChatModelOptionsBySessionType,
13
13
  } from '@/features/chat/features/ncp/utils/ncp-chat-query-derived.utils';
14
14
  import { adaptNcpSessionSummaries } from '@/features/chat/features/session/utils/ncp-session-adapter.utils';
15
- import { resolveRecentSessionPreferredValue } from '@/features/chat/features/session/utils/chat-session-preference-governance.utils';
15
+ import { resolveRecentSessionPreferenceSnapshot } from '@/features/chat/features/session/utils/chat-session-preference-governance.utils';
16
16
  import { useChatSessionTypeState } from '@/features/chat/features/session-type/hooks/use-chat-session-type-state';
17
17
  import { chatRecentModelsManager } from '@/features/chat/managers/chat-recent-models.manager';
18
18
  import { useChatQueryStore } from '@/features/chat/stores/ncp-chat-query.store';
@@ -112,27 +112,17 @@ export function useSessionConversationInputQuery(params: UseSessionConversationI
112
112
  const recentSessionTypeModel = chatRecentModelsManager
113
113
  .read({ namespace: sessionTypeState.selectedSessionType })
114
114
  .find((value) => availableModelValueSet.has(value));
115
- const fallbackPreferredModel = useMemo(
115
+ const recentSessionPreferences = useMemo(
116
116
  () =>
117
- recentSessionTypeModel ??
118
- resolveRecentSessionPreferredValue<string>({
117
+ resolveRecentSessionPreferenceSnapshot({
119
118
  sessions,
120
119
  selectedSessionKey,
121
120
  sessionType: sessionTypeState.selectedSessionType,
122
- readPreference: (session) => session.preferredModel?.trim() || undefined,
123
- }),
124
- [recentSessionTypeModel, selectedSessionKey, sessionTypeState.selectedSessionType, sessions],
125
- );
126
- const fallbackPreferredThinking = useMemo(
127
- () =>
128
- resolveRecentSessionPreferredValue({
129
- sessions,
130
- selectedSessionKey,
131
- sessionType: sessionTypeState.selectedSessionType,
132
- readPreference: (session) => session.preferredThinking ?? undefined,
133
121
  }),
134
122
  [selectedSessionKey, sessionTypeState.selectedSessionType, sessions],
135
123
  );
124
+ const fallbackPreferredModel = recentSessionPreferences?.model ?? recentSessionTypeModel;
125
+ const fallbackPreferredThinking = recentSessionPreferences?.thinking;
136
126
 
137
127
  const defaultProjectRoot = normalizeSessionProjectRootValue(
138
128
  config?.agents.defaults.workspace,