@nextclaw/ui 0.27.0 → 0.27.1

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 +29 -0
  2. package/dist/.vite/manifest.json +1242 -1171
  3. package/dist/assets/{abnfDiagram-VRR7QNED-DmeCRAkd.js → abnfDiagram-VRR7QNED-BT0_ycaq.js} +1 -1
  4. package/dist/assets/account-panel-BVNV8XqM.js +1 -0
  5. package/dist/assets/agents-CfpN8PQc.js +1 -0
  6. package/dist/assets/agents-E4nu-17c.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-2DwHRRle.js +28 -0
  10. package/dist/assets/appearance-settings-page-Cn9w3F-K.js +1 -0
  11. package/dist/assets/apps-YFlJirpx.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-CH5c55CR.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-ClDUWo9V.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-BjBZIbgx.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-BqfbbKir.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-CWYF9QU_.js +2 -0
  111. package/dist/assets/main-D_zmSVS1.css +1 -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-CnQ5USfr.js +1 -0
  115. package/dist/assets/mcp-marketplace-page-Dor6H9IC.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-C-izvtu9.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-CzBaTOTL.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-DvCdHjzA.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-BF1wxvzR.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-D3wwPRzV.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 +86 -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 +85 -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,8 +132,12 @@ 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
  };
@@ -162,6 +172,7 @@ describe('useSessionConversationController backend run queue', () => {
162
172
  const envelope = send.mock.calls[0]?.[0];
163
173
  expect(envelope?.sessionId).toMatch(/^ncp-/);
164
174
  expect(envelope?.message.sessionId).toBe(envelope?.sessionId);
175
+ expect(envelope?.idempotencyKey).toBe(envelope?.message.id);
165
176
  expect(params.onSessionMaterialized).toHaveBeenCalledWith(envelope?.sessionId);
166
177
  });
167
178
 
@@ -182,7 +193,21 @@ describe('useSessionConversationController backend run queue', () => {
182
193
  parts: [{ type: 'text', text: 'next task' }],
183
194
  }),
184
195
  });
185
- expect(params.resetComposer).toHaveBeenCalledTimes(1);
196
+ expect(params.beginSubmission).toHaveBeenCalledTimes(1);
197
+ });
198
+
199
+ it('attempts a send when a nonempty draft has a stale blocked runtime status', async () => {
200
+ const send = createSendMock();
201
+ const params = { ...createControllerParams({ isRunning: false, send }), isRuntimeBlocked: true };
202
+ const { result } = renderHook(() => useSessionConversationController(params));
203
+
204
+ expect(result.current.sendDisabled).toBe(false);
205
+ await act(async () => {
206
+ await result.current.send();
207
+ });
208
+
209
+ expect(send).toHaveBeenCalledTimes(1);
210
+ expect(params.setSendError).toHaveBeenLastCalledWith(null);
186
211
  });
187
212
 
188
213
  it('uses prefer-steer for command/control-enter submission without changing the send button path', async () => {
@@ -241,7 +266,7 @@ describe('useSessionConversationController backend run queue', () => {
241
266
  }),
242
267
  });
243
268
  expect(envelope?.metadata).not.toHaveProperty('ui_inline_tokens');
244
- expect(params.resetComposer).not.toHaveBeenCalled();
269
+ expect(params.beginSubmission).not.toHaveBeenCalled();
245
270
  expect(params.restoreComposer).not.toHaveBeenCalled();
246
271
  });
247
272
 
@@ -257,7 +282,7 @@ describe('useSessionConversationController backend run queue', () => {
257
282
  await expect(result.current.sendPresetMessage('Update title')).rejects.toThrow(sendError);
258
283
  });
259
284
 
260
- expect(params.resetComposer).not.toHaveBeenCalled();
285
+ expect(params.beginSubmission).not.toHaveBeenCalled();
261
286
  expect(params.restoreComposer).not.toHaveBeenCalled();
262
287
  expect(params.setSendError).toHaveBeenLastCalledWith(sendError.message);
263
288
  });
@@ -275,7 +300,7 @@ describe('useSessionConversationController backend run queue', () => {
275
300
  submission = result.current.send();
276
301
  });
277
302
 
