@vibe-forge/client 2.0.0 → 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/AGENTS.md +4 -1
  2. package/dist/assets/{arc-CbOXL0l9.js → arc-1JbypnRY.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-CqxINvsS.js → blockDiagram-c4efeb88-jT5b9nId.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BKazU0hb.js → c4Diagram-c83219d4-KNPh-1ta.js} +1 -1
  5. package/dist/assets/channel-pzvjJnfd.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-fAFX5BpB.js → classDiagram-beda092f-vvXuOT3F.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-w1VkNGJj.js → classDiagram-v2-2358418a-vH4j7skr.js} +1 -1
  8. package/dist/assets/clone-CtWwIeUb.js +1 -0
  9. package/dist/assets/{createText-1719965b-CEinakVP.js → createText-1719965b-COeSYnf3.js} +1 -1
  10. package/dist/assets/{cssMode-DPqRki4y.js → cssMode-Doc7QRKn.js} +1 -1
  11. package/dist/assets/{edges-96097737-Cb0F1_3K.js → edges-96097737-D09v6R_r.js} +1 -1
  12. package/dist/assets/{erDiagram-0228fc6a-C-N2fx-J.js → erDiagram-0228fc6a-CbZ6rfxl.js} +1 -1
  13. package/dist/assets/{flowDb-c6c81e3f-D1Xz_8Gf.js → flowDb-c6c81e3f-CnZOzhdk.js} +1 -1
  14. package/dist/assets/{flowDiagram-50d868cf-DyPSZyAj.js → flowDiagram-50d868cf-jv-xWM2p.js} +1 -1
  15. package/dist/assets/flowDiagram-v2-4f6560a1-BiyrkIKs.js +1 -0
  16. package/dist/assets/{flowchart-elk-definition-6af322e1-Dr1DDXwE.js → flowchart-elk-definition-6af322e1-D8pf677G.js} +1 -1
  17. package/dist/assets/{freemarker2-C3DvPFaK.js → freemarker2-CL0o23Gj.js} +1 -1
  18. package/dist/assets/{ganttDiagram-a2739b55-DmvY1GRj.js → ganttDiagram-a2739b55-BAPQzC4u.js} +1 -1
  19. package/dist/assets/{gitGraphDiagram-82fe8481-CoXfPYYi.js → gitGraphDiagram-82fe8481-BI2x71m0.js} +1 -1
  20. package/dist/assets/{graph-BkDQy7Qt.js → graph-CZK4Bjpq.js} +1 -1
  21. package/dist/assets/{handlebars-BcTFdqjl.js → handlebars-CCG38Pg6.js} +1 -1
  22. package/dist/assets/{html-Dg-O6XFr.js → html-BddshTWG.js} +1 -1
  23. package/dist/assets/{htmlMode-B_wqYWvn.js → htmlMode-xKXiYcDz.js} +1 -1
  24. package/dist/assets/{index-5325376f-kxPTR3_e.js → index-5325376f-BWMTD8RU.js} +1 -1
  25. package/dist/assets/{index-wkhI4dr6.js → index-CBe7kDkV.js} +398 -377
  26. package/dist/assets/index-MWOwVzqE.css +32 -0
  27. package/dist/assets/{infoDiagram-8eee0895-BEvqkwPI.js → infoDiagram-8eee0895-CR78btIF.js} +1 -1
  28. package/dist/assets/{javascript-DhlOH8_z.js → javascript-BPlRHPjg.js} +1 -1
  29. package/dist/assets/{journeyDiagram-c64418c1-gKtLYmmp.js → journeyDiagram-c64418c1-DZRv6FKz.js} +1 -1
  30. package/dist/assets/{jsonMode-DxTbF9OD.js → jsonMode-BYLVfdkf.js} +1 -1
  31. package/dist/assets/{layout-CDaZEk6E.js → layout-BtudyPU2.js} +1 -1
  32. package/dist/assets/{line-DNRQu8iq.js → line-DgHXrIhS.js} +1 -1
  33. package/dist/assets/{linear-Cph9Z6_j.js → linear-DtBoKICx.js} +1 -1
  34. package/dist/assets/{liquid-ByZ6JgRG.js → liquid-DxBlJk0W.js} +1 -1
  35. package/dist/assets/{lspLanguageFeatures-DzvhkgnM.js → lspLanguageFeatures-DmKVpmeH.js} +1 -1
  36. package/dist/assets/{mdx-D8RGHTl6.js → mdx-DrScsd-w.js} +1 -1
  37. package/dist/assets/{mermaid.core-BgcryF__.js → mermaid.core-Cj_NJ_lZ.js} +4 -4
  38. package/dist/assets/{mindmap-definition-8da855dc-WrxK0FcB.js → mindmap-definition-8da855dc-CMXbwtsc.js} +1 -1
  39. package/dist/assets/{pieDiagram-a8764435-VsZBsiQy.js → pieDiagram-a8764435-Bd6rfpJv.js} +1 -1
  40. package/dist/assets/{python-CXVtk_cg.js → python-Crbi7B7n.js} +1 -1
  41. package/dist/assets/{quadrantDiagram-1e28029f-BVlgwOvU.js → quadrantDiagram-1e28029f-H-FdBQn6.js} +1 -1
  42. package/dist/assets/{razor-0tind7h2.js → razor-DHyrzIfE.js} +1 -1
  43. package/dist/assets/{requirementDiagram-08caed73-CpPMPoYp.js → requirementDiagram-08caed73-BSyCVDSG.js} +1 -1
  44. package/dist/assets/{sankeyDiagram-a04cb91d-Cm5nnRmc.js → sankeyDiagram-a04cb91d-S6E6BReg.js} +1 -1
  45. package/dist/assets/{sequenceDiagram-c5b8d532-DpMlJvJB.js → sequenceDiagram-c5b8d532-BMHY4KMj.js} +1 -1
  46. package/dist/assets/{stateDiagram-1ecb1508-DU1zc7vq.js → stateDiagram-1ecb1508-BMrMw6XR.js} +1 -1
  47. package/dist/assets/{stateDiagram-v2-c2b004d7-D-0RgmAp.js → stateDiagram-v2-c2b004d7-B5SIFUb_.js} +1 -1
  48. package/dist/assets/{styles-b4e223ce-BSO-yNWV.js → styles-b4e223ce-DqiXwnx3.js} +1 -1
  49. package/dist/assets/{styles-ca3715f6-CHnsn2Ro.js → styles-ca3715f6-jNxW2Db8.js} +1 -1
  50. package/dist/assets/{styles-d45a18b0-B-rVGjEq.js → styles-d45a18b0-C4nlfLcZ.js} +1 -1
  51. package/dist/assets/{svgDrawCommon-b86b1483-CA3Pl89f.js → svgDrawCommon-b86b1483-FIWFuZfb.js} +1 -1
  52. package/dist/assets/{timeline-definition-faaaa080-BcihLR6s.js → timeline-definition-faaaa080-CEQDoqdf.js} +1 -1
  53. package/dist/assets/{tsMode-D9GGa5Ur.js → tsMode-DnNy3rE9.js} +1 -1
  54. package/dist/assets/{typescript-BT9CK_EL.js → typescript-YBZ4vw5B.js} +1 -1
  55. package/dist/assets/{xml-DNO75J-T.js → xml-BJGGYD70.js} +1 -1
  56. package/dist/assets/{xychartDiagram-f5964ef8-DJTwe32X.js → xychartDiagram-f5964ef8-DUNOFOk0.js} +1 -1
  57. package/dist/assets/{yaml-7CVzhiP2.js → yaml-BMY4mu5s.js} +1 -1
  58. package/dist/index.html +8 -3
  59. package/dist/manifest.webmanifest +30 -0
  60. package/dist/pwa-icon-192.png +0 -0
  61. package/dist/pwa-icon-512.png +0 -0
  62. package/dist/sw.js +105 -0
  63. package/index.html +6 -1
  64. package/package.json +13 -13
  65. package/public/manifest.webmanifest +30 -0
  66. package/public/pwa-icon-192.png +0 -0
  67. package/public/pwa-icon-512.png +0 -0
  68. package/public/sw.js +105 -0
  69. package/src/App.tsx +13 -1
  70. package/src/api/README.md +1 -0
  71. package/src/api/adapters.ts +63 -0
  72. package/src/api/auth-token.ts +51 -0
  73. package/src/api/auth.ts +46 -0
  74. package/src/api/automation.ts +10 -0
  75. package/src/api/base.ts +20 -17
  76. package/src/api/config.ts +5 -1
  77. package/src/api/knowledge.ts +59 -0
  78. package/src/api/sessions.ts +35 -3
  79. package/src/api/skill-hub.ts +126 -0
  80. package/src/api/workspace.ts +33 -1
  81. package/src/api/worktree-environments.ts +53 -0
  82. package/src/api.ts +62 -7
  83. package/src/client-build-info.ts +19 -0
  84. package/src/components/ConfigView.scss +596 -28
  85. package/src/components/ConfigView.tsx +569 -138
  86. package/src/components/NavRail.scss +1 -2
  87. package/src/components/NavRail.tsx +33 -3
  88. package/src/components/Sidebar.scss +0 -44
  89. package/src/components/Sidebar.tsx +110 -37
  90. package/src/components/auth/AuthGate.scss +79 -0
  91. package/src/components/auth/AuthGate.tsx +174 -0
  92. package/src/components/automation-view/@components/AutomationTaskComposer.tsx +220 -0
  93. package/src/components/automation-view/@components/AutomationTriggerRow.tsx +192 -0
  94. package/src/components/automation-view/@hooks/use-automation-startup-options-data.tsx +289 -0
  95. package/src/components/automation-view/@hooks/use-automation-startup-static-options.ts +51 -0
  96. package/src/components/automation-view/@utils/sender-model-options.tsx +52 -0
  97. package/src/components/automation-view/@utils/startup-options.ts +26 -0
  98. package/src/components/automation-view/AutomationEmptyGuide.tsx +61 -0
  99. package/src/components/automation-view/AutomationEmptyLanding.scss +165 -0
  100. package/src/components/automation-view/AutomationEmptyLanding.tsx +211 -0
  101. package/src/components/automation-view/AutomationRuleDetailPreview.tsx +179 -0
  102. package/src/components/automation-view/PanelTitleActions.tsx +66 -0
  103. package/src/components/automation-view/RuleFormPanel.scss +172 -49
  104. package/src/components/automation-view/RuleFormPanel.tsx +196 -91
  105. package/src/components/automation-view/RuleSidebar.scss +128 -41
  106. package/src/components/automation-view/RuleSidebar.tsx +173 -89
  107. package/src/components/automation-view/RunHistoryPanel.scss +307 -72
  108. package/src/components/automation-view/RunHistoryPanel.tsx +185 -165
  109. package/src/components/automation-view/TaskList.scss +130 -64
  110. package/src/components/automation-view/TaskList.tsx +15 -31
  111. package/src/components/automation-view/TriggerList.scss +87 -8
  112. package/src/components/automation-view/TriggerList.tsx +14 -173
  113. package/src/components/automation-view/index.scss +165 -37
  114. package/src/components/automation-view/index.tsx +174 -87
  115. package/src/components/automation-view/types.ts +13 -0
  116. package/src/components/chat/AGENTS.md +3 -1
  117. package/src/components/chat/ChatHeader.tsx +56 -33
  118. package/src/components/chat/ChatHistoryView.tsx +254 -123
  119. package/src/components/chat/NewSessionGuide.scss +274 -204
  120. package/src/components/chat/NewSessionGuide.tsx +46 -111
  121. package/src/components/chat/NewSessionGuideStarterList.tsx +190 -0
  122. package/src/components/chat/NewSessionGuideStarterSection.tsx +121 -0
  123. package/src/components/chat/bottom-dock-constants.ts +4 -0
  124. package/src/components/chat/conversation-starter-apply.ts +181 -0
  125. package/src/components/chat/git-controls/ChatGitControls.scss +65 -0
  126. package/src/components/chat/git-controls/DraftGitControls.tsx +14 -0
  127. package/src/components/chat/git-controls/DraftWorktreeEnvironmentDropdown.tsx +115 -0
  128. package/src/components/chat/messages/MessageItem.tsx +21 -6
  129. package/src/components/chat/messages/MessageStatusNotice.tsx +12 -4
  130. package/src/components/chat/messages/build-chat-history-status-notices.ts +18 -13
  131. package/src/components/chat/messages/message-action-utils.ts +29 -0
  132. package/src/components/chat/new-session-guide-config.ts +19 -0
  133. package/src/components/chat/new-session-guide-items.ts +172 -0
  134. package/src/components/chat/new-session-guide-list-order.ts +58 -0
  135. package/src/components/chat/sender/@components/account-select/AccountSelectControl.scss +112 -0
  136. package/src/components/chat/sender/@components/account-select/AccountSelectControl.tsx +280 -0
  137. package/src/components/chat/sender/@components/account-select/AccountSelectDropdown.scss +155 -0
  138. package/src/components/chat/sender/@components/adapter-select/AdapterSelectControl.scss +51 -12
  139. package/src/components/chat/sender/@components/adapter-select/AdapterSelectDropdown.scss +14 -0
  140. package/src/components/chat/sender/@components/effort-select/EffortSelectControl.scss +36 -0
  141. package/src/components/chat/sender/@components/effort-select/EffortSelectControl.tsx +17 -12
  142. package/src/components/chat/sender/@components/model-select/ModelSelectControl.scss +62 -0
  143. package/src/components/chat/sender/@components/model-select/ModelSelectControl.tsx +17 -12
  144. package/src/components/chat/sender/@components/model-select/ModelSelectMenu.scss +2 -0
  145. package/src/components/chat/sender/@components/model-select/ModelSelectMenuLabels.scss +24 -0
  146. package/src/components/chat/sender/@components/permission-mode-control/PermissionModeControl.scss +199 -0
  147. package/src/components/chat/sender/@components/permission-mode-control/PermissionModeControl.tsx +172 -0
  148. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsControl.scss +1 -10
  149. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsControl.tsx +16 -65
  150. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +13 -1
  151. package/src/components/chat/sender/@components/sender-header-controls/SenderHeaderControls.tsx +157 -0
  152. package/src/components/chat/sender/@components/sender-monaco-editor/monaco-runtime.ts +1 -17
  153. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectBase.scss +18 -2
  154. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectShared.scss +18 -1
  155. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.scss +2 -0
  156. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.tsx +40 -40
  157. package/src/components/chat/sender/@components/session-target/SenderSessionTargetBar.scss +215 -0
  158. package/src/components/chat/sender/@components/session-target/SenderSessionTargetBar.tsx +185 -0
  159. package/src/components/chat/sender/@core/build-sender-toolbar.ts +6 -0
  160. package/src/components/chat/sender/@core/create-sender-toolbar-handlers.ts +21 -1
  161. package/src/components/chat/sender/@core/get-sender-runtime-state.ts +3 -2
  162. package/src/components/chat/sender/@core/sender-toolbar-bindings.ts +12 -0
  163. package/src/components/chat/sender/@hooks/use-sender-controller.ts +56 -1
  164. package/src/components/chat/sender/@hooks/use-sender-reference-actions.ts +9 -66
  165. package/src/components/chat/sender/@types/sender-props.ts +18 -0
  166. package/src/components/chat/sender/@types/sender-toolbar-types.ts +8 -1
  167. package/src/components/chat/sender/@types/sender-types.ts +1 -3
  168. package/src/components/chat/sender/@utils/sender-constants.ts +1 -1
  169. package/src/components/chat/sender/Sender.scss +257 -2
  170. package/src/components/chat/sender/Sender.tsx +1 -0
  171. package/src/components/chat/status-bar/ChatStatusBar.scss +85 -0
  172. package/src/components/chat/status-bar/ChatStatusBar.tsx +48 -0
  173. package/src/components/chat/terminal/@components/TerminalManagerList.tsx +191 -0
  174. package/src/components/chat/terminal/@components/TerminalPane.scss +71 -0
  175. package/src/components/chat/terminal/@components/TerminalPane.tsx +137 -0
  176. package/src/components/chat/terminal/@components/TerminalPanelActions.tsx +75 -0
  177. package/src/components/chat/terminal/@hooks/use-terminal-instance.ts +36 -0
  178. package/src/components/chat/terminal/@hooks/use-terminal-session.ts +18 -21
  179. package/src/components/chat/terminal/@hooks/use-terminal-title-editor.ts +72 -0
  180. package/src/components/chat/terminal/@utils/terminal-keyboard.ts +141 -0
  181. package/src/components/chat/terminal/@utils/terminal-panes.ts +123 -0
  182. package/src/components/chat/terminal/ChatTerminalView.scss +310 -38
  183. package/src/components/chat/terminal/ChatTerminalView.tsx +151 -79
  184. package/src/components/chat/tools/core/ToolDiffViewer.tsx +3 -17
  185. package/src/components/chat/workspace-drawer/ChatWorkspaceDrawer.scss +778 -0
  186. package/src/components/chat/workspace-drawer/ChatWorkspaceDrawer.tsx +112 -0
  187. package/src/components/chat/workspace-drawer/ChatWorkspaceDrawerToolbar.tsx +183 -0
  188. package/src/components/chat/workspace-drawer/WorkspaceDrawerChangedFileRow.tsx +75 -0
  189. package/src/components/chat/workspace-drawer/WorkspaceDrawerChangedFiles.tsx +161 -0
  190. package/src/components/chat/workspace-drawer/WorkspaceDrawerChangedFolderTree.tsx +191 -0
  191. package/src/components/chat/workspace-drawer/WorkspaceDrawerTree.tsx +35 -0
  192. package/src/components/chat/workspace-drawer/WorkspaceDrawerTreeState.tsx +17 -0
  193. package/src/components/chat/workspace-drawer/changed-files-model.ts +152 -0
  194. package/src/components/chat/workspace-drawer/workspace-drawer-icons.ts +110 -0
  195. package/src/components/chat/workspace-file-editor/WorkspaceFileBreadcrumb.tsx +17 -0
  196. package/src/components/chat/workspace-file-editor/WorkspaceFileEditorView.scss +283 -0
  197. package/src/components/chat/workspace-file-editor/WorkspaceFileEditorView.tsx +165 -0
  198. package/src/components/chat/workspace-file-editor/WorkspaceFileTabs.tsx +135 -0
  199. package/src/components/chat/workspace-file-editor/use-workspace-file-editor-state.ts +113 -0
  200. package/src/components/chat/workspace-file-editor/workspace-file-editor-language.ts +55 -0
  201. package/src/components/composer-landing/ComposerLanding.scss +75 -0
  202. package/src/components/composer-landing/ComposerLanding.tsx +47 -0
  203. package/src/components/config/AGENTS.md +45 -0
  204. package/src/components/config/AdapterAccountsManager.scss +540 -0
  205. package/src/components/config/AdapterAccountsManager.tsx +846 -0
  206. package/src/components/config/AppSettingsPanel.tsx +85 -9
  207. package/src/components/config/ConfigAboutSection.scss +7 -1
  208. package/src/components/config/ConfigAboutSection.tsx +21 -3
  209. package/src/components/config/ConfigEditors.scss +12 -0
  210. package/src/components/config/ConfigFieldRow.scss +88 -4
  211. package/src/components/config/ConfigSectionForm.scss +88 -3
  212. package/src/components/config/ConfigSectionForm.tsx +948 -138
  213. package/src/components/config/ConfigSectionPanel.tsx +188 -12
  214. package/src/components/config/ConfigSourceSwitch.tsx +32 -18
  215. package/src/components/config/DetailCollectionFieldActions.tsx +63 -0
  216. package/src/components/config/DetailListField.tsx +413 -0
  217. package/src/components/config/McpServerItemEditor.tsx +154 -0
  218. package/src/components/config/RecommendedModelsItemEditor.tsx +146 -0
  219. package/src/components/config/WorktreeEnvironmentDetailView.tsx +126 -0
  220. package/src/components/config/WorktreeEnvironmentListView.tsx +126 -0
  221. package/src/components/config/WorktreeEnvironmentPanel.scss +430 -0
  222. package/src/components/config/WorktreeEnvironmentPanel.tsx +147 -0
  223. package/src/components/config/WorktreeEnvironmentScriptEditorCard.tsx +125 -0
  224. package/src/components/config/WorktreeEnvironmentScriptEditors.tsx +189 -0
  225. package/src/components/config/configConflict.ts +41 -0
  226. package/src/components/config/configDetail.ts +381 -0
  227. package/src/components/config/configSchema.ts +850 -179
  228. package/src/components/config/configUtils.ts +1 -1
  229. package/src/components/config/record-editors/RecordEditors.scss +187 -2
  230. package/src/components/config/record-editors/RecordJsonEditor.tsx +27 -2
  231. package/src/components/config/record-editors/SchemaObjectEditor.tsx +183 -0
  232. package/src/components/config/record-editors/SchemaRecordEditor.tsx +184 -0
  233. package/src/components/config/record-editors/index.tsx +2 -1
  234. package/src/components/config/record-editors/schemaRecordUtils.ts +55 -0
  235. package/src/components/config/use-worktree-environment-auto-save.ts +386 -0
  236. package/src/components/config/worktree-environment-panel-model.ts +108 -0
  237. package/src/components/dock-panel/DockPanel.scss +84 -17
  238. package/src/components/dock-panel/DockPanel.tsx +37 -34
  239. package/src/components/dock-panel/DockPanelHeader.tsx +65 -0
  240. package/src/components/dock-panel/use-dock-panel-fullscreen.ts +51 -0
  241. package/src/components/knowledge-base/KnowledgeBaseView.scss +276 -38
  242. package/src/components/knowledge-base/KnowledgeBaseView.tsx +252 -46
  243. package/src/components/knowledge-base/components/@hooks/use-skills-cli-modal-controller.ts +157 -0
  244. package/src/components/knowledge-base/components/@hooks/use-skills-tab-actions.ts +63 -0
  245. package/src/components/knowledge-base/components/ActionButton.scss +30 -4
  246. package/src/components/knowledge-base/components/ActionButton.tsx +13 -3
  247. package/src/components/knowledge-base/components/CreateSkillModal.tsx +59 -0
  248. package/src/components/knowledge-base/components/EmptyState.scss +4 -2
  249. package/src/components/knowledge-base/components/EntitiesTab.tsx +20 -20
  250. package/src/components/knowledge-base/components/EntityList.scss +3 -0
  251. package/src/components/knowledge-base/components/FilterBar.scss +1 -0
  252. package/src/components/knowledge-base/components/FilterBar.tsx +12 -8
  253. package/src/components/knowledge-base/components/FlowsTab.tsx +20 -20
  254. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +7 -6
  255. package/src/components/knowledge-base/components/KnowledgeContentControls.tsx +35 -0
  256. package/src/components/knowledge-base/components/KnowledgeList.scss +14 -3
  257. package/src/components/knowledge-base/components/KnowledgeMobilePanel.tsx +122 -0
  258. package/src/components/knowledge-base/components/KnowledgeSidebar.tsx +97 -0
  259. package/src/components/knowledge-base/components/LoadingState.scss +2 -1
  260. package/src/components/knowledge-base/components/ProjectSkillsList.tsx +79 -0
  261. package/src/components/knowledge-base/components/RuleList.scss +3 -0
  262. package/src/components/knowledge-base/components/RulesTab.tsx +31 -30
  263. package/src/components/knowledge-base/components/SectionHeader.scss +13 -1
  264. package/src/components/knowledge-base/components/SectionHeader.tsx +5 -3
  265. package/src/components/knowledge-base/components/SkillArchiveInput.tsx +43 -0
  266. package/src/components/knowledge-base/components/SkillHubResultItem.tsx +112 -0
  267. package/src/components/knowledge-base/components/SkillMarketResults.tsx +98 -0
  268. package/src/components/knowledge-base/components/SkillMarketView.tsx +198 -0
  269. package/src/components/knowledge-base/components/SkillMarketView.types.ts +28 -0
  270. package/src/components/knowledge-base/components/SkillRegistryErrors.tsx +21 -0
  271. package/src/components/knowledge-base/components/SkillRegistryModal.tsx +74 -0
  272. package/src/components/knowledge-base/components/SkillsCliModal.tsx +154 -0
  273. package/src/components/knowledge-base/components/SkillsTab.scss +424 -0
  274. package/src/components/knowledge-base/components/SkillsTab.tsx +166 -35
  275. package/src/components/knowledge-base/components/SkillsTabActions.tsx +88 -0
  276. package/src/components/knowledge-base/components/SpecList.scss +3 -0
  277. package/src/components/knowledge-base/components/TabContent.scss +4 -3
  278. package/src/components/knowledge-base/components/skill-hub-utils.ts +108 -0
  279. package/src/components/knowledge-base/components/use-skill-market-filters.ts +37 -0
  280. package/src/components/knowledge-base/components/use-skill-market-query-input.ts +44 -0
  281. package/src/components/knowledge-base/components/use-skill-market-search.ts +49 -0
  282. package/src/components/knowledge-base/components/use-skill-registry-modal.ts +68 -0
  283. package/src/components/monaco/monaco-runtime.ts +44 -0
  284. package/src/components/monaco/use-monaco-theme.ts +63 -0
  285. package/src/components/nav-rail-account-actions.tsx +104 -0
  286. package/src/components/server-connection/ServerConnectionGate.scss +356 -0
  287. package/src/components/server-connection/ServerConnectionGate.tsx +238 -0
  288. package/src/components/server-connection/ServerConnectionProfileModal.tsx +145 -0
  289. package/src/components/server-connection/ServerConnectionProfiles.tsx +113 -0
  290. package/src/components/server-connection/ServerConnectionUrlInput.tsx +85 -0
  291. package/src/components/sidebar/SidebarHeader.scss +5 -41
  292. package/src/components/sidebar/SidebarHeader.tsx +117 -91
  293. package/src/components/sidebar/SidebarHeaderSearchActions.tsx +24 -28
  294. package/src/components/sidebar/sidebar-search-visibility.ts +18 -0
  295. package/src/components/sidebar-list/SidebarListHeader.scss +246 -0
  296. package/src/components/sidebar-list/SidebarListHeader.tsx +146 -0
  297. package/src/components/workspace/ContextFilePicker.scss +9 -26
  298. package/src/components/workspace/ContextFilePicker.tsx +31 -113
  299. package/src/components/workspace/context-file-types.ts +36 -0
  300. package/src/components/workspace/project-file-tree/ProjectFileTree.scss +298 -0
  301. package/src/components/workspace/project-file-tree/ProjectFileTree.tsx +138 -0
  302. package/src/components/workspace/project-file-tree/ProjectFileTreeRow.tsx +167 -0
  303. package/src/components/workspace/project-file-tree/ProjectFileTreeRowContextMenu.tsx +106 -0
  304. package/src/components/workspace/project-file-tree/ProjectFileTreeRows.tsx +139 -0
  305. package/src/components/workspace/project-file-tree/project-file-tree-helpers.ts +101 -0
  306. package/src/components/workspace/project-file-tree/project-file-tree-icons.ts +93 -0
  307. package/src/components/workspace/project-file-tree/project-file-tree-types.ts +27 -0
  308. package/src/components/workspace/project-file-tree/use-project-file-tree-data.ts +197 -0
  309. package/src/components/workspace/project-file-tree/use-project-file-tree-selection.ts +144 -0
  310. package/src/hooks/chat/chat-session-target.ts +69 -0
  311. package/src/hooks/chat/chat-session-workspace-draft.ts +11 -4
  312. package/src/hooks/chat/interaction-state.ts +1 -0
  313. package/src/hooks/chat/optimistic-session-creation.ts +189 -0
  314. package/src/hooks/chat/use-chat-adapter-account-selection.tsx +156 -0
  315. package/src/hooks/chat/use-chat-route-bottom-panel.ts +181 -0
  316. package/src/hooks/chat/use-chat-route-deep-link-view.ts +33 -0
  317. package/src/hooks/chat/use-chat-session-actions.ts +259 -65
  318. package/src/hooks/chat/use-chat-session-messages.ts +71 -4
  319. package/src/hooks/chat/use-chat-session.ts +36 -1
  320. package/src/hooks/chat/workspace-file-panel-state.ts +43 -0
  321. package/src/hooks/session-subscription-cache.ts +25 -0
  322. package/src/hooks/use-chat-layout-query-state.ts +29 -0
  323. package/src/hooks/use-sender-header-query-state.ts +35 -0
  324. package/src/hooks/use-session-subscription.ts +17 -8
  325. package/src/hooks/useQueryParams.ts +91 -23
  326. package/src/i18n-resources.ts +44 -0
  327. package/src/i18n.ts +21 -6
  328. package/src/main.tsx +8 -0
  329. package/src/pwa.ts +46 -0
  330. package/src/resources/locales/en.json +741 -24
  331. package/src/resources/locales/zh.json +743 -26
  332. package/src/routes/ChatRoute.scss +110 -7
  333. package/src/routes/ChatRoute.tsx +11 -165
  334. package/src/routes/ChatRouteBottomPanel.tsx +47 -0
  335. package/src/routes/ChatRouteView.tsx +199 -0
  336. package/src/runtime-config.ts +155 -2
  337. package/src/server-connection-history.ts +179 -0
  338. package/src/store/index.ts +151 -3
  339. package/src/styles/global.scss +3 -0
  340. package/src/utils/mobile-viewport.ts +67 -0
  341. package/src/version-compatibility.ts +37 -0
  342. package/src/vite-env.d.ts +9 -0
  343. package/src/ws.ts +20 -9
  344. package/vite.config.ts +23 -1
  345. package/dist/assets/channel-Dnopc5A6.js +0 -1
  346. package/dist/assets/clone-sQthahUA.js +0 -1
  347. package/dist/assets/flowDiagram-v2-4f6560a1-OazrdWQO.js +0 -1
  348. package/dist/assets/index-o93dlo92.css +0 -32
  349. package/src/components/chat/NewSessionGuideCompactPanel.tsx +0 -130
  350. package/src/components/chat/NewSessionGuideGrid.tsx +0 -141
  351. package/src/components/chat/sender/@components/reference-actions/ReferencePermissionActionsPopover.tsx +0 -114
