@nextclaw/ui 0.15.4 → 0.15.6

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 (300) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/abnfDiagram-VRR7QNED-BYpK0F5F.js +1 -0
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Bd99vut2.js → aiden0z-pptx-renderer.es-D87ywFdC.js} +1 -1
  4. package/dist/assets/api-Dyb5rgh0.js +13 -0
  5. package/dist/assets/app-presenter-provider-7jV0nPiL.js +106 -0
  6. package/dist/assets/{appearance-settings-page-fLIJbx1C.js → appearance-settings-page-DFm3BbEb.js} +1 -1
  7. package/dist/assets/arc-DFELHWM2.js +1 -0
  8. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +1 -0
  9. package/dist/assets/architectureDiagram-ZJ3FMSHR-CvRhdovL.js +36 -0
  10. package/dist/assets/array-BqYdCIE6.js +1 -0
  11. package/dist/assets/blockDiagram-677ZJIJ3-DVYNkbR_.js +132 -0
  12. package/dist/assets/{book-open-DWUjS6aW.js → book-open-L6LCuqOr.js} +1 -1
  13. package/dist/assets/c4Diagram-LMCZKHZV-B1w-iwRe.js +10 -0
  14. package/dist/assets/channel-BEHOSFc5.js +1 -0
  15. package/dist/assets/{channels-list-page-CvCTJraa.js → channels-list-page-DOaPKNJA.js} +2 -2
  16. package/dist/assets/{chat-page-B42UUIFR.js → chat-page-Cw9uDhD_.js} +1 -1
  17. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +1 -0
  18. package/dist/assets/chunk-32BRIVSS-kG3lg0bC.js +1 -0
  19. package/dist/assets/chunk-52WLFC77-BLeB7iGk.js +10 -0
  20. package/dist/assets/chunk-5FCAYU7R-BQga1Rr7.js +1 -0
  21. package/dist/assets/chunk-5HE753X5-DU81hkcF.js +1 -0
  22. package/dist/assets/chunk-5JV3BV7I-C2bAx0m7.js +1 -0
  23. package/dist/assets/chunk-5TONJI2A-BExIIgWY.js +2 -0
  24. package/dist/assets/chunk-5VM5RSS4-C3XB05yh.js +15 -0
  25. package/dist/assets/chunk-7BUUIJ7U-JiyRtrGu.js +1 -0
  26. package/dist/assets/chunk-BIQX33UG-BZRV20pI.js +1 -0
  27. package/dist/assets/chunk-C7G6YPKG-AsyDSQ9j.js +1 -0
  28. package/dist/assets/chunk-CQNSW5MT-iN-fiXVA.js +1 -0
  29. package/dist/assets/chunk-CYSBUYHQ-DhC_TRzI.js +1 -0
  30. package/dist/assets/chunk-EMLP6XTP-QFLK8R6d.js +1 -0
  31. package/dist/assets/chunk-EX3LRPZG-DzUwhkCz.js +231 -0
  32. package/dist/assets/chunk-FWX5IMBZ-DFelpZBy.js +2 -0
  33. package/dist/assets/chunk-HOUHSVGY-BMPwFEwN.js +1 -0
  34. package/dist/assets/chunk-ICXQ74PX-DH8NJMok.js +2 -0
  35. package/dist/assets/chunk-JG7HCLWE-D-95AsO4.js +2 -0
  36. package/dist/assets/chunk-JWPE2WC7-DIaTv8kx.js +1 -0
  37. package/dist/assets/chunk-KEIR6QF5-A3jEqwzn.js +161 -0
  38. package/dist/assets/chunk-MOJQB5TN-BPFLzWgj.js +88 -0
  39. package/dist/assets/chunk-MOZMSUNE-DR_fzxG0.js +1 -0
  40. package/dist/assets/chunk-OGEWGWER-TIcEoPPH.js +1 -0
  41. package/dist/assets/chunk-OSBZ3O6U-BALNvSfG.js +1 -0
  42. package/dist/assets/chunk-PUDLZKDR-I-EXhf_k.js +156 -0
  43. package/dist/assets/chunk-Q4XR5HBZ-uvIGstBS.js +70 -0
  44. package/dist/assets/chunk-QBLGF6JB-r-TjLdxa.js +1 -0
  45. package/dist/assets/chunk-R7FJI6CG-N-1LDgvy.js +1 -0
  46. package/dist/assets/chunk-RYQCIY6F-jxNASx65.js +1 -0
  47. package/dist/assets/chunk-U6XO7XAA-Cphbuc7q.js +2 -0
  48. package/dist/assets/chunk-V7JOEXUC-BLEuBf1s.js +206 -0
  49. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +1 -0
  50. package/dist/assets/chunk-VR4S4FIN-BAUbR0ke.js +1 -0
  51. package/dist/assets/chunk-WYO6CB5R-Do6SJrnY.js +127 -0
  52. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +1 -0
  53. package/dist/assets/chunk-Y2CYZVJY-BGrd8tLy.js +1 -0
  54. package/dist/assets/chunk-YOTPTUD7-CQqvMTCr.js +1 -0
  55. package/dist/assets/chunk-ZGVPDNZ5-B7kWWaXv.js +62 -0
  56. package/dist/assets/chunk-ZIRB5QZD-DTU0cDNh.js +32 -0
  57. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +1 -0
  58. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +1 -0
  59. package/dist/assets/{config-split-page-DBgt9S8b.js → config-split-page-CqlqjnCK.js} +1 -1
  60. package/dist/assets/{confirm-dialog-GSXIvoRU.js → confirm-dialog-bAp8NU93.js} +1 -1
  61. package/dist/assets/cose-bilkent-JH36ORCC-B9LrtV-q.js +1 -0
  62. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +1 -0
  63. package/dist/assets/cynefinDiagram-TSTJHNR4-BfjvW4su.js +62 -0
  64. package/dist/assets/cytoscape.esm-CRW8q92n.js +321 -0
  65. package/dist/assets/dagre-DkiXZKkL.js +1 -0
  66. package/dist/assets/dagre-VKFMJZFB-B1pQKQSY.js +4 -0
  67. package/dist/assets/defaultLocale-Cdv9x6q7.js +1 -0
  68. package/dist/assets/desktop-update-config-2SjMfCMl.js +1 -0
  69. package/dist/assets/diagram-FQU43EPY-cB4EZdaP.js +3 -0
  70. package/dist/assets/diagram-G47NLZAW-CtW8im1B.js +24 -0
  71. package/dist/assets/diagram-NH7WQ7WH-BDCtrCCm.js +24 -0
  72. package/dist/assets/diagram-OA4YK3LP-DEMJ42Gt.js +30 -0
  73. package/dist/assets/diagram-WEI45ONY-CdCcxSYU.js +41 -0
  74. package/dist/assets/{dist-DC7f4TJH.js → dist-CrtbGLlh.js} +1 -1
  75. package/dist/assets/dist-dw-gAfM_.js +1 -0
  76. package/dist/assets/doc-browser-A5IRyDXQ2.js +1 -0
  77. package/dist/assets/{doc-browser-D0monHjG.js → doc-browser-BmnQa30-.js} +1 -1
  78. package/dist/assets/{doc-browser-context-CGwEOhBy.js → doc-browser-context-BlpUUFrR.js} +1 -1
  79. package/dist/assets/{docx-preview-eFH9DRsS.js → docx-preview-zis3U0Fa.js} +1 -1
  80. package/dist/assets/{download-DiP9zx-J.js → download-BhZZNXD8.js} +1 -1
  81. package/dist/assets/ebnfDiagram-CCIWWBDH-DBZETBwE.js +1 -0
  82. package/dist/assets/erDiagram-Q63AITRT-y2zDXprP.js +85 -0
  83. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +1 -0
  84. package/dist/assets/{external-link-C6ZeSPef.js → external-link-B9Uoq5J7.js} +1 -1
  85. package/dist/assets/{eye-suvKmtwn.js → eye-K5knauEM.js} +1 -1
  86. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +1 -0
  87. package/dist/assets/{folder-CMELbQGG.js → folder-BIYZZWJv.js} +1 -1
  88. package/dist/assets/{form-actions-CUAzpNe_.js → form-actions-CKprWFeM.js} +1 -1
  89. package/dist/assets/ganttDiagram-NO4QXBWP-D2DP344e.js +292 -0
  90. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +1 -0
  91. package/dist/assets/gitGraphDiagram-IHSO6WYX-D37uiwA2.js +106 -0
  92. package/dist/assets/graphlib-BcNFQr5y.js +1 -0
  93. package/dist/assets/index-BVn702m4.css +1 -0
  94. package/dist/assets/index-CFxJ1Fkg.js +2 -0
  95. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +1 -0
  96. package/dist/assets/infoDiagram-FWYZ7A6U-CG-WAIRi.js +2 -0
  97. package/dist/assets/init-D8TjIqfm.js +1 -0
  98. package/dist/assets/ishikawaDiagram-FXEZZL3T-WeCTnW0f.js +70 -0
  99. package/dist/assets/journeyDiagram-5HDEW3XC-DUFBW9Td.js +139 -0
  100. package/dist/assets/kanban-definition-HUTT4EX6-B0lxgMpg.js +89 -0
  101. package/dist/assets/katex-wEmrEFTg.js +257 -0
  102. package/dist/assets/line-B78frP_c.js +1 -0
  103. package/dist/assets/linear-C6lVEcb2.js +1 -0
  104. package/dist/assets/loader-circle-Bj2RMrCR.js +1 -0
  105. package/dist/assets/{logo-badge-Cu5UGH_4.js → logo-badge-BQ-XFZod.js} +1 -1
  106. package/dist/assets/map-Bdri5UL5.js +1 -0
  107. package/dist/assets/mcp-marketplace-page-BfA7A3n4.js +9 -0
  108. package/dist/assets/mcp-marketplace-page-CXHYO95j.js +1 -0
  109. package/dist/assets/mermaid-parser.core-Cue18vLo.js +4 -0
  110. package/dist/assets/mermaid.core-CXwWRh5i.js +11 -0
  111. package/dist/assets/mindmap-definition-LN4V7U3C-CFxz0P0-.js +96 -0
  112. package/dist/assets/{model-config-page-CWP6pyBm.js → model-config-page-BbWbW_qM.js} +1 -1
  113. package/dist/assets/ordinal-Cbd8t4LB.js +1 -0
  114. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +1 -0
  115. package/dist/assets/path-DzBnMI1r.js +1 -0
  116. package/dist/assets/pegDiagram-2B236MQR-B_Qt9zyT.js +1 -0
  117. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +1 -0
  118. package/dist/assets/pieDiagram-ENE6RG2P-CRQdSuwS.js +39 -0
  119. package/dist/assets/plus-TmFdwO3P.js +1 -0
  120. package/dist/assets/{popover-DImguRok.js → popover-CX9zFsuc.js} +1 -1
  121. package/dist/assets/preload-helper-D4M6sveU.js +1 -0
  122. package/dist/assets/{provider-scoped-model-input-CE7p4SbJ.js → provider-scoped-model-input-B-9_DtIP.js} +1 -1
  123. package/dist/assets/providers-config-page-xwIjGur2.js +1 -0
  124. package/dist/assets/quadrantDiagram-ABIIQ3AL-D5kPpttq.js +7 -0
  125. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +1 -0
  126. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +1 -0
  127. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +1 -0
  128. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +1 -0
  129. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +1 -0
  130. package/dist/assets/railroadDiagram-RFXS5EU6-DGh_TGlQ.js +1 -0
  131. package/dist/assets/{react-5sjAT_6d.js → react-DsgudwpT.js} +1 -1
  132. package/dist/assets/{refresh-cw-CHLBzWWj.js → refresh-cw-kXdvbhFf.js} +1 -1
  133. package/dist/assets/remote-B9F2uCk9.js +1 -0
  134. package/dist/assets/requirementDiagram-TGXJPOKE-EmFph0H_.js +84 -0
  135. package/dist/assets/rotate-cw-DEc6aihr.js +1 -0
  136. package/dist/assets/rough.esm-wJ1d3nQl.js +1 -0
  137. package/dist/assets/runtime-config-page-DTsi7czJ.js +1 -0
  138. package/dist/assets/sankeyDiagram-HTMAVEWB-BXWB5Ugi.js +40 -0
  139. package/dist/assets/{save-BABzyrjB.js → save-C9qpz2_2.js} +1 -1
  140. package/dist/assets/{search-BTqxG3ZG.js → search-_kEi29qo.js} +1 -1
  141. package/dist/assets/{search-config-page-BkKKmxmS.js → search-config-page-H7ug8f4D.js} +1 -1
  142. package/dist/assets/{secrets-config-page-CWJMyu-k.js → secrets-config-page-C1_6rINu.js} +2 -2
  143. package/dist/assets/security-config-Cr2ERKLu.js +1 -0
  144. package/dist/assets/{select-TYZDBNmq.js → select-MJKhKAWR.js} +1 -1
  145. package/dist/assets/sequenceDiagram-DBY2YBRQ-ClKnO7v0.js +162 -0
  146. package/dist/assets/{setting-row-DR23Z_5I.js → setting-row-DA3tJuew.js} +1 -1
  147. package/dist/assets/{settings-2-D8WWcBnw.js → settings-2-D_y7zXJz.js} +1 -1
  148. package/dist/assets/sizeCapture-X5ZJPWSS-DSo-t5ou.js +1 -0
  149. package/dist/assets/{skeleton-CloHeg0S.js → skeleton-0uTLUhm9.js} +1 -1
  150. package/dist/assets/src-Dad2ZbCt.js +1 -0
  151. package/dist/assets/stateDiagram-2N3HPSRC-BvAalBfz.js +1 -0
  152. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +1 -0
  153. package/dist/assets/swimlanes-5IMT3BWC-DdVqVa1a.js +2 -0
  154. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +8 -0
  155. package/dist/assets/{tabs-CX-ckWoo.js → tabs-h44XPocd.js} +1 -1
  156. package/dist/assets/{tag-chip-mCc23Rhb.js → tag-chip-bRZ04q3C.js} +1 -1
  157. package/dist/assets/timeline-definition-FHXFAJF6-D4ar8IQY.js +120 -0
  158. package/dist/assets/{trash-2-SAW1Z4Po.js → trash-2-BRpSE-Wt.js} +1 -1
  159. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +1 -0
  160. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +1 -0
  161. package/dist/assets/use-config-CwDEAl6f.js +1 -0
  162. package/dist/assets/{use-confirm-dialog-C95F6sT1.js → use-confirm-dialog-BbnWw-8c.js} +1 -1
  163. package/dist/assets/{use-viewport-layout-C6m3Nte7.js → use-viewport-layout-CqgIMDQL.js} +1 -1
  164. package/dist/assets/vennDiagram-L72KCM5P-CDXJ_i3p.js +34 -0
  165. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +1 -0
  166. package/dist/assets/wardleyDiagram-EHGQE667-D-dSuT_7.js +78 -0
  167. package/dist/assets/x-DdEDL6ju.js +1 -0
  168. package/dist/assets/xychartDiagram-FW5EYKEG-C9JCzsZF.js +7 -0
  169. package/dist/index.html +33 -32
  170. package/package.json +8 -8
  171. package/src/features/chat/components/chat-popover-content.tsx +1 -26
  172. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +84 -1
  173. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
  174. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  175. package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
  176. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
  177. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
  178. package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
  179. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +52 -2
  181. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
  182. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +36 -20
  183. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
  184. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
  185. package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
  186. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +37 -0
  187. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +140 -63
  188. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +175 -0
  189. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +66 -0
  190. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +8 -1
  191. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +274 -0
  192. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +18 -11
  193. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
  194. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +50 -4
  195. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +51 -0
  196. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
  197. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
  198. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +66 -8
  199. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +62 -21
  200. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +42 -10
  201. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +80 -0
  202. package/src/features/chat/features/message/components/chat-message-list.container.tsx +55 -60
  203. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +2 -0
  204. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +41 -3
  205. package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
  206. package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
  207. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
  208. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
  209. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
  210. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
  211. package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
  212. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
  213. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +81 -127
  214. package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
  215. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
  216. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
  217. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
  218. package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
  219. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
  220. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
  221. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +22 -24
  222. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +2 -1
  223. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +6 -17
  224. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +41 -19
  225. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  226. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +5 -1
  227. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +104 -3
  228. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +83 -0
  229. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +4 -1
  230. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +96 -25
  231. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +25 -0
  232. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +5 -1
  233. package/src/features/chat/managers/chat-session-list.manager.ts +1 -0
  234. package/src/features/chat/managers/chat-thread.manager.ts +15 -37
  235. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
  236. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +9 -0
  237. package/src/features/chat/stores/chat-thread.store.ts +16 -36
  238. package/src/features/chat/types/chat-input-bar.types.ts +2 -0
  239. package/src/features/settings/components/config/provider-form.tsx +0 -4
  240. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
  241. package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
  242. package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
  243. package/src/features/system-status/index.ts +9 -0
  244. package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
  245. package/src/index.css +10 -16
  246. package/src/shared/components/common/__tests__/brand-header.test.tsx +127 -10
  247. package/src/shared/components/common/brand-header.tsx +142 -56
  248. package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
  249. package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
  250. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
  251. package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
  252. package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
  253. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
  254. package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
  255. package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
  256. package/src/shared/hooks/use-projects.ts +28 -0
  257. package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
  258. package/src/shared/hooks/use-server-path-read.ts +10 -5
  259. package/src/shared/hooks/use-server-path-search.ts +40 -0
  260. package/src/shared/lib/api/index.ts +1 -0
  261. package/src/shared/lib/api/projects/project.utils.ts +14 -0
  262. package/src/shared/lib/api/types.ts +30 -3
  263. package/src/shared/lib/api/utils/server-path.utils.ts +22 -1
  264. package/src/shared/lib/i18n/locales/en-US/chat.json +43 -2
  265. package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
  266. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
  267. package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
  268. package/src/shared/lib/i18n/locales/zh-CN/chat.json +43 -2
  269. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
  270. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
  271. package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
  272. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +1 -4
  273. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +1 -18
  274. package/dist/assets/api-BOXOrpcJ.js +0 -13
  275. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +0 -106
  276. package/dist/assets/desktop-update-config-7bmNeWIC.js +0 -1
  277. package/dist/assets/doc-browser-CgdoLUT92.js +0 -1
  278. package/dist/assets/index-BFeNDQ4R.css +0 -1
  279. package/dist/assets/index-D_6U-M94.js +0 -2
  280. package/dist/assets/loader-circle-tYHStefV.js +0 -1
  281. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +0 -9
  282. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +0 -1
  283. package/dist/assets/plus-tywizkeY.js +0 -1
  284. package/dist/assets/providers-config-page-BKPo8SjH.js +0 -1
  285. package/dist/assets/remote-DXJryUxB.js +0 -1
  286. package/dist/assets/rotate-cw-BefO5brU.js +0 -1
  287. package/dist/assets/runtime-config-page-1Axv4QGu.js +0 -1
  288. package/dist/assets/security-config-DeQUIXfi.js +0 -1
  289. package/dist/assets/use-config-BucdaK23.js +0 -1
  290. package/dist/assets/x-DtCPHwCu.js +0 -1
  291. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +0 -41
  292. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +0 -55
  293. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
  294. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
  295. /package/dist/assets/{config-hints-fDrYfl0l.js → config-hints-LxG80Uj5.js} +0 -0
  296. /package/dist/assets/{host-capabilities-zZvY9U63.js → host-capabilities-CFbAfrrA.js} +0 -0
  297. /package/dist/assets/{jszip.min-Das2IAnb.js → jszip.min-CP5uPNQa.js} +0 -0
  298. /package/dist/assets/{middleware-BLMHv9YF.js → middleware-CAXxl-af.js} +0 -0
  299. /package/dist/assets/{provider-models-C_yOh6DE.js → provider-models-BoUhFo9R.js} +0 -0
  300. /package/dist/assets/{xlsx-B6zVt1RK.js → xlsx-DUqJhg5u.js} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { createChatComposerTextNode, createChatComposerTokenNode } from '@nextclaw/agent-chat-ui';