278
- expect(params.resetComposer).toHaveBeenCalledTimes(1);
303
+ expect(params.beginSubmission).toHaveBeenCalledTimes(1);
279
304
  expect(result.current.queuedInputs).toEqual([
280
305
  expect.objectContaining({
281
306
  isSubmitting: true,
@@ -306,13 +331,64 @@ describe('useSessionConversationController backend run queue', () => {
306
331
  });
307
332
 
308
333
  expect(result.current.queuedInputs).toEqual([]);
309
- expect(params.restoreComposer).toHaveBeenCalledWith(expect.objectContaining({
310
- text: 'next task',
311
- }));
312
- expect(params.setSendError).toHaveBeenLastCalledWith(sendError.message);
334
+ expect(params.failSubmission).toHaveBeenCalledWith(expect.any(String), 'uncertain', sendError.message);
313
335
  expect(params.runQueue.refreshQueuedInputs).not.toHaveBeenCalled();
314
336
  });
315
337
 
338
+ it('shows a send failure and restores the draft when the client returns no acceptance handle', async () => {
339
+ const send = createSendMock(null);
340
+ const params = createControllerParams({ isRunning: false, send });
341
+ const { result } = renderHook(() => useSessionConversationController(params));
342
+
343
+ await act(async () => {
344
+ await expect(result.current.send()).rejects.toThrow('Failed to send message');
345
+ });
346
+
347
+ expect(params.failSubmission).toHaveBeenCalledWith(expect.any(String), 'rejected', 'Failed to send message');
348
+ expect(params.onSessionMaterialized).not.toHaveBeenCalled();
349
+ });
350
+
351
+ it('classifies an explicit HTTP rejection so the draft can be restored', async () => {
352
+ const error = new NcpHttpSendError('invalid request', true);
353
+ const send = vi.fn<TestAgentSend>(async () => { throw error; });
354
+ const params = createControllerParams({ isRunning: false, send });
355
+ const { result } = renderHook(() => useSessionConversationController(params));
356
+ await act(async () => {
357
+ await expect(result.current.send()).rejects.toThrow(error);
358
+ });
359
+ expect(params.failSubmission).toHaveBeenCalledWith(expect.any(String), 'rejected', 'invalid request');
360
+ });
361
+
362
+ it('rechecks the original message before retrying with the same identity', async () => {
363
+ fetchNcpSessionMessages.mockResolvedValue({ messages: [] });
364
+ const params = createControllerParams({ isRunning: false });
365
+ const envelope: NcpAgentSendEnvelope = {
366
+ sessionId: 'session-1', idempotencyKey: 'user-original',
367
+ message: {
368
+ id: 'user-original', sessionId: 'session-1', role: 'user', status: 'final',
369
+ timestamp: '2026-09-24T00:00:00.000Z', parts: [{ type: 'text', text: 'next task' }],
370
+ },
371
+ };
372
+ params.inputSnapshot.pendingSubmission = {
373
+ envelope,
374
+ composer: {
375
+ text: 'next task', nodes: [createTextNode('next task')],
376
+ selectedSkills: [], skillRecords: [], attachments: [],
377
+ },
378
+ status: 'uncertain',
379
+ };
380
+ const { result } = renderHook(() => useSessionConversationController(params));
381
+ expect(result.current.sendDisabled).toBe(true);
382
+ await act(async () => {
383
+ await result.current.send();
384
+ await result.current.retryPendingSubmission();
385
+ });
386
+ expect(fetchNcpSessionMessages).toHaveBeenCalledWith('session-1', { limit: 100 });
387
+ expect(params.agent.send).toHaveBeenCalledTimes(1);
388
+ expect(params.agent.send).toHaveBeenCalledWith(envelope);
389
+ expect(params.acceptSubmission).toHaveBeenCalledWith('user-original');
390
+ });
391
+
316
392
  it('keeps an existing session bound to its agent instead of the global draft selection', async () => {
317
393
  const send = createSendMock();
318
394
  const params = createControllerParams({ isRunning: false, send });
@@ -406,7 +482,7 @@ describe('useSessionConversationController backend run queue', () => {
406
482
 
407
483
  expect(params.agent.continueRun).toHaveBeenCalledWith({ sessionId: 'session-1' });
408
484
  expect(params.agent.send).not.toHaveBeenCalled();
409
- expect(params.resetComposer).not.toHaveBeenCalled();
485
+ expect(params.beginSubmission).not.toHaveBeenCalled();
410
486
  });
411
487
 
412
488
  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
  ) {
@@ -197,6 +193,7 @@ function buildSubmissionEnvelope(
197
193
  ): NcpAgentSendEnvelope | null {
198
194
  const envelope = buildNcpRequestEnvelope({
199
195
  sessionId: sessionKey ?? undefined,
196
+ messageId: `user-${crypto.randomUUID()}`,
200
197
  text: draft.composerSnapshot.text.trim(),
201
198
  attachments: [...draft.composerSnapshot.attachments],
202
199
  parts: deriveNcpMessagePartsFromComposer(
@@ -205,7 +202,7 @@ function buildSubmissionEnvelope(
205
202
  ),
206
203
  metadata: draft.metadata,
207
204
  });
208
- return envelope ? { ...envelope, delivery } : null;
205
+ return envelope ? { ...envelope, delivery, idempotencyKey: envelope.message.id } : null;
209
206
  }
210
207
 
211
208
  function useSubmissionDraftRunner(params: {
@@ -213,8 +210,9 @@ function useSubmissionDraftRunner(params: {
213
210
  readonly clearSubmittingInput: (input: SubmittingQueuedInput) => void;
214
211
  readonly materializationContext?: SessionConversationMaterializationContext | null;
215
212
  readonly onSessionMaterialized?: (sessionKey: string) => void;
216
- readonly resetComposer: () => void;
217
- readonly restoreComposer: (snapshot: ComposerDraftSnapshot) => void;
213
+ readonly beginSubmission: (envelope: NcpAgentSendEnvelope, composer: ComposerDraftSnapshot) => void;
214
+ readonly acceptSubmission: (messageId: string) => void;
215
+ readonly failSubmission: (messageId: string, status: 'uncertain' | 'rejected', message: string) => void;
218
216
  readonly runQueue: SessionRunQueue;
219
217
  readonly sessionKey: string | null;
220
218
  readonly setSendError: (message: string | null) => void;
@@ -225,8 +223,9 @@ function useSubmissionDraftRunner(params: {
225
223
  clearSubmittingInput,
226
224
  materializationContext,
227
225
  onSessionMaterialized,
228
- resetComposer,
229
- restoreComposer,
226
+ beginSubmission,
227
+ acceptSubmission,
228
+ failSubmission,
230
229
  runQueue,
231
230
  sessionKey,
232
231
  setSendError,
@@ -244,10 +243,16 @@ function useSubmissionDraftRunner(params: {
244
243
  const queuedSubmission = agent.isRunning && delivery === 'queue'
245
244
  ? stageSubmittingInput(envelope)
246
245
  : null;
247
- if (composerBehavior === 'reset-and-restore') resetComposer();
246
+ if (composerBehavior === 'reset-and-restore') beginSubmission(envelope, draft.composerSnapshot);
248
247
  setSendError(null);
248
+ let definitelyRejected = false;
249
249
  try {
250
250
  const handle = await agent.send(envelope);
251
+ if (!handle) {
252
+ definitelyRejected = true;
253
+ throw new Error(t('chatSendFailed'));
254
+ }
255
+ if (composerBehavior === 'reset-and-restore') acceptSubmission(envelope.message.id);
251
256
  if (handle?.delivery === 'steered') {
252
257
  await runQueue.refreshPendingInputs().catch(() => undefined);
253
258
  } else if (handle?.delivery === 'queued') {
@@ -261,8 +266,12 @@ function useSubmissionDraftRunner(params: {
261
266
  }
262
267
  } catch (error) {
263
268
  if (queuedSubmission) clearSubmittingInput(queuedSubmission);
264
- if (composerBehavior === 'reset-and-restore') restoreComposer(draft.composerSnapshot);
265
- setSendError(error instanceof Error ? error.message : String(error));
269
+ const message = error instanceof Error ? error.message : String(error);
270
+ if (composerBehavior === 'reset-and-restore') {
271
+ failSubmission(envelope.message.id,
272
+ definitelyRejected || (error instanceof NcpHttpSendError && error.sendRejected) ? 'rejected' : 'uncertain',
273
+ message);
274
+ } else setSendError(message);
266
275
  throw error;
267
276
  }
268
277
  }, [
@@ -270,8 +279,9 @@ function useSubmissionDraftRunner(params: {
270
279
  clearSubmittingInput,
271
280
  materializationContext,
272
281
  onSessionMaterialized,
273
- resetComposer,
274
- restoreComposer,
282
+ beginSubmission,
283
+ acceptSubmission,
284
+ failSubmission,
275
285
  runQueue,
276
286
  sessionKey,
277
287
  setSendError,
@@ -283,7 +293,6 @@ function useSteeringSubmission(params: {
283
293
  readonly agent: SessionConversationAgent;
284
294
  readonly inputSnapshot: SessionConversationInputSnapshot;
285
295
  readonly inputQuery: SessionConversationInputQuery;
286
- readonly isRuntimeBlocked: boolean;
287
296
  readonly materializationContext?: SessionConversationMaterializationContext | null;
288
297
  readonly selectedAgentId: string;
289
298
  readonly sessionKey: string | null;
@@ -297,7 +306,6 @@ function useSteeringSubmission(params: {
297
306
  agent,
298
307
  inputQuery,
299
308
  inputSnapshot,
300
- isRuntimeBlocked,
301
309
  materializationContext,
302
310
  selectedAgentId,
303
311
  sessionKey,
@@ -308,7 +316,6 @@ function useSteeringSubmission(params: {
308
316
  agentIsSending: agent.isSending,
309
317
  inputSnapshot,
310
318
  inputQuery,
311
- isRuntimeBlocked,
312
319
  materializationContext,
313
320
  selectedAgentId,
314
321
  sessionKey,
@@ -318,7 +325,6 @@ function useSteeringSubmission(params: {
318
325
  agent.isSending,
319
326
  inputQuery,
320
327
  inputSnapshot,
321
- isRuntimeBlocked,
322
328
  materializationContext,
323
329
  selectedAgentId,
324
330
  sessionKey,
@@ -331,7 +337,6 @@ function usePrimarySubmission(params: {
331
337
  readonly continueRun: () => Promise<void>;
332
338
  readonly inputSnapshot: SessionConversationInputSnapshot;
333
339
  readonly inputQuery: SessionConversationInputQuery;
334
- readonly isRuntimeBlocked: boolean;
335
340
  readonly materializationContext?: SessionConversationMaterializationContext | null;
336
341
  readonly primaryAction: 'continue' | 'send';
337
342
  readonly selectedAgentId: string;
@@ -346,7 +351,6 @@ function usePrimarySubmission(params: {
346
351
  continueRun,
347
352
  inputQuery,
348
353
  inputSnapshot,
349
- isRuntimeBlocked,
350
354
  materializationContext,
351
355
  primaryAction,
352
356
  selectedAgentId,
@@ -359,7 +363,6 @@ function usePrimarySubmission(params: {
359
363
  agentIsSending: agent.isSending,
360
364
  inputSnapshot,
361
365
  inputQuery,
362
- isRuntimeBlocked,
363
366
  materializationContext,
364
367
  selectedAgentId,
365
368
  sessionKey,
@@ -370,7 +373,6 @@ function usePrimarySubmission(params: {
370
373
  continueRun,
371
374
  inputQuery,
372
375
  inputSnapshot,
373
- isRuntimeBlocked,
374
376
  materializationContext,
375
377
  primaryAction,
376
378
  selectedAgentId,
@@ -379,6 +381,32 @@ function usePrimarySubmission(params: {
379
381
  ]);
380
382
  }
381
383
 
384
+ function usePresetSubmission(params: {
385
+ readonly agent: SessionConversationAgent;
386
+ readonly inputSnapshot: SessionConversationInputSnapshot;
387
+ readonly inputQuery: SessionConversationInputQuery;
388
+ readonly materializationContext?: SessionConversationMaterializationContext | null;
389
+ readonly selectedAgentId: string;
390
+ readonly sessionKey: string | null;
391
+ readonly submitDraft: (draft: SubmissionDraft, behavior: SubmissionComposerBehavior) => Promise<void>;
392
+ }) {
393
+ const { agent, inputSnapshot, inputQuery, materializationContext, selectedAgentId, sessionKey, submitDraft } = params;
394
+ return useCallback(async (message: string) => {
395
+ const text = message.trim();
396
+ if (!text) return;
397
+ const draft = buildSubmissionDraft({
398
+ agentIsSending: agent.isSending,
399
+ composerSnapshot: {
400
+ text,
401
+ nodes: createChatComposerNodesFromDraft(text),
402
+ selectedSkills: [], skillRecords: [], attachments: [],
403
+ },
404
+ inputSnapshot, inputQuery, materializationContext, selectedAgentId, sessionKey,
405
+ });
406
+ if (draft) await submitDraft(draft, 'preserve');
407
+ }, [agent.isSending, inputQuery, inputSnapshot, materializationContext, selectedAgentId, sessionKey, submitDraft]);
408
+ }
409
+
382
410
  export function useSessionConversationController(params: UseSessionConversationControllerParams) {
383
411
  const {
384
412
  agent,
@@ -390,8 +418,12 @@ export function useSessionConversationController(params: UseSessionConversationC
390
418
  selectedAgentId,
391
419
  sessionKey,
392
420
  onSessionMaterialized,
393
- resetComposer,
394
421
  restoreComposer,
422
+ beginSubmission,
423
+ acceptSubmission,
424
+ failSubmission,
425
+ restoreSubmission,
426
+ discardSubmission,
395
427
  setSendError,
396
428
  } = params;
397
429
  const {
@@ -421,12 +453,10 @@ export function useSessionConversationController(params: UseSessionConversationC
421
453
  );
422
454
  const primaryAction: 'continue' | 'send' =
423
455
  canContinue && !hasSendableDraft ? 'continue' : 'send';
424
- const sendDisabled = primaryAction === 'continue'
456
+ const sendDisabled = inputSnapshot.pendingSubmission ? true : primaryAction === 'continue'
425
457
  ? false
426
458
  : isNcpChatSendDisabled({
427
- snapshot: buildInputAvailabilitySnapshot(inputQuery),
428
459
  hasSendableDraft,
429
- isRuntimeBlocked,
430
460
  }) || agent.isSending;
431
461
 
432
462
  const { continueRun, editMessage } = useSessionConversationRecoveryActions({
@@ -440,8 +470,9 @@ export function useSessionConversationController(params: UseSessionConversationC
440
470
  clearSubmittingInput,
441
471
  materializationContext,
442
472
  onSessionMaterialized,
443
- resetComposer,
444
- restoreComposer,
473
+ beginSubmission,
474
+ acceptSubmission,
475
+ failSubmission,
445
476
  runQueue,
446
477
  sessionKey,
447
478
  setSendError,
@@ -453,7 +484,6 @@ export function useSessionConversationController(params: UseSessionConversationC
453
484
  continueRun,
454
485
  inputQuery,
455
486
  inputSnapshot,
456
- isRuntimeBlocked,
457
487
  materializationContext,
458
488
  primaryAction,
459
489
  selectedAgentId,
@@ -465,52 +495,33 @@ export function useSessionConversationController(params: UseSessionConversationC
465
495
  agent,
466
496
  inputQuery,
467
497
  inputSnapshot,
468
- isRuntimeBlocked,
469
498
  materializationContext,
470
499
  selectedAgentId,
471
500
  sessionKey,
472
501
  submitDraft,
473
502
  });
474
503
 
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
- ]);
504
+ const sendPresetMessage = usePresetSubmission({
505
+ agent, inputSnapshot, inputQuery, materializationContext,
506
+ selectedAgentId, sessionKey, submitDraft,
507
+ });
509
508
 
510
509
  const stop = useCallback(async () => {
511
510
  await agent.abort();
512
511
  }, [agent]);
513
512
 
513
+ const submissionRecovery = useSessionSubmissionRecovery({
514
+ agent,
515
+ submission: inputSnapshot.pendingSubmission,
516
+ sessionKey,
517
+ onSessionMaterialized,
518
+ acceptSubmission,
519
+ failSubmission,
520
+ restoreSubmission,
521
+ discardSubmission,
522
+ setSendError,
523
+ });
524
+
514
525
  return {
515
526
  canContinue,
516
527
  canStopGeneration: agent.isRunning,
@@ -525,6 +536,7 @@ export function useSessionConversationController(params: UseSessionConversationC
525
536
  send,
526
537
  sendSteering,
527
538
  sendPresetMessage,
539
+ ...submissionRecovery,
528
540
  sendDisabled,
529
541
  primaryAction,
530
542
  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,