@@ -1,57 +1,283 @@
1
1
  import './ConfigSectionForm.scss'
2
2
 
3
- import { Collapse, Empty, Input, InputNumber, Select, Slider, Switch } from 'antd'
3
+ import { Button, Collapse, Empty, Input, InputNumber, Select, Slider, Switch } from 'antd'
4
4
  import type { ReactNode } from 'react'
5
+ import { Fragment, useMemo } from 'react'
6
+ import useSWR from 'swr'
5
7
 
8
+ import type { ConfigUiObjectSchema, ConfigUiSection } from '@vibe-forge/types'
9
+
10
+ import { getAdapterAccounts } from '#~/api'
6
11
  import { normalizeSendShortcut, resolveSendShortcut } from '#~/utils/shortcutUtils'
12
+
13
+ import { AdapterAccountsManager, mergeAccounts } from './AdapterAccountsManager'
14
+ import { DisplayValue } from './ConfigDisplayValue'
7
15
  import { ComplexTextEditor, StringArrayEditor } from './ConfigEditors'
8
16
  import { FieldRow } from './ConfigFieldRow'
9
17
  import { ShortcutInput } from './ConfigShortcutInput'
18
+ import { DetailCollectionField } from './DetailListField'
19
+ import { McpServerItemEditor } from './McpServerItemEditor'
20
+ import { RecommendedModelsItemEditor } from './RecommendedModelsItemEditor'
21
+ import type { ConfigDetailRoute } from './configDetail'
22
+ import { resolveConfigDetailRouteMeta } from './configDetail'
10
23
  import type { FieldSpec } from './configSchema'