2
2
  import {
3
3
  buildInlineTokensFromTextProtocol,
4
- buildInlineSkillTokensFromComposer,
5
- CHAT_UI_INLINE_TOKENS_METADATA_KEY,
4
+ buildInlineTokensFromComposer,
5
+ CHAT_INLINE_TOKENS_METADATA_KEY,
6
6
  readInlineTokensFromMetadata,
7
7
  resolveInlineTokensForText
8
8
  } from '@/features/chat/features/input/utils/chat-inline-token.utils';
@@ -10,7 +10,7 @@ import {
10
10
  describe('chat-inline-token utils', () => {
11
11
  it('builds ordered inline skill tokens from composer nodes', () => {
12
12
  expect(
13
- buildInlineSkillTokensFromComposer([
13
+ buildInlineTokensFromComposer([
14
14
  createChatComposerTextNode('before '),
15
15
  createChatComposerTokenNode({
16
16
  tokenKind: 'skill',
@@ -42,7 +42,7 @@ describe('chat-inline-token utils', () => {
42
42
  it('reads generic inline token metadata safely', () => {
43
43
  expect(
44
44
  readInlineTokensFromMetadata({
45
- [CHAT_UI_INLINE_TOKENS_METADATA_KEY]: [
45
+ [CHAT_INLINE_TOKENS_METADATA_KEY]: [
46
46
  {
47
47
  kind: 'skill',
48
48
  key: 'weather',
@@ -97,4 +97,50 @@ describe('chat-inline-token utils', () => {
97
97
  }
98
98
  ]);
99
99
  });
100
+
101
+ it('serializes and parses workspace file and directory references', () => {
102
+ expect(
103
+ buildInlineTokensFromComposer([
104
+ createChatComposerTokenNode({
105
+ tokenKind: 'workspace_file',
106
+ tokenKey: 'src/file name.ts',
107
+ label: 'file name.ts',
108
+ }),
109
+ createChatComposerTokenNode({
110
+ tokenKind: 'workspace_directory',
111
+ tokenKey: 'docs/设计',
112
+ label: '设计',
113
+ }),
114
+ ]),
115
+ ).toEqual([
116
+ {
117
+ kind: 'workspace_file',
118
+ key: 'src/file name.ts',
119
+ label: 'file name.ts',
120
+ rawText: '@file:src%2Ffile%20name.ts',
121
+ },
122
+ {
123
+ kind: 'workspace_directory',
124
+ key: 'docs/设计',
125
+ label: '设计',
126
+ rawText: '@folder:docs%2F%E8%AE%BE%E8%AE%A1',
127
+ },
128
+ ]);
129
+ expect(
130
+ buildInlineTokensFromTextProtocol('review @file:src%2Ffile%20name.ts and @folder:docs%2F%E8%AE%BE%E8%AE%A1'),
131
+ ).toEqual([
132
+ {
133
+ kind: 'workspace_file',
134
+ key: 'src/file name.ts',
135
+ label: 'file name.ts',
136
+ rawText: '@file:src%2Ffile%20name.ts',
137
+ },
138
+ {
139
+ kind: 'workspace_directory',
140
+ key: 'docs/设计',
141
+ label: '设计',
142
+ rawText: '@folder:docs%2F%E8%AE%BE%E8%AE%A1',
143
+ },
144
+ ]);
145
+ });
100
146
  });
@@ -83,6 +83,25 @@ describe('buildChatSlashItems', () => {
83
83
 
84
84
  expect(items.map((item) => item.value)).toEqual(['weather', 'web-search', 'docs']);
85
85
  });
86
+
87
+ it('keeps skill source groups contiguous in their catalog order', () => {
88
+ const items = buildChatSlashItems(
89
+ [
90
+ createSkillRecord({ key: 'project:zeta', label: 'Zeta', groupKey: 'project', groupLabel: 'Project skills' }),
91
+ createSkillRecord({ key: 'global:alpha', label: 'Alpha', groupKey: 'global', groupLabel: 'Global skills' }),
92
+ createSkillRecord({ key: 'project:alpha', label: 'Alpha', groupKey: 'project', groupLabel: 'Project skills' }),
93
+ ],
94
+ '',
95
+ texts,
96
+ );
97
+
98
+ expect(items.map((item) => item.value)).toEqual(['project:alpha', 'project:zeta', 'global:alpha']);
99
+ expect(items.map((item) => [item.sectionKey, item.sectionLabel])).toEqual([
100
+ ['project', 'Project skills'],
101
+ ['project', 'Project skills'],
102
+ ['global', 'Global skills'],
103
+ ]);
104
+ });
86
105
  });
87
106
 
88
107
  describe('buildSkillPickerModel', () => {
@@ -158,6 +177,38 @@ describe('buildSkillPickerModel', () => {
158
177
  ]);
159
178
  });
160
179
 
180
+ it('groups the skill catalog by source when no recent group is visible', () => {
181
+ const model = buildSkillPickerModel({
182
+ skillRecords: [
183
+ createSkillRecord({ key: 'project:review', label: 'Review', groupKey: 'project', groupLabel: 'Project skills' }),
184
+ createSkillRecord({ key: 'workspace:docs', label: 'Docs', groupKey: 'workspace', groupLabel: 'NextClaw skills' }),
185
+ createSkillRecord({ key: 'global:browser', label: 'Browser', groupKey: 'global', groupLabel: 'Global skills' }),
186
+ createSkillRecord({ key: 'builtin:weather', label: 'Weather', groupKey: 'builtin', groupLabel: 'Built-in skills' })
187
+ ],
188
+ recentSkillValues: [],
189
+ groupedRecentSkillValues: [],
190
+ selectedSkills: [],
191
+ isLoading: false,
192
+ onSelectedKeysChange: vi.fn(),
193
+ texts: {
194
+ title: 'Skills',
195
+ searchPlaceholder: 'Search skills',
196
+ emptyLabel: 'No skills',
197
+ loadingLabel: 'Loading',
198
+ manageLabel: 'Manage',
199
+ recentSkillsLabel: 'Recent',
200
+ allSkillsLabel: 'All skills'
201
+ }
202
+ });
203
+
204
+ expect(model.groups?.map((group) => [group.key, group.label])).toEqual([
205
+ ['project', 'Project skills'],
206
+ ['workspace', 'NextClaw skills'],
207
+ ['global', 'Global skills'],
208
+ ['builtin', 'Built-in skills']
209
+ ]);
210
+ });
211
+
161
212
  it('still reorders recent skills even when grouped labels are omitted', () => {
162
213
  const model = buildSkillPickerModel({
163
214
  skillRecords: [
@@ -10,9 +10,7 @@ import {
10
10
  import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
11
11
  import type { NcpMessagePart } from '@nextclaw/ncp';
12
12
  import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
13
-
14
- const CHAT_SKILL_TOKEN_PREFIX = '$';
15
- const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
13
+ import { serializeChatComposerTokenText } from './chat-composer-token-protocol.utils';
16
14
 
17
15
  function appendTextPart(parts: NcpMessagePart[], text: string): NcpMessagePart[] {
18
16
  if (text.length === 0) {
@@ -110,13 +108,9 @@ export function deriveNcpMessagePartsFromComposer(
110
108
  continue;
111
109
  }
112
110
 
113
- if (node.tokenKind === 'skill') {
114
- parts = appendTextPart(parts, `${CHAT_SKILL_TOKEN_PREFIX}${node.tokenKey}`);
115
- continue;
116
- }
117
-
118
- if (node.tokenKind === 'panel_app') {
119
- parts = appendTextPart(parts, `${CHAT_PANEL_APP_TOKEN_PREFIX}${node.tokenKey}`);
111
+ const tokenText = serializeChatComposerTokenText(node);
112
+ if (tokenText) {
113
+ parts = appendTextPart(parts, tokenText);
120
114
  continue;
121
115
  }
122
116
 
@@ -0,0 +1,29 @@
1
+ import {
2
+ CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
3
+ CHAT_WORKSPACE_FILE_TOKEN_KIND,
4
+ } from '@nextclaw/shared';
5
+
6
+ const CHAT_SKILL_TOKEN_PREFIX = '$';
7
+ const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
8
+ const CHAT_WORKSPACE_FILE_TOKEN_PREFIX = '@file:';
9
+ const CHAT_WORKSPACE_DIRECTORY_TOKEN_PREFIX = '@folder:';
10
+
11
+ export function serializeChatComposerTokenText(params: {
12
+ tokenKey: string;
13
+ tokenKind: string;
14
+ }): string | null {
15
+ const { tokenKey, tokenKind } = params;
16
+ if (tokenKind === 'skill') {
17
+ return `${CHAT_SKILL_TOKEN_PREFIX}${tokenKey}`;
18
+ }
19
+ if (tokenKind === 'panel_app') {
20
+ return `${CHAT_PANEL_APP_TOKEN_PREFIX}${tokenKey}`;
21
+ }
22
+ if (tokenKind === CHAT_WORKSPACE_FILE_TOKEN_KIND) {
23
+ return `${CHAT_WORKSPACE_FILE_TOKEN_PREFIX}${encodeURIComponent(tokenKey)}`;
24
+ }
25
+ if (tokenKind === CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND) {
26
+ return `${CHAT_WORKSPACE_DIRECTORY_TOKEN_PREFIX}${encodeURIComponent(tokenKey)}`;
27
+ }
28
+ return null;
29
+ }
@@ -1,11 +1,19 @@
1
1
  import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
2
+ import {
3
+ CHAT_INLINE_TOKENS_METADATA_KEY,
4
+ CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
5
+ CHAT_WORKSPACE_FILE_TOKEN_KIND,
6
+ type ChatInlineTokenMetadata,
7
+ } from '@nextclaw/shared';
8
+ import { serializeChatComposerTokenText } from './chat-composer-token-protocol.utils';
2
9
 
3
- export const CHAT_UI_INLINE_TOKENS_METADATA_KEY = 'ui_inline_tokens';
4
- const CHAT_SKILL_TOKEN_PREFIX = '$';
10
+ export { CHAT_INLINE_TOKENS_METADATA_KEY };
5
11
  const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
6
12
  const CHAT_PANEL_APP_TOKEN_PATTERN = /@panel-app:([A-Za-z0-9_-]+)/g;
13
+ const CHAT_WORKSPACE_FILE_TOKEN_PATTERN = /@file:([^\s]+)/g;
14
+ const CHAT_WORKSPACE_DIRECTORY_TOKEN_PATTERN = /@folder:([^\s]+)/g;
7
15
 
8
- export type ChatInlineTokenSource = { kind: string; key: string; label: string; rawText: string };
16
+ export type ChatInlineTokenSource = ChatInlineTokenMetadata;
9
17
 
10
18
  function isRecord(value: unknown): value is Record<string, unknown> {
11
19
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -33,22 +41,60 @@ function dedupeInlineTokens(tokens: readonly ChatInlineTokenSource[]): ChatInlin
33
41
  return output;
34
42
  }
35
43
 
36
- export function buildInlineSkillTokensFromComposer(nodes: readonly ChatComposerNode[]): ChatInlineTokenSource[] {
44
+ export function buildInlineTokensFromComposer(nodes: readonly ChatComposerNode[]): ChatInlineTokenSource[] {
37
45
  const tokens: ChatInlineTokenSource[] = [];
38
46
  for (const node of nodes) {
39
- if (node.type !== 'token' || node.tokenKind !== 'skill') {
47
+ if (node.type !== 'token') {
48
+ continue;
49
+ }
50
+ if (
51
+ node.tokenKind !== 'skill' &&
52
+ node.tokenKind !== CHAT_WORKSPACE_FILE_TOKEN_KIND &&
53
+ node.tokenKind !== CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND
54
+ ) {
55
+ continue;
56
+ }
57
+ const rawText = serializeChatComposerTokenText(node);
58
+ if (!rawText) {
40
59
  continue;
41
60
  }
42
61
  tokens.push({
43
- kind: 'skill',
62
+ kind: node.tokenKind,
44
63
  key: node.tokenKey,
45
64
  label: node.label,
46
- rawText: `${CHAT_SKILL_TOKEN_PREFIX}${node.tokenKey}`
65
+ rawText,
47
66
  });
48
67
  }
49
68
  return dedupeInlineTokens(tokens);
50
69
  }
51
70
 
71
+ function appendWorkspaceTokens(params: {
72
+ kind: typeof CHAT_WORKSPACE_FILE_TOKEN_KIND | typeof CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND;
73
+ pattern: RegExp;
74
+ text: string;
75
+ tokens: ChatInlineTokenSource[];
76
+ }): void {
77
+ const { kind, pattern, text, tokens } = params;
78
+ for (const match of text.matchAll(pattern)) {
79
+ const encodedKey = match[1];
80
+ if (!encodedKey) {
81
+ continue;
82
+ }
83
+ let key: string;
84
+ try {
85
+ key = decodeURIComponent(encodedKey);
86
+ } catch {
87
+ continue;
88
+ }
89
+ tokens.push({
90
+ kind,
91
+ key,
92
+ label: key.split('/').filter(Boolean).at(-1) ?? key,
93
+ rawText: match[0],
94
+ });
95
+ }
96
+ }
97
+
52
98
  export function buildInlineTokensFromTextProtocol(text: string): ChatInlineTokenSource[] {
53
99
  const tokens: ChatInlineTokenSource[] = [];
54
100
  for (const match of text.matchAll(CHAT_PANEL_APP_TOKEN_PATTERN)) {
@@ -63,6 +109,18 @@ export function buildInlineTokensFromTextProtocol(text: string): ChatInlineToken
63
109
  rawText: `${CHAT_PANEL_APP_TOKEN_PREFIX}${key}`
64
110
  });
65
111
  }
112
+ appendWorkspaceTokens({
113
+ kind: CHAT_WORKSPACE_FILE_TOKEN_KIND,
114
+ pattern: CHAT_WORKSPACE_FILE_TOKEN_PATTERN,
115
+ text,
116
+ tokens,
117
+ });
118
+ appendWorkspaceTokens({
119
+ kind: CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
120
+ pattern: CHAT_WORKSPACE_DIRECTORY_TOKEN_PATTERN,
121
+ text,
122
+ tokens,
123
+ });
66
124
  return dedupeInlineTokens(tokens);
67
125
  }
68
126
 
@@ -76,7 +134,7 @@ export function resolveInlineTokensForText(
76
134
  export function readInlineTokensFromMetadata(
77
135
  metadata: Record<string, unknown> | undefined
78
136
  ): ChatInlineTokenSource[] {
79
- const raw = metadata?.[CHAT_UI_INLINE_TOKENS_METADATA_KEY];
137
+ const raw = metadata?.[CHAT_INLINE_TOKENS_METADATA_KEY];
80
138
  if (!Array.isArray(raw)) {
81
139
  return [];
82
140
  }
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  ChatSkillPickerOption,
3
+ ChatSkillPickerOptionGroup,
3
4
  ChatSkillPickerProps,
4
5
  ChatSlashItem,
5
6
  } from '@nextclaw/agent-chat-ui';
@@ -139,6 +140,13 @@ export function buildChatSlashItems(
139
140
  ): ChatSlashItem[] {
140
141
  const skillSortCollator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
141
142
  const recentOrderIndex = buildRecentOrderIndex(recentSkillValues);
143
+ const groupOrderIndex = new Map<string, number>();
144
+ for (const record of skillRecords) {
145
+ const groupKey = record.groupKey?.trim();
146
+ if (groupKey && !groupOrderIndex.has(groupKey)) {
147
+ groupOrderIndex.set(groupKey, groupOrderIndex.size);
148
+ }
149
+ }
142
150
 
143
151
  return skillRecords
144
152
  .map((record, order) => ({
@@ -148,6 +156,11 @@ export function buildChatSlashItems(
148
156
  }))
149
157
  .filter((entry) => entry.score > 0)
150
158
  .sort((left, right) => {
159
+ const leftGroupIndex = groupOrderIndex.get(left.record.groupKey?.trim() ?? '') ?? Number.POSITIVE_INFINITY;
160
+ const rightGroupIndex = groupOrderIndex.get(right.record.groupKey?.trim() ?? '') ?? Number.POSITIVE_INFINITY;
161
+ if (leftGroupIndex !== rightGroupIndex) {
162
+ return leftGroupIndex - rightGroupIndex;
163
+ }
151
164
  const leftTier = resolveSlashMatchTier(left.score);
152
165
  const rightTier = resolveSlashMatchTier(right.score);
153
166
  if (rightTier !== leftTier) {
@@ -178,6 +191,8 @@ export function buildChatSlashItems(
178
191
  `${texts.slashSkillSpecLabel}: ${record.key}`,
179
192
  ...(record.scopeLabel ? [`${texts.slashSkillScopeLabel}: ${record.scopeLabel}`] : [])
180
193
  ],
194
+ sectionKey: record.groupKey,
195
+ sectionLabel: record.groupLabel,
181
196
  tokenKind: 'skill',
182
197
  tokenKey: record.key,
183
198
  value: record.key
@@ -193,6 +208,46 @@ function buildSkillPickerOptions(skillRecords: ChatSkillRecord[]): ChatSkillPick
193
208
  }));
194
209
  }
195
210
 
211
+ function buildSkillPickerGroups(params: {
212
+ skillRecords: ChatSkillRecord[];
213
+ recentKeySet: Set<string>;
214
+ recentSkillsLabel: string;
215
+ allSkillsLabel: string;
216
+ }): ChatSkillPickerOptionGroup[] | undefined {
217
+ const { allSkillsLabel, recentKeySet, recentSkillsLabel, skillRecords } = params;
218
+ const recentRecords = skillRecords.filter((record) => recentKeySet.has(record.key));
219
+ const groupedRecords = new Map<string, { label: string; records: ChatSkillRecord[] }>();
220
+ for (const record of skillRecords) {
221
+ if (recentKeySet.has(record.key)) {
222
+ continue;
223
+ }
224
+ const groupKey = record.groupKey?.trim() || 'all-skills';
225
+ const groupLabel = record.groupLabel?.trim() || allSkillsLabel;
226
+ const group = groupedRecords.get(groupKey) ?? { label: groupLabel, records: [] };
227
+ group.records.push(record);
228
+ groupedRecords.set(groupKey, group);
229
+ }
230
+ const sourceGroups = [...groupedRecords].map(([key, group]) => ({
231
+ key,
232
+ label: group.label,
233
+ options: buildSkillPickerOptions(group.records),
234
+ }));
235
+ const hasSourceGroups = sourceGroups.some((group) => group.key !== 'all-skills');
236
+ if (recentRecords.length === 0 && !hasSourceGroups) {
237
+ return undefined;
238
+ }
239
+ return [
240
+ ...(recentRecords.length > 0
241
+ ? [{
242
+ key: 'recent-skills',
243
+ label: recentSkillsLabel,
244
+ options: buildSkillPickerOptions(recentRecords),
245
+ }]
246
+ : []),
247
+ ...sourceGroups,
248
+ ].filter((group) => group.options.length > 0);
249
+ }
250
+
196
251
  export function buildSkillPickerModel(params: {
197
252
  skillRecords: ChatSkillRecord[];
198
253
  recentSkillValues?: string[];
@@ -221,14 +276,9 @@ export function buildSkillPickerModel(params: {
221
276
  } = params;
222
277
  const prioritizedSkillRecords = prioritizeSkillRecords(skillRecords, recentSkillValues ?? []);
223
278
  const recentKeySet = new Set(groupedRecentSkillValues ?? []);
224
- const recentSkillOptions = buildSkillPickerOptions(
225
- prioritizedSkillRecords.filter((record) => recentKeySet.has(record.key))
226
- );
227
- const remainingSkillOptions = buildSkillPickerOptions(
228
- prioritizedSkillRecords.filter((record) => !recentKeySet.has(record.key))
229
- );
230
279
  return {
231
280
  title: texts.title,
281
+ allGroupsLabel: texts.allSkillsLabel,
232
282
  searchPlaceholder: texts.searchPlaceholder,
233
283
  emptyLabel: texts.emptyLabel,
234
284
  loadingLabel: texts.loadingLabel,
@@ -236,21 +286,12 @@ export function buildSkillPickerModel(params: {
236
286
  manageLabel: texts.manageLabel,
237
287
  manageHref: '/marketplace/skills',
238
288
  options: buildSkillPickerOptions(prioritizedSkillRecords),
239
- groups:
240
- recentSkillOptions.length > 0
241
- ? [
242
- {
243
- key: 'recent-skills',
244
- label: texts.recentSkillsLabel,
245
- options: recentSkillOptions
246
- },
247
- {
248
- key: 'all-skills',
249
- label: texts.allSkillsLabel,
250
- options: remainingSkillOptions
251
- }
252
- ].filter((group) => group.options.length > 0)
253
- : undefined,
289
+ groups: buildSkillPickerGroups({
290
+ skillRecords: prioritizedSkillRecords,
291
+ recentKeySet,
292
+ recentSkillsLabel: texts.recentSkillsLabel,
293
+ allSkillsLabel: texts.allSkillsLabel,
294
+ }),
254
295
  selectedKeys: selectedSkills,
255
296
  onSelectedKeysChange
256
297
  };
@@ -19,6 +19,7 @@ const captures = vi.hoisted(() => ({
19
19
  openFilePreview: vi.fn(),
20
20
  handleToolAction: vi.fn(),
21
21
  showContent: vi.fn(),
22
+ filePreviewProps: [] as Array<{ showBreadcrumbs?: boolean }>,
22
23
  }));
23
24
 
24
25
  vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
@@ -61,12 +62,25 @@ vi.mock("@/shared/lib/i18n", () => ({
61
62
  t: (key: string) => key,
62
63
  }));
63
64
 
65
+ vi.mock(
66
+ "@/features/chat/features/workspace/components/chat-session-workspace-file-preview",
67
+ () => ({
68
+ ChatSessionWorkspaceFilePreview: (props: {
69
+ showBreadcrumbs?: boolean;
70
+ }) => {
71
+ captures.filePreviewProps.push(props);
72
+ return <div data-testid="inline-workspace-file-preview" />;
73
+ },
74
+ }),
75
+ );
76
+
64
77
  beforeEach(() => {
65
78
  captures.renders = [];
66
79
  captures.language = "en";
67
80
  captures.openFilePreview.mockReset();
68
81
  captures.handleToolAction.mockReset();
69
82
  captures.showContent.mockReset();
83
+ captures.filePreviewProps = [];
70
84
  });
71
85
 
72
86
  it("reuses adapted message references when the source message object is unchanged", () => {
@@ -484,22 +498,40 @@ it("passes the inline panel app renderer to the shared chat UI", () => {
484
498
  expect(captures.renders[captures.renders.length - 1]?.renderPanelAppCard).toEqual(expect.any(Function));
485
499
  });
486
500
 
487
- it("passes the inline display renderer to the shared chat UI", () => {
501
+ it("renders inline file displays through the workspace file preview", () => {
488
502
  render(<ChatMessageListContainer messages={[]} isSending={false} />);
489
503
 
490
504
  const renderInlineDisplay =
491
505
  captures.renders[captures.renders.length - 1]?.renderInlineDisplay;
492
506
 
493
507
  expect(renderInlineDisplay).toEqual(expect.any(Function));
494
- expect(
495
- renderInlineDisplay?.({
496
- target: {
497
- type: "file",
498
- payload: { path: "README.md" },
499
- },
500
- title: "README",
501
- }),
502
- ).toBeUndefined();
508
+ const rendered = renderInlineDisplay?.({
509
+ target: {
510
+ type: "file",
511
+ payload: { path: "preview.html", viewer: "rendered" },
512
+ },
513
+ title: "Preview",
514
+ description: "Rendered HTML",
515
+ });
516
+
517
+ if (!isValidElement<{ display?: unknown }>(rendered)) {
518
+ throw new Error("Expected inline file renderer to return a React element");
519
+ }
520
+ expect(rendered.props.display).toEqual({
521
+ target: {
522
+ type: "file",
523
+ payload: { path: "preview.html", viewer: "rendered" },
524
+ },
525
+ title: "Preview",
526
+ description: "Rendered HTML",
527
+ });
528
+
529
+ render(rendered);
530
+
531
+ expect(screen.getByText("Preview")).toBeTruthy();
532
+ expect(screen.getByText("preview.html")).toBeTruthy();
533
+ expect(screen.queryByText("Rendered HTML")).toBeNull();
534
+ expect(captures.filePreviewProps.at(-1)?.showBreadcrumbs).toBe(false);
503
535
  });
504
536
 
505
537
  it("keeps inline panel app displays expandable from the card header", () => {
@@ -0,0 +1,80 @@
1
+ import type {
2
+ ChatFileOpenActionViewModel,
3
+ ChatInlineDisplayViewModel,
4
+ } from "@nextclaw/agent-chat-ui";
5
+ import { FileText } from "lucide-react";
6
+ import { ChatSessionWorkspaceFilePreview } from "@/features/chat/features/workspace/components/chat-session-workspace-file-preview";
7
+ import { createWorkspaceFileTab } from "@/features/chat/features/workspace/utils/chat-workspace-file-tab.utils";
8
+
9
+ type ChatInlineFilePreviewProps = {
10
+ display: ChatInlineDisplayViewModel;
11
+ parentSessionKey: string | null;
12
+ sessionProjectRoot: string | null;
13
+ sessionWorkingDir: string | null;
14
+ onFileOpen: (action: ChatFileOpenActionViewModel) => void;
15
+ };
16
+
17
+ function readFileName(path: string): string {
18
+ return path.split(/[\\/]/).filter(Boolean).pop() ?? path;
19
+ }
20
+
21
+ export function ChatInlineFilePreview({
22
+ display,
23
+ parentSessionKey,
24
+ sessionProjectRoot,
25
+ sessionWorkingDir,
26
+ onFileOpen,
27
+ }: ChatInlineFilePreviewProps) {
28
+ if (display.target.type !== "file") {
29
+ return null;
30
+ }
31
+ const { path, line, column, viewer } = display.target.payload;
32
+ const file = createWorkspaceFileTab(
33
+ {
34
+ path,
35
+ label: display.title,
36
+ viewMode: "preview",
37
+ previewViewer: viewer,
38
+ line,
39
+ column,
40
+ },
41
+ parentSessionKey,
42
+ );
43
+ if (!file) {
44
+ return null;
45
+ }
46
+ const title = display.title ?? readFileName(file.path);
47
+
48
+ return (
49
+ <section
50
+ className="my-2 w-full max-w-[48rem] overflow-hidden rounded-lg border border-border bg-card shadow-sm"
51
+ data-chat-inline-file-preview="true"
52
+ data-chat-message-wide-content="true"
53
+ >
54
+ <header className="border-b border-border bg-muted/45 px-3 py-2">
55
+ <div className="flex min-w-0 items-center gap-2 text-xs font-medium text-foreground">
56
+ <FileText
57
+ className="h-3.5 w-3.5 shrink-0 text-muted-foreground"
58
+ aria-hidden
59
+ />
60
+ <span className="truncate">{title}</span>
61
+ </div>
62
+ <p
63
+ className="mt-1 truncate font-mono text-[11px] font-normal text-muted-foreground"
64
+ title={file.path}
65
+ >
66
+ {file.path}
67
+ </p>
68
+ </header>
69
+ <div className="h-[420px] min-h-[240px] max-h-[min(60vh,420px)] overflow-hidden">
70
+ <ChatSessionWorkspaceFilePreview
71
+ file={file}
72
+ sessionProjectRoot={sessionProjectRoot}
73
+ sessionWorkingDir={sessionWorkingDir}
74
+ showBreadcrumbs={false}
75
+ onFileOpen={onFileOpen}
76
+ />
77
+ </div>
78
+ </section>
79
+ );
80
+ }