11
- import { configGroupMeta, configSchema } from './configSchema'
24
+ import { configGroupMeta, configGroupOrder, configSchema } from './configSchema'
12
25
  import {
13
26
  getFieldDescription,
14
27
  getFieldLabel,
15
28
  getTypeIcon,
16
29
  getValueByPath,
17
30
  getValueType,
18
- isEmptyValue,
19
31
  setValueByPath
20
32
  } from './configUtils'
21
33
  import type { TranslationFn } from './configUtils'
22
34
  import {
23
35
  BooleanRecordEditor,
24
- ChannelRecordEditor,
25
36
  KeyValueEditor,
26
37
  McpServersRecordEditor,
27
38
  ModelServicesRecordEditor,
28
- RecordJsonEditor
39
+ RecordJsonEditor,
40
+ SchemaObjectEditor,
41
+ SchemaRecordEditor
29
42
  } from './record-editors/index'
43
+ import { resolveConfigUiRecordEntry } from './record-editors/schemaRecordUtils'
44
+
45
+ const directRecordSections = new Set(['models'])
46
+ const directDetailSections = new Set(['modelServices', 'channels', 'adapters'])
47
+ const defaultGroupOrder = ['base', 'permissions', 'env', 'items', 'default']
48
+ const isRecord = (value: unknown): value is Record<string, unknown> => (
49
+ value != null && typeof value === 'object' && !Array.isArray(value)
50
+ )
51
+ const isTopLevelField = (path: string[], key: string) => path.length === 1 && path[0] === key
52
+ const configSelectSuffixIcon = (
53
+ <span className='material-symbols-rounded config-view__select-chevron'>expand_more</span>
54
+ )
30
55
 
31
56
  export const SectionForm = ({
32
57
  sectionKey,
33
58
  fields: providedFields,
59
+ uiSection,
34
60
  value,
61
+ resolvedValue,
35
62
  onChange,
36
63
  mergedModelServices,
37
64
  mergedAdapters,
38
65
  selectedModelService,
66
+ worktreeEnvironmentOptions,
67
+ detailRoute = null,
68
+ onOpenDetailRoute,
39
69
  t
40
70
  }: {
41
71
  sectionKey: string
42
72
  fields?: FieldSpec[]
73
+ uiSection?: ConfigUiSection
43
74
  value: unknown
75
+ resolvedValue?: unknown
44
76
  onChange: (nextValue: unknown) => void
45
77
  mergedModelServices: Record<string, unknown>
46
78
  mergedAdapters: Record<string, unknown>
47
79
  selectedModelService?: string
80
+ worktreeEnvironmentOptions?: Array<{ value: string; label: ReactNode }>
81
+ detailRoute?: ConfigDetailRoute | null
82
+ onOpenDetailRoute?: (route: ConfigDetailRoute) => void
48
83
  t: TranslationFn
49
84
  }) => {
50
85
  const fields = providedFields ?? configSchema[sectionKey] ?? []
86
+ const detailContext = {
87
+ mergedModelServices,
88
+ mergedAdapters,
89
+ t
90
+ }
91
+ const resolveSchemaFieldLabel = (schemaSectionKey: string) => (field: { path: string[] }, fallback: string) => (
92
+ getFieldLabel(t, schemaSectionKey, field.path, fallback)
93
+ )
94
+ const resolveSchemaFieldDescription = (schemaSectionKey: string) => (field: { path: string[] }, fallback: string) => {
95
+ const translated = getFieldDescription(t, schemaSectionKey, field.path)
96
+ return translated !== '' ? translated : fallback
97
+ }
98
+ const renderAdapterSection = ({
99
+ body,
100
+ title,
101
+ collapsible = false,
102
+ defaultExpanded = true,
103
+ collapseKey = 'section'
104
+ }: {
105
+ body: ReactNode
106
+ title?: string
107
+ collapsible?: boolean
108
+ defaultExpanded?: boolean
109
+ collapseKey?: string
110
+ }) => {
111
+ if (collapsible) {
112
+ return (
113
+ <Collapse
114
+ className='config-view__subsection-collapse'
115
+ ghost
116
+ expandIconPosition='end'
117
+ expandIcon={({ isActive }) => (
118
+ <span
119
+ className={`material-symbols-rounded config-view__subsection-expand-icon${isActive ? ' is-active' : ''}`}
120
+ >
121
+ chevron_right
122
+ </span>
123
+ )}
124
+ defaultActiveKey={defaultExpanded ? [collapseKey] : []}
125
+ items={[
126
+ {
127
+ key: collapseKey,
128
+ label: <div className='config-view__subsection-title'>{title}</div>,
129
+ children: body
130
+ }
131
+ ]}
132
+ />
133
+ )
134
+ }
135
+
136
+ return (
137
+ <div className='config-view__subsection'>
138
+ {title != null && title !== '' && (
139
+ <div className='config-view__subsection-title'>{title}</div>
140
+ )}
141
+ {body}
142
+ </div>
143
+ )
144
+ }
145
+ const renderAdapterSchemaSection = ({
146
+ schema,
147
+ currentValue,
148
+ onCurrentValueChange,
149
+ visibleFieldPaths,
150
+ title,
151
+ collapsible = false,
152
+ defaultExpanded = true,
153
+ collapseKey,
154
+ resolveFieldOptions
155
+ }: {
156
+ schema: ConfigUiObjectSchema
157
+ currentValue: Record<string, unknown>
158
+ onCurrentValueChange: (nextValue: Record<string, unknown>) => void
159
+ visibleFieldPaths: string[][]
160
+ title?: string
161
+ collapsible?: boolean
162
+ defaultExpanded?: boolean
163
+ collapseKey?: string
164
+ resolveFieldOptions?: Parameters<typeof SchemaObjectEditor>[0]['resolveFieldOptions']
165
+ }) => {
166
+ if (visibleFieldPaths.length === 0) return null
167
+
168
+ return renderAdapterSection({
169
+ title,
170
+ collapsible,
171
+ defaultExpanded,
172
+ collapseKey,
173
+ body: (
174
+ <div className='config-view__subsection-body'>
175
+ <SchemaObjectEditor
176
+ value={currentValue}
177
+ schema={schema}
178
+ onChange={onCurrentValueChange}
179
+ t={t}
180
+ visibleFieldPaths={visibleFieldPaths}
181
+ resolveFieldLabel={resolveSchemaFieldLabel('adapters')}
182
+ resolveFieldDescription={resolveSchemaFieldDescription('adapters')}
183
+ resolveFieldOptions={resolveFieldOptions}
184
+ />
185
+ </div>
186
+ )
187
+ })
188
+ }
189
+ const renderAdapterAdvancedSections = ({
190
+ schema,
191
+ currentValue,
192
+ onCurrentValueChange,
193
+ modelSection,
194
+ advancedSections
195
+ }: {
196
+ schema: ConfigUiObjectSchema
197
+ currentValue: Record<string, unknown>
198
+ onCurrentValueChange: (nextValue: Record<string, unknown>) => void
199
+ modelSection?: {
200
+ title: string
201
+ visibleFieldPaths: string[][]
202
+ }
203
+ advancedSections: Array<{
204
+ key: string
205
+ title: string
206
+ visibleFieldPaths: string[][]
207
+ }>
208
+ }) => {
209
+ const visibleAdvancedSections = advancedSections.filter(section => section.visibleFieldPaths.length > 0)
210
+ const hasModelSection = modelSection != null && modelSection.visibleFieldPaths.length > 0
211
+ if (!hasModelSection && visibleAdvancedSections.length === 0) return null
212
+ const shouldExpandAdvancedByDefault = !hasModelSection && visibleAdvancedSections.length > 0
213
+
214
+ return (
215
+ <div className='config-view__field-stack'>
216
+ {hasModelSection && renderAdapterSchemaSection({
217
+ schema,
218
+ currentValue,
219
+ onCurrentValueChange,
220
+ visibleFieldPaths: modelSection.visibleFieldPaths,
221
+ title: modelSection.title,
222
+ collapsible: true,
223
+ defaultExpanded: true,
224
+ collapseKey: 'models'
225
+ })}
226
+ {visibleAdvancedSections.length > 0 && renderAdapterSection({
227
+ title: t('config.sectionGroups.advanced'),
228
+ collapsible: true,
229
+ defaultExpanded: shouldExpandAdvancedByDefault,
230
+ collapseKey: 'advanced',
231
+ body: (
232
+ <div className='config-view__subsection-body'>
233
+ <div className='config-view__field-stack'>
234
+ {visibleAdvancedSections.map(section => (
235
+ <Fragment key={section.key}>
236
+ {renderAdapterSchemaSection({
237
+ schema,
238
+ currentValue,
239
+ onCurrentValueChange,
240
+ visibleFieldPaths: section.visibleFieldPaths,
241
+ title: section.title
242
+ })}
243
+ </Fragment>
244
+ ))}
245
+ </div>
246
+ </div>
247
+ )
248
+ })}
249
+ </div>
250
+ )
251
+ }
252
+
253
+ if (uiSection?.kind === 'recordMap' && fields.length === 0) {
254
+ const recordValue = (value != null && typeof value === 'object')
255
+ ? value as Record<string, unknown>
256
+ : {}
257
+ if (uiSection.recordMap.mode === 'discriminated' && (uiSection.recordMap.entryKinds?.length ?? 0) === 0) {
258
+ return (
259
+ <RecordJsonEditor
260
+ value={recordValue}
261
+ onChange={onChange}
262
+ t={t}
263
+ keyPlaceholder={t('config.editor.fieldKey')}
264
+ />
265
+ )
266
+ }
267
+ return (
268
+ <SchemaRecordEditor
269
+ value={recordValue}
270
+ schema={uiSection.recordMap}
271
+ onChange={onChange}
272
+ t={t}
273
+ keyPlaceholder={uiSection.recordMap.keyPlaceholder ?? t('config.editor.fieldKey')}
274
+ />
275
+ )
276
+ }
277
+
51
278
  if (fields.length === 0) {
52
279
  return <Empty description={t('common.noData')} image={null} />
53
280
  }
54
- const directRecordSections = new Set(['models', 'modelServices', 'channels', 'adapters', 'plugins', 'mcp'])
55
281
 
56
282
  const modelServiceEntries = Object.entries(mergedModelServices)
57
283
  const modelServiceOptions: Array<{ value: string; label: ReactNode }> = modelServiceEntries.map(([key, entry]) => {
@@ -82,37 +308,63 @@ export const SectionForm = ({
82
308
  label: <span>{item}</span>
83
309
  }))
84
310
  : []
85
- const adapterOptions: Array<{ value: string; label: ReactNode }> = Object.keys(mergedAdapters)
86
- .map(key => ({
87
- value: key,
88
- label: <span>{key}</span>
89
- }))
311
+ const adapterOptions: Array<{ value: string; label: ReactNode }> = Object.keys(mergedAdapters).map(key => ({
312
+ value: key,
313
+ label: <span>{key}</span>
314
+ }))
90
315
 
91
- const groupedFields = fields.reduce<Record<string, FieldSpec[]>>((acc, field) => {
92
- const key = field.group ?? 'default'
93
- if (!acc[key]) acc[key] = []
94
- acc[key].push(field)
95
- return acc
96
- }, {})
97
- const orderedGroups = ['base', 'permissions', 'env', 'items', 'default'].filter(key => groupedFields[key]?.length)
98
- const notificationEventOrder = ['completed', 'failed', 'terminated', 'waiting_input']
99
-
100
- const getRecordKeyPlaceholder = (field: FieldSpec) => {
101
- if (sectionKey === 'models') return t('config.editor.newModelSelectorName')
102
- if (sectionKey === 'modelServices') return t('config.editor.newModelServiceName')
103
- if (sectionKey === 'channels') return t('config.editor.newChannelName')
104
- if (sectionKey === 'adapters') return t('config.editor.newAdapterName')
105
- if (sectionKey === 'plugins') {
106
- return t('config.editor.newPluginName')
107
- }
108
- if (sectionKey === 'mcp') return t('config.editor.newMcpServerName')
109
- if (sectionKey === 'general' && field.path.join('.') === 'env') return t('config.editor.newEnvVarName')
110
- return t('config.editor.fieldKey')
111
- }
316
+ const renderInheritedReadonlyControl = ({
317
+ fieldValue,
318
+ fieldPath,
319
+ onOverride
320
+ }: {
321
+ fieldValue: unknown
322
+ fieldPath: string[]
323
+ onOverride?: () => void
324
+ }) => (
325
+ <div className='config-view__field-readonly'>
326
+ <div className='config-view__field-readonly-value'>
327
+ <DisplayValue value={fieldValue} sectionKey={sectionKey} path={fieldPath} t={t} />
328
+ </div>
329
+ {onOverride != null && (
330
+ <Button size='small' onClick={onOverride}>
331
+ {t('config.detail.override')}
332
+ </Button>
333
+ )}
334
+ </div>
335
+ )
112
336
 
113
- const renderField = (field: FieldSpec) => {
114
- const fieldValue = getValueByPath(value, field.path)
115
- const valueToUse = fieldValue !== undefined ? fieldValue : field.defaultValue
337
+ const renderField = ({
338
+ field,
339
+ currentValue,
340
+ currentResolvedValue,
341
+ onCurrentValueChange,
342
+ keyPrefix,
343
+ readOnly = false
344
+ }: {
345
+ field: FieldSpec
346
+ currentValue: unknown
347
+ currentResolvedValue?: unknown
348
+ onCurrentValueChange: (nextValue: unknown) => void
349
+ keyPrefix: string
350
+ readOnly?: boolean
351
+ }) => {
352
+ const fieldValue = getValueByPath(currentValue, field.path)
353
+ const resolvedFieldValue = getValueByPath(currentResolvedValue, field.path)
354
+ const hasLocalValue = fieldValue !== undefined
355
+ const hasResolvedValue = resolvedFieldValue !== undefined
356
+ const inheritedOnly = !hasLocalValue && hasResolvedValue
357
+ const canInlineOverride = field.type === 'string' ||
358
+ field.type === 'multiline' ||
359
+ field.type === 'number' ||
360
+ field.type === 'boolean' ||
361
+ field.type === 'select' ||
362
+ field.type === 'shortcut'
363
+ const valueToUse = hasLocalValue
364
+ ? fieldValue
365
+ : hasResolvedValue && canInlineOverride
366
+ ? resolvedFieldValue
367
+ : field.defaultValue
116
368
  const label = field.labelKey
117
369
  ? t(field.labelKey)
118
370
  : getFieldLabel(t, sectionKey, field.path, field.path[field.path.length - 1] ?? '')
@@ -120,16 +372,48 @@ export const SectionForm = ({
120
372
  ? t(field.descriptionKey)
121
373
  : getFieldDescription(t, sectionKey, field.path)
122
374
  const icon = field.icon ?? getTypeIcon(getValueType(valueToUse))
123
-
124
375
  const handleValueChange = (nextValue: unknown) => {
125
- const nextSectionValue = setValueByPath(value, field.path, nextValue)
126
- onChange(nextSectionValue)
376
+ onCurrentValueChange(setValueByPath(currentValue, field.path, nextValue))
127
377
  }
128
378
 
129
379
  let control: ReactNode = null
130
- const isStacked = ['multiline', 'json', 'record', 'string[]'].includes(field.type)
380
+ const isStacked = ['multiline', 'json', 'record', 'string[]', 'detailCollection'].includes(field.type)
381
+ const overrideCurrentField = () => {
382
+ if (!hasResolvedValue) return
383
+ handleValueChange(resolvedFieldValue)
384
+ }
131
385
 
132
- if (field.type === 'string') {
386
+ if (readOnly) {
387
+ control = (
388
+ <DisplayValue
389
+ value={hasResolvedValue ? resolvedFieldValue : fieldValue ?? field.defaultValue}
390
+ sectionKey={sectionKey}
391
+ path={field.path}
392
+ t={t}
393
+ />
394
+ )
395
+ } else if (field.type === 'detailCollection') {
396
+ control = (
397
+ <DetailCollectionField
398
+ sectionKey={sectionKey}
399
+ field={field}
400
+ value={fieldValue}
401
+ resolvedValue={resolvedFieldValue}
402
+ onChange={(next) => handleValueChange(next)}
403
+ onOpenDetail={(route) => onOpenDetailRoute?.(route)}
404
+ mergedModelServices={mergedModelServices}
405
+ mergedAdapters={mergedAdapters}
406
+ uiSection={uiSection}
407
+ t={t}
408
+ />
409
+ )
410
+ } else if (inheritedOnly && !canInlineOverride) {
411
+ control = renderInheritedReadonlyControl({
412
+ fieldValue: resolvedFieldValue,
413
+ fieldPath: field.path,
414
+ onOverride: overrideCurrentField
415
+ })
416
+ } else if (field.type === 'string') {
133
417
  const placeholder = field.placeholderKey ? t(field.placeholderKey) : undefined
134
418
  if (field.sensitive === true) {
135
419
  control = (
@@ -204,12 +488,15 @@ export const SectionForm = ({
204
488
  const isDefaultAdapter = sectionKey === 'general' && field.path.join('.') === 'defaultAdapter'
205
489
  const isDefaultModelService = sectionKey === 'general' && field.path.join('.') === 'defaultModelService'
206
490
  const isDefaultModel = sectionKey === 'general' && field.path.join('.') === 'defaultModel'
491
+ const isWorktreeEnvironment = sectionKey === 'conversation' && field.path.join('.') === 'worktreeEnvironment'
207
492
  const options: Array<{ value: string; label: ReactNode }> = isDefaultModelService
208
493
  ? modelServiceOptions
209
494
  : isDefaultModel
210
495
  ? modelOptions
211
496
  : isDefaultAdapter
212
497
  ? adapterOptions
498
+ : isWorktreeEnvironment
499
+ ? worktreeEnvironmentOptions ?? []
213
500
  : (field.options ?? []).map(option => ({
214
501
  value: option.value,
215
502
  label: <span>{t(option.label)}</span>
@@ -220,12 +507,15 @@ export const SectionForm = ({
220
507
  options={options}
221
508
  onChange={(next) => handleValueChange(next)}
222
509
  allowClear
510
+ suffixIcon={configSelectSuffixIcon}
223
511
  disabled={isDefaultModel && modelOptions.length === 0}
224
512
  placeholder={t(
225
513
  isDefaultAdapter
226
514
  ? 'config.editor.defaultAdapterPlaceholder'
227
515
  : isDefaultModelService
228
516
  ? 'config.editor.defaultModelServicePlaceholder'
517
+ : isWorktreeEnvironment
518
+ ? 'config.editor.worktreeEnvironmentPlaceholder'
229
519
  : 'config.editor.defaultModelPlaceholder'
230
520
  )}
231
521
  />
@@ -247,16 +537,16 @@ export const SectionForm = ({
247
537
  value={recordValue}
248
538
  onChange={handleValueChange}
249
539
  t={t}
250
- keyPlaceholder={getRecordKeyPlaceholder(field)}
540
+ keyPlaceholder={getRecordKeyPlaceholder(sectionKey, field, t)}
251
541
  />
252
542
  )
253
543
  } else if (field.recordKind === 'channels') {
254
544
  control = (
255
- <ChannelRecordEditor
545
+ <RecordJsonEditor
256
546
  value={recordValue}
257
547
  onChange={handleValueChange}
258
548
  t={t}
259
- keyPlaceholder={getRecordKeyPlaceholder(field)}
549
+ keyPlaceholder={getRecordKeyPlaceholder(sectionKey, field, t)}
260
550
  />
261
551
  )
262
552
  } else if (field.recordKind === 'mcpServers') {
@@ -265,7 +555,7 @@ export const SectionForm = ({
265
555
  value={recordValue}
266
556
  onChange={handleValueChange}
267
557
  t={t}
268
- keyPlaceholder={getRecordKeyPlaceholder(field)}
558
+ keyPlaceholder={getRecordKeyPlaceholder(sectionKey, field, t)}
269
559
  />
270
560
  )
271
561
  } else if (field.recordKind === 'keyValue') {
@@ -274,7 +564,7 @@ export const SectionForm = ({
274
564
  value={recordValue as Record<string, string>}
275
565
  onChange={handleValueChange}
276
566
  t={t}
277
- keyPlaceholder={getRecordKeyPlaceholder(field)}
567
+ keyPlaceholder={getRecordKeyPlaceholder(sectionKey, field, t)}
278
568
  />
279
569
  )
280
570
  } else if (field.recordKind === 'boolean') {
@@ -283,7 +573,7 @@ export const SectionForm = ({
283
573
  value={recordValue as Record<string, boolean>}
284
574
  onChange={handleValueChange}
285
575
  t={t}
286
- keyPlaceholder={getRecordKeyPlaceholder(field)}
576
+ keyPlaceholder={getRecordKeyPlaceholder(sectionKey, field, t)}
287
577
  />
288
578
  )
289
579
  } else {
@@ -292,7 +582,7 @@ export const SectionForm = ({
292
582
  value={recordValue}
293
583
  onChange={handleValueChange}
294
584
  t={t}
295
- keyPlaceholder={getRecordKeyPlaceholder(field)}
585
+ keyPlaceholder={getRecordKeyPlaceholder(sectionKey, field, t)}
296
586
  />
297
587
  )
298
588
  }
@@ -317,14 +607,23 @@ export const SectionForm = ({
317
607
 
318
608
  if (directRecordSections.has(sectionKey) && field.type === 'record') {
319
609
  return (
320
- <div key={`${field.path.join('.')}-${field.type}-${field.recordKind ?? ''}`}>
610
+ <div key={`${keyPrefix}:${field.path.join('.')}:${field.type}:${field.recordKind ?? ''}`}>
611
+ {control}
612
+ </div>
613
+ )
614
+ }
615
+
616
+ if (directDetailSections.has(sectionKey) && field.type === 'detailCollection' && field.path.length === 0) {
617
+ return (
618
+ <div key={`${keyPrefix}:${field.path.join('.')}:${field.type}`}>
321
619
  {control}
322
620
  </div>
323
621
  )
324
622
  }
623
+
325
624
  return (
326
625
  <FieldRow
327
- key={`${field.path.join('.')}-${field.type}-${field.labelKey ?? ''}-${field.recordKind ?? ''}`}
626
+ key={`${keyPrefix}:${field.path.join('.')}:${field.type}:${field.labelKey ?? ''}:${field.recordKind ?? ''}`}
328
627
  title={label}
329
628
  description={description}
330
629
  icon={icon}
@@ -335,55 +634,91 @@ export const SectionForm = ({
335
634
  )
336
635
  }
337
636
 
338
- return (
339
- <div className='config-view__field-stack'>
340
- {orderedGroups.map((groupKey) => {
341
- const groupFields = groupedFields[groupKey] ?? []
342
- const hideEmptyGroups = new Set(['base', 'permissions'])
343
- if (hideEmptyGroups.has(groupKey)) {
344
- const hasGroupValues = groupFields.some((field) => {
345
- const fieldValue = getValueByPath(value, field.path)
346
- if (typeof fieldValue === 'boolean') return fieldValue
347
- return !isEmptyValue(fieldValue)
348
- })
349
- if (!hasGroupValues) {
350
- return null
351
- }
352
- }
353
- if (groupKey === 'default') {
354
- if (directRecordSections.has(sectionKey)) {
637
+ const renderFieldGroups = ({
638
+ currentFields,
639
+ currentValue,
640
+ currentResolvedValue,
641
+ onCurrentValueChange,
642
+ keyPrefix,
643
+ readOnly = false
644
+ }: {
645
+ currentFields: FieldSpec[]
646
+ currentValue: unknown
647
+ currentResolvedValue?: unknown
648
+ onCurrentValueChange: (nextValue: unknown) => void
649
+ keyPrefix: string
650
+ readOnly?: boolean
651
+ }) => {
652
+ const groupedFields = currentFields.reduce<Record<string, FieldSpec[]>>((acc, field) => {
653
+ const key = field.group ?? 'default'
654
+ if (!acc[key]) acc[key] = []
655
+ acc[key].push(field)
656
+ return acc
657
+ }, {})
658
+ const orderedGroups = (configGroupOrder[sectionKey] ?? defaultGroupOrder).filter(key => groupedFields[key]?.length)
659
+
660
+ return (
661
+ <div className='config-view__field-stack'>
662
+ {orderedGroups.map((groupKey) => {
663
+ const groupFields = groupedFields[groupKey] ?? []
664
+ if (groupKey === 'default') {
665
+ if (directRecordSections.has(sectionKey)) {
666
+ return (
667
+ <div key={`${keyPrefix}:${groupKey}`}>
668
+ {groupFields.map(field =>
669
+ renderField({
670
+ field,
671
+ currentValue,
672
+ currentResolvedValue,
673
+ onCurrentValueChange,
674
+ keyPrefix,
675
+ readOnly
676
+ })
677
+ )}
678
+ </div>
679
+ )
680
+ }
355
681
  return (
356
- <div key={groupKey}>
357
- {groupFields.map(renderField)}
682
+ <div key={`${keyPrefix}:${groupKey}`} className='config-view__field-list'>
683
+ {groupFields.map(field =>
684
+ renderField({
685
+ field,
686
+ currentValue,
687
+ currentResolvedValue,
688
+ onCurrentValueChange,
689
+ keyPrefix,
690
+ readOnly
691
+ })
692
+ )}
358
693
  </div>
359
694
  )
360
695
  }
361
- return (
362
- <div key={groupKey} className='config-view__field-list'>
363
- {groupFields.map(renderField)}
364
- </div>
365
- )
366
- }
367
- const groupLabel = (() => {
368
- const labelKey = configGroupMeta[sectionKey]?.[groupKey]?.labelKey
369
- if (labelKey) return t(labelKey)
370
- return groupKey === 'base'
371
- ? t('config.sectionGroups.base')
372
- : groupKey === 'permissions'
373
- ? t('config.sectionGroups.permissions')
374
- : groupKey === 'env'
375
- ? t('config.sectionGroups.env')
376
- : sectionKey === 'plugins'
377
- ? t('config.sectionGroups.plugins')
378
- : t('config.sectionGroups.items')
379
- })()
380
- const visibleFields = groupFields.filter(field => field.hidden !== true)
381
- const collapseFields = visibleFields.filter(field => field.collapse != null)
382
- const nonCollapseFields = visibleFields.filter(field => field.collapse == null)
383
- const collapseGroups = collapseFields.reduce<
384
- Map<string, { meta: NonNullable<FieldSpec['collapse']>; fields: FieldSpec[] }>
385
- >(
386
- (acc, field) => {
696
+
697
+ const groupMeta = configGroupMeta[sectionKey]?.[groupKey]
698
+ const groupLabel = (() => {
699
+ const labelKey = groupMeta?.labelKey
700
+ if (labelKey) return t(labelKey)
701
+ return groupKey === 'base'
702
+ ? t('config.sectionGroups.base')
703
+ : groupKey === 'models'
704
+ ? t('config.sectionGroups.models')
705
+ : groupKey === 'advanced'
706
+ ? t('config.sectionGroups.advanced')
707
+ : groupKey === 'permissions'
708
+ ? t('config.sectionGroups.permissions')
709
+ : groupKey === 'env'
710
+ ? t('config.sectionGroups.env')
711
+ : sectionKey === 'plugins'
712
+ ? t('config.sectionGroups.plugins')
713
+ : t('config.sectionGroups.items')
714
+ })()
715
+
716
+ const visibleFields = groupFields.filter(field => field.hidden !== true)
717
+ const collapseFields = visibleFields.filter(field => field.collapse != null)
718
+ const nonCollapseFields = visibleFields.filter(field => field.collapse == null)
719
+ const collapseGroups = collapseFields.reduce<
720
+ Map<string, { meta: NonNullable<FieldSpec['collapse']>; fields: FieldSpec[] }>
721
+ >((acc, field) => {
387
722
  const meta = field.collapse
388
723
  if (!meta) return acc
389
724
  const existing = acc.get(meta.key)
@@ -393,51 +728,64 @@ export const SectionForm = ({
393
728
  acc.set(meta.key, { meta, fields: [field] })
394
729
  }
395
730
  return acc
396
- },
397
- new Map()
398
- )
399
- const collapseItems = Array.from(collapseGroups.values()).map((group) => ({
400
- key: group.meta.key,
401
- collapsible: 'header' as const,
402
- label: (
403
- <div className='config-view__collapse-header'>
404
- <div className='config-view__collapse-header-main'>
405
- <div className='config-view__collapse-title'>
406
- {t(group.meta.labelKey)}
407
- </div>
408
- {group.meta.descKey && (
409
- <div className='config-view__collapse-desc'>
410
- {t(group.meta.descKey)}
731
+ }, new Map())
732
+ const collapseItems = Array.from(collapseGroups.values()).map(group => ({
733
+ key: group.meta.key,
734
+ collapsible: 'header' as const,
735
+ label: (
736
+ <div className='config-view__collapse-header'>
737
+ <div className='config-view__collapse-header-main'>
738
+ <div className='config-view__collapse-title'>
739
+ {t(group.meta.labelKey)}
411
740
  </div>
741
+ {group.meta.descKey && (
742
+ <div className='config-view__collapse-desc'>
743
+ {t(group.meta.descKey)}
744
+ </div>
745
+ )}
746
+ </div>
747
+ {group.meta.togglePath && (
748
+ <Switch
749
+ checked={!getValueByPath(currentValue, group.meta.togglePath)}
750
+ onChange={(next) => {
751
+ onCurrentValueChange(setValueByPath(currentValue, group.meta.togglePath!, !next))
752
+ }}
753
+ disabled={readOnly}
754
+ onClick={(_, event) => {
755
+ event.stopPropagation()
756
+ }}
757
+ />
412
758
  )}
413
759
  </div>
414
- {group.meta.togglePath && (
415
- <Switch
416
- checked={!getValueByPath(value, group.meta.togglePath)}
417
- onChange={(next) => {
418
- const nextValue = setValueByPath(value, group.meta.togglePath!, !next)
419
- onChange(nextValue)
420
- }}
421
- onClick={(_, event) => {
422
- event.stopPropagation()
423
- }}
424
- />
425
- )}
426
- </div>
427
- ),
428
- children: (
429
- <div className='config-view__field-list'>
430
- {group.fields.map(renderField)}
431
- </div>
432
- )
433
- }))
434
- return (
435
- <div key={groupKey} className='config-view__subsection'>
436
- <div className='config-view__subsection-title'>
437
- {groupLabel}
438
- </div>
760
+ ),
761
+ children: (
762
+ <div className='config-view__field-list'>
763
+ {group.fields.map(field =>
764
+ renderField({
765
+ field,
766
+ currentValue,
767
+ currentResolvedValue,
768
+ onCurrentValueChange,
769
+ keyPrefix,
770
+ readOnly
771
+ })
772
+ )}
773
+ </div>
774
+ )
775
+ }))
776
+
777
+ const groupBody = (
439
778
  <div className='config-view__subsection-body'>
440
- {nonCollapseFields.map(renderField)}
779
+ {nonCollapseFields.map(field =>
780
+ renderField({
781
+ field,
782
+ currentValue,
783
+ currentResolvedValue,
784
+ onCurrentValueChange,
785
+ keyPrefix,
786
+ readOnly
787
+ })
788
+ )}
441
789
  {collapseItems.length > 0 && (
442
790
  <Collapse
443
791
  className='config-view__collapse-group config-view__field-row'
@@ -446,9 +794,471 @@ export const SectionForm = ({
446
794
  />
447
795
  )}
448
796
  </div>
797
+ )
798
+
799
+ if (groupMeta?.collapsible === true) {
800
+ return (
801
+ <Collapse
802
+ key={`${keyPrefix}:${groupKey}`}
803
+ className='config-view__subsection-collapse'
804
+ ghost
805
+ expandIconPosition='end'
806
+ expandIcon={({ isActive }) => (
807
+ <span
808
+ className={`material-symbols-rounded config-view__subsection-expand-icon${
809
+ isActive ? ' is-active' : ''
810
+ }`}
811
+ >
812
+ chevron_right
813
+ </span>
814
+ )}
815
+ defaultActiveKey={groupMeta.defaultExpanded === false ? [] : [groupKey]}
816
+ items={[
817
+ {
818
+ key: groupKey,
819
+ label: <div className='config-view__subsection-title'>{groupLabel}</div>,
820
+ children: groupBody
821
+ }
822
+ ]}
823
+ />
824
+ )
825
+ }
826
+
827
+ return (
828
+ <div key={`${keyPrefix}:${groupKey}`} className='config-view__subsection'>
829
+ <div className='config-view__subsection-title'>
830
+ {groupLabel}
831
+ </div>
832
+ {groupBody}
833
+ </div>
834
+ )
835
+ })}
836
+ </div>
837
+ )
838
+ }
839
+
840
+ const detailMeta = resolveConfigDetailRouteMeta({
841
+ sectionKey,
842
+ fields,
843
+ value,
844
+ resolvedValue,
845
+ route: detailRoute,
846
+ detailContext,
847
+ t
848
+ })
849
+ const adapterDetailKey = (
850
+ sectionKey === 'adapters' &&
851
+ uiSection?.kind === 'recordMap' &&
852
+ detailMeta?.field.path.length === 0
853
+ )
854
+ ? detailMeta.itemKey
855
+ : null
856
+ const { data: adapterAccountsData } = useSWR(
857
+ adapterDetailKey != null ? `/api/adapters/${adapterDetailKey}/accounts` : null,
858
+ () => getAdapterAccounts(adapterDetailKey!)
859
+ )
860
+ const adapterDefaultAccountOptions = useMemo(() => {
861
+ if (adapterDetailKey == null || !isRecord(detailMeta?.item)) return undefined
862
+
863
+ const configuredDefaultAccount =
864
+ typeof detailMeta.item.defaultAccount === 'string' && detailMeta.item.defaultAccount.trim() !== ''
865
+ ? detailMeta.item.defaultAccount.trim()
866
+ : undefined
867
+ const configuredAccountsValue = getValueByPath(detailMeta.item, ['accounts'])
868
+ const configuredAccounts = isRecord(configuredAccountsValue) ? { ...configuredAccountsValue } : {}
869
+
870
+ if (configuredDefaultAccount != null && configuredAccounts[configuredDefaultAccount] == null) {
871
+ configuredAccounts[configuredDefaultAccount] = { title: configuredDefaultAccount }
872
+ }
873
+
874
+ return mergeAccounts(configuredAccounts, adapterAccountsData?.accounts ?? [], configuredDefaultAccount)
875
+ .map((account) => ({
876
+ value: account.key,
877
+ label: account.title === account.key ? account.key : `${account.title} (${account.key})`
878
+ }))
879
+ }, [adapterAccountsData?.accounts, adapterDetailKey, detailMeta?.item])
880
+
881
+ if (detailMeta != null) {
882
+ const detailCollection = detailMeta.field.detailCollection
883
+ const canOverrideInheritedDetailItem = detailCollection != null &&
884
+ (
885
+ detailCollection.collectionKind !== 'list' ||
886
+ detailCollection.getMergeKey != null
887
+ )
888
+ const writeDetailItem = (nextItem: unknown) => {
889
+ if (detailMeta.field.type !== 'detailCollection' || detailMeta.field.detailCollection == null) return
890
+ const resolvedNextItem = (
891
+ nextItem != null &&
892
+ typeof nextItem === 'object' &&
893
+ !Array.isArray(nextItem)
894
+ )
895
+ ? nextItem as Record<string, unknown>
896
+ : {}
897
+
898
+ if (detailMeta.field.detailCollection.collectionKind === 'list') {
899
+ const currentListValue = getValueByPath(value, detailMeta.field.path)
900
+ const currentItems = Array.isArray(currentListValue)
901
+ ? currentListValue.filter((item): item is Record<string, unknown> => (
902
+ item != null &&
903
+ typeof item === 'object' &&
904
+ !Array.isArray(item)
905
+ ))
906
+ : []
907
+ const nextItems = [...currentItems]
908
+ if (detailMeta.localItemIndex == null) {
909
+ nextItems.push(resolvedNextItem)
910
+ } else {
911
+ nextItems[detailMeta.localItemIndex] = resolvedNextItem
912
+ }
913
+ onChange(setValueByPath(value, detailMeta.field.path, nextItems))
914
+ return
915
+ }
916
+
917
+ onChange(setValueByPath(value, [...detailMeta.field.path, detailMeta.itemKey], resolvedNextItem))
918
+ }
919
+ const overrideDetailItem = () => {
920
+ if (!canOverrideInheritedDetailItem) return
921
+ writeDetailItem(detailMeta.resolvedItem)
922
+ }
923
+ const detailNotice = detailMeta.itemSource === 'inherited'
924
+ ? (
925
+ <div className='config-view__detail-notice'>
926
+ <div className='config-view__detail-notice-text'>
927
+ {canOverrideInheritedDetailItem
928
+ ? t('config.detail.inheritedReadonly')
929
+ : t('config.detail.inheritedAppendOnly')}
930
+ </div>
931
+ {canOverrideInheritedDetailItem && (
932
+ <Button size='small' type='primary' onClick={overrideDetailItem}>
933
+ {t('config.detail.override')}
934
+ </Button>
935
+ )}
936
+ </div>
937
+ )
938
+ : detailMeta.hasResolvedOverlay
939
+ ? (
940
+ <div className='config-view__detail-notice'>
941
+ <div className='config-view__detail-notice-text'>
942
+ {t('config.detail.partialOverride')}
943
+ </div>
944
+ </div>
945
+ )
946
+ : null
947
+
948
+ if (detailMeta.itemSource === 'inherited') {
949
+ if ((detailMeta.field.detailCollection?.itemFields?.length ?? 0) > 0) {
950
+ return (
951
+ <div className='config-view__detail-panel'>
952
+ {detailNotice}
953
+ {renderFieldGroups({
954
+ currentFields: detailMeta.field.detailCollection!.itemFields!,
955
+ currentValue: undefined,
956
+ currentResolvedValue: detailMeta.resolvedItem,
957
+ onCurrentValueChange: () => undefined,
958
+ keyPrefix: `detail:${detailMeta.field.path.join('.')}:${detailMeta.itemKey}`,
959
+ readOnly: true
960
+ })}
449
961
  </div>
450
962
  )
451
- })}
452
- </div>
453
- )
963
+ }
964
+
965
+ return (
966
+ <div className='config-view__detail-panel'>
967
+ {detailNotice}
968
+ <DisplayValue value={detailMeta.resolvedItem} sectionKey={sectionKey} path={detailMeta.field.path} t={t} />
969
+ </div>
970
+ )
971
+ }
972
+
973
+ if (detailMeta.field.detailCollection?.detailKind === 'recommendedModels') {
974
+ return (
975
+ <div className='config-view__detail-panel'>
976
+ {detailNotice}
977
+ <RecommendedModelsItemEditor
978
+ value={detailMeta.item}
979
+ onChange={writeDetailItem}
980
+ mergedModelServices={mergedModelServices}
981
+ t={t}
982
+ />
983
+ </div>
984
+ )
985
+ }
986
+
987
+ if (detailMeta.field.detailCollection?.detailKind === 'mcpServer') {
988
+ return (
989
+ <div className='config-view__detail-panel'>
990
+ {detailNotice}
991
+ <McpServerItemEditor
992
+ value={detailMeta.item}
993
+ onChange={writeDetailItem}
994
+ t={t}
995
+ />
996
+ </div>
997
+ )
998
+ }
999
+
1000
+ if (uiSection?.kind === 'recordMap' && detailMeta.field.path.length === 0) {
1001
+ const { itemSchema, isKnownEntry } = resolveConfigUiRecordEntry({
1002
+ schema: uiSection.recordMap,
1003
+ entryKey: detailMeta.itemKey,
1004
+ entryValue: detailMeta.item
1005
+ })
1006
+ const shouldRenderJsonFallback = !isKnownEntry && uiSection.recordMap.unknownEditor === 'json'
1007
+ const discriminatorField = uiSection.recordMap.discriminatorField ?? 'type'
1008
+
1009
+ if (shouldRenderJsonFallback) {
1010
+ return (
1011
+ <div className='config-view__detail-panel'>
1012
+ {detailNotice}
1013
+ <ComplexTextEditor
1014
+ value={detailMeta.item}
1015
+ onChange={(next) => writeDetailItem((next ?? {}) as Record<string, unknown>)}
1016
+ />
1017
+ </div>
1018
+ )
1019
+ }
1020
+
1021
+ if (itemSchema != null) {
1022
+ if (sectionKey === 'adapters') {
1023
+ const accountItemSchema = itemSchema.recordFields?.accounts?.itemSchema
1024
+ const isAccountsNestedRoute = detailRoute?.nestedPath?.[0] === 'accounts'
1025
+ const hiddenFieldPaths = [
1026
+ ...(isKnownEntry && uiSection.recordMap.mode === 'discriminated' ? [[discriminatorField]] : []),
1027
+ ['accounts']
1028
+ ]
1029
+ const visibleAdapterFields = itemSchema.fields.filter(field =>
1030
+ !hiddenFieldPaths.some(hiddenPath => (
1031
+ field.path.length === hiddenPath.length &&
1032
+ field.path.every((segment, index) => segment === hiddenPath[index])
1033
+ ))
1034
+ )
1035
+ const defaultAccountFieldPaths = visibleAdapterFields
1036
+ .filter(field => field.path.length === 1 && field.path[0] === 'defaultAccount')
1037
+ .map(field => field.path)
1038
+ const advancedFields = visibleAdapterFields
1039
+ .filter(field => (
1040
+ (field.path.length === 1 && (
1041
+ field.path[0] === 'includeModels' ||
1042
+ field.path[0] === 'excludeModels' ||
1043
+ field.path[0] === 'experimentalApi' ||
1044
+ field.path[0] === 'maxOutputTokens'
1045
+ )) ||
1046
+ field.type === 'json' ||
1047
+ field.type === 'multiline' ||
1048
+ field.type === 'string[]'
1049
+ ))
1050
+ const primaryFieldPaths = visibleAdapterFields
1051
+ .filter(field =>
1052
+ !defaultAccountFieldPaths.some(path => (
1053
+ field.path.length === path.length &&
1054
+ field.path.every((segment, index) => segment === path[index])
1055
+ ))
1056
+ )
1057
+ .filter(field =>
1058
+ !advancedFields.some(advancedField => (
1059
+ field.path.length === advancedField.path.length &&
1060
+ field.path.every((segment, index) => segment === advancedField.path[index])
1061
+ ))
1062
+ )
1063
+ .map(field => field.path)
1064
+ const modelSection = {
1065
+ title: t('config.sectionGroups.models'),
1066
+ visibleFieldPaths: advancedFields
1067
+ .filter(field => (
1068
+ isTopLevelField(field.path, 'includeModels') ||
1069
+ isTopLevelField(field.path, 'excludeModels')
1070
+ ))
1071
+ .map(field => field.path)
1072
+ }
1073
+ const advancedSections = [
1074
+ {
1075
+ key: 'runtime',
1076
+ title: t('config.sectionGroups.advancedRuntime'),
1077
+ visibleFieldPaths: advancedFields
1078
+ .filter(field => (
1079
+ isTopLevelField(field.path, 'experimentalApi') ||
1080
+ isTopLevelField(field.path, 'maxOutputTokens')
1081
+ ))
1082
+ .map(field => field.path)
1083
+ },
1084
+ {
1085
+ key: 'sandbox',
1086
+ title: t('config.sectionGroups.advancedSandbox'),
1087
+ visibleFieldPaths: advancedFields
1088
+ .filter(field => isTopLevelField(field.path, 'sandboxPolicy'))
1089
+ .map(field => field.path)
1090
+ },
1091
+ {
1092
+ key: 'client',
1093
+ title: t('config.sectionGroups.advancedClient'),
1094
+ visibleFieldPaths: advancedFields
1095
+ .filter(field => isTopLevelField(field.path, 'clientInfo'))
1096
+ .map(field => field.path)
1097
+ },
1098
+ {
1099
+ key: 'overrides',
1100
+ title: t('config.sectionGroups.advancedOverrides'),
1101
+ visibleFieldPaths: advancedFields
1102
+ .filter(field => (
1103
+ isTopLevelField(field.path, 'configOverrides') ||
1104
+ isTopLevelField(field.path, 'features')
1105
+ ))
1106
+ .map(field => field.path)
1107
+ },
1108
+ {
1109
+ key: 'misc',
1110
+ title: t('config.sectionGroups.advancedMisc'),
1111
+ visibleFieldPaths: advancedFields
1112
+ .filter(field =>
1113
+ !(
1114
+ isTopLevelField(field.path, 'includeModels') ||
1115
+ isTopLevelField(field.path, 'excludeModels') ||
1116
+ isTopLevelField(field.path, 'experimentalApi') ||
1117
+ isTopLevelField(field.path, 'maxOutputTokens') ||
1118
+ isTopLevelField(field.path, 'sandboxPolicy') ||
1119
+ isTopLevelField(field.path, 'clientInfo') ||
1120
+ isTopLevelField(field.path, 'configOverrides') ||
1121
+ isTopLevelField(field.path, 'features')
1122
+ )
1123
+ )
1124
+ .map(field => field.path)
1125
+ }
1126
+ ]
1127
+
1128
+ if (isAccountsNestedRoute) {
1129
+ return (
1130
+ <div className='config-view__detail-panel'>
1131
+ {detailNotice}
1132
+ <AdapterAccountsManager
1133
+ adapterKey={detailMeta.itemKey}
1134
+ value={detailMeta.item}
1135
+ accountItemSchema={accountItemSchema}
1136
+ onChange={writeDetailItem}
1137
+ nestedPath={detailRoute?.nestedPath}
1138
+ onOpenNestedPath={(nextPath) => {
1139
+ onOpenDetailRoute?.({
1140
+ kind: detailRoute?.kind ?? 'detailCollectionItem',
1141
+ fieldPath: detailMeta.field.path,
1142
+ itemKey: detailMeta.itemKey,
1143
+ nestedPath: nextPath
1144
+ })
1145
+ }}
1146
+ t={t}
1147
+ />
1148
+ </div>
1149
+ )
1150
+ }
1151
+
1152
+ return (
1153
+ <div className='config-view__detail-panel'>
1154
+ {detailNotice}
1155
+ <div className='config-view__field-stack'>
1156
+ {renderAdapterSchemaSection({
1157
+ schema: itemSchema,
1158
+ currentValue: detailMeta.item,
1159
+ onCurrentValueChange: writeDetailItem,
1160
+ visibleFieldPaths: primaryFieldPaths,
1161
+ title: t('config.sectionGroups.base'),
1162
+ collapsible: true,
1163
+ defaultExpanded: true,
1164
+ collapseKey: 'base'
1165
+ })}
1166
+ {renderAdapterSchemaSection({
1167
+ schema: itemSchema,
1168
+ currentValue: detailMeta.item,
1169
+ onCurrentValueChange: writeDetailItem,
1170
+ visibleFieldPaths: defaultAccountFieldPaths,
1171
+ title: undefined,
1172
+ resolveFieldOptions: (field) => (
1173
+ field.path.length === 1 && field.path[0] === 'defaultAccount'
1174
+ ? adapterDefaultAccountOptions ?? []
1175
+ : undefined
1176
+ )
1177
+ })}
1178
+ <AdapterAccountsManager
1179
+ adapterKey={detailMeta.itemKey}
1180
+ value={detailMeta.item}
1181
+ accountItemSchema={accountItemSchema}
1182
+ onChange={writeDetailItem}
1183
+ nestedPath={detailRoute?.nestedPath}
1184
+ onOpenNestedPath={(nextPath) => {
1185
+ onOpenDetailRoute?.({
1186
+ kind: detailRoute?.kind ?? 'detailCollectionItem',
1187
+ fieldPath: detailMeta.field.path,
1188
+ itemKey: detailMeta.itemKey,
1189
+ nestedPath: nextPath
1190
+ })
1191
+ }}
1192
+ t={t}
1193
+ />
1194
+ {renderAdapterAdvancedSections({
1195
+ schema: itemSchema,
1196
+ currentValue: detailMeta.item,
1197
+ onCurrentValueChange: writeDetailItem,
1198
+ modelSection,
1199
+ advancedSections
1200
+ })}
1201
+ </div>
1202
+ </div>
1203
+ )
1204
+ }
1205
+
1206
+ return (
1207
+ <div className='config-view__detail-panel'>
1208
+ {detailNotice}
1209
+ <SchemaObjectEditor
1210
+ value={detailMeta.item}
1211
+ schema={itemSchema}
1212
+ onChange={writeDetailItem}
1213
+ t={t}
1214
+ hideFieldPaths={isKnownEntry && uiSection.recordMap.mode === 'discriminated'
1215
+ ? [[discriminatorField]]
1216
+ : undefined}
1217
+ />
1218
+ </div>
1219
+ )
1220
+ }
1221
+ }
1222
+
1223
+ if ((detailMeta.field.detailCollection?.itemFields?.length ?? 0) > 0) {
1224
+ return (
1225
+ <div className='config-view__detail-panel'>
1226
+ {detailNotice}
1227
+ {renderFieldGroups({
1228
+ currentFields: detailMeta.field.detailCollection!.itemFields!,
1229
+ currentValue: detailMeta.item,
1230
+ currentResolvedValue: detailMeta.resolvedItem,
1231
+ onCurrentValueChange: writeDetailItem,
1232
+ keyPrefix: `detail:${detailMeta.field.path.join('.')}:${detailMeta.itemKey}`
1233
+ })}
1234
+ </div>
1235
+ )
1236
+ }
1237
+
1238
+ return (
1239
+ <div className='config-view__detail-panel'>
1240
+ {detailNotice}
1241
+ <Empty description={t('common.noData')} image={null} />
1242
+ </div>
1243
+ )
1244
+ }
1245
+
1246
+ return renderFieldGroups({
1247
+ currentFields: fields,
1248
+ currentValue: value,
1249
+ currentResolvedValue: resolvedValue,
1250
+ onCurrentValueChange: onChange,
1251
+ keyPrefix: sectionKey
1252
+ })
1253
+ }
1254
+
1255
+ const getRecordKeyPlaceholder = (sectionKey: string, field: FieldSpec, t: TranslationFn) => {
1256
+ if (sectionKey === 'models') return t('config.editor.newModelSelectorName')
1257
+ if (sectionKey === 'modelServices') return t('config.editor.newModelServiceName')
1258
+ if (sectionKey === 'channels') return t('config.editor.newChannelName')
1259
+ if (sectionKey === 'adapters') return t('config.editor.newAdapterName')
1260
+ if (sectionKey === 'plugins') return t('config.editor.newPluginName')
1261
+ if (sectionKey === 'mcp') return t('config.editor.newMcpServerName')
1262
+ if (sectionKey === 'general' && field.path.join('.') === 'env') return t('config.editor.newEnvVarName')
1263
+ return t('config.editor.fieldKey')
454
1264
  }