@yancyyu/openhermit 1.6.42 → 1.6.43

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 (281) hide show
  1. package/README.md +98 -89
  2. package/bin/hermit.mjs +96 -0
  3. package/dist-renderer/assets/{ProjectEditorOverlay-DlFQ6mai.js → ProjectEditorOverlay-C98qSs7-.js} +1 -1
  4. package/dist-renderer/assets/{TeamGraphOverlay-D2TPMPGR.js → TeamGraphOverlay-CsBbZwcL.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-Cmd0RHLQ.js → _basePickBy-ZOyLWjMK.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-BI_iy8ea.js → _baseUniq-DBb726rt.js} +1 -1
  7. package/dist-renderer/assets/{arc-NzW2mjTP.js → arc-CdiTaR_R.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Bzq85AYv.js → architectureDiagram-VXUJARFQ-Cz3sc5TH.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-D1PvYS-b.js → blockDiagram-VD42YOAC-DE4c-KJ3.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-D49RKzPC.js → c4Diagram-YG6GDRKO-CmTMDTrV.js} +1 -1
  11. package/dist-renderer/assets/channel-KTpqi9eT.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-fmI_MQmQ.js → chunk-4BX2VUAB-rhHy3tFl.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-Xsv9RCXZ.js → chunk-55IACEB6-fLZBzuo_.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-BE1KO8Um.js → chunk-B4BG7PRW-DOzxQhim.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-tqJ7Mv7f.js → chunk-DI55MBZ5-COQCcXC5.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-DMD45MVJ.js → chunk-FMBD7UC4-IKU9U_Y4.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-DOhGrz-q.js → chunk-QN33PNHL-D6WV154X.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-D8yDgJdD.js → chunk-QZHKN3VN-D90_2DQp.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-BcsEDu7A.js → chunk-TZMSLE5B-BQEil57G.js} +1 -1
  20. package/dist-renderer/assets/classDiagram-2ON5EDUG-lpzulY5X.js +1 -0
  21. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-lpzulY5X.js +1 -0
  22. package/dist-renderer/assets/clone-CriGymY9.js +1 -0
  23. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DlSqGHMX.js → cose-bilkent-S5V4N54A-6WiK6U2P.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-BTT9tSAx.js → dagre-6UL2VRFP-DF4MMuTn.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-Du-U-mK2.js → diagram-PSM6KHXK-CcF1eZ7E.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-jFdHeKas.js → diagram-QEK2KX5R-DYlOVPQB.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-DKLNK2bu.js → diagram-S2PKOQOG-BHXWsZOP.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CZxHgIIo.js → erDiagram-Q2GNP2WA-GjmuBx8d.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-v4XStCD0.js → flowDiagram-NV44I4VS-BuS7YVHk.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DJjD_BEL.js → ganttDiagram-JELNMOA3-3Teu5tAa.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-BNy-jr03.js → gitGraphDiagram-V2S2FVAM-BiLdCYu5.js} +1 -1
  32. package/dist-renderer/assets/{graph-DDTrn6je.js → graph-CDP_R8ct.js} +1 -1
  33. package/dist-renderer/assets/{index-BBp78BAu.js → index-BSZdT-g-.js} +1 -1
  34. package/dist-renderer/assets/{index-eotrJaYy.js → index-BhWvMqsz.js} +1 -1
  35. package/dist-renderer/assets/{index-D8_B-cfs.js → index-C2_AupSj.js} +1 -1
  36. package/dist-renderer/assets/{index-BQrwHZ-k.js → index-C5ujiwAR.js} +580 -588
  37. package/dist-renderer/assets/index-CIS2CTK9.css +1 -0
  38. package/dist-renderer/assets/{index-CRKQSG9S.js → index-CVNjLwkq.js} +1 -1
  39. package/dist-renderer/assets/{index-DR6Wz52b.js → index-CwG3se0q.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DqnOsuza.js → infoDiagram-HS3SLOUP-DLHUFo72.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DTobaO1d.js → journeyDiagram-XKPGCS4Q-BE07RpJD.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-HbwVOvWc.js → kanban-definition-3W4ZIXB7-DDHZy4NB.js} +1 -1
  43. package/dist-renderer/assets/{layout--VYmTcw2.js → layout-5nA5wUxO.js} +1 -1
  44. package/dist-renderer/assets/{linear-BsJh89Mr.js → linear-BtF1i2qN.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-BZqUZePd.js → mindmap-definition-VGOIOE7T-Z1Ui9Sqy.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-B1q_nH6P.js → pieDiagram-ADFJNKIX-LCjxckWv.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-UD8QhSEu.js → quadrantDiagram-AYHSOK5B-BOwKjSco.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-BA_i7Nw8.js → requirementDiagram-UZGBJVZJ-pChP8Znd.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CMTnX-2d.js → sankeyDiagram-TZEHDZUN-DifZ2qpo.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BQXDB615.js → sequenceDiagram-WL72ISMW-CJg-WYyY.js} +1 -1
  51. package/dist-renderer/assets/{splashScene-D0YB9uxm.js → splashScene-94xWCzLA.js} +1 -1
  52. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BAsPXy6X.js → stateDiagram-FKZM4ZOC-DWHOoFdv.js} +1 -1
  53. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-CGYZOoMb.js +1 -0
  54. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BdasmVkC.js → timeline-definition-IT6M3QCI-CPgokIo8.js} +1 -1
  55. package/dist-renderer/assets/{treemap-GDKQZRPO-BkKQqIui.js → treemap-GDKQZRPO-DAVqSR9L.js} +1 -1
  56. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-EAlPHOdx.js → xychartDiagram-PRI3JC2R-CCOcGbrD.js} +1 -1
  57. package/dist-renderer/chat-community-qr.jpg +0 -0
  58. package/dist-renderer/fonts/Agave-Bold.ttf +0 -0
  59. package/dist-renderer/fonts/Agave-Regular.ttf +0 -0
  60. package/dist-renderer/icon.png +0 -0
  61. package/dist-renderer/icon.rar +0 -0
  62. package/dist-renderer/index.html +3 -3
  63. package/package.json +21 -26
  64. package/src/features/worker-society/core/application/WorkerSocietyService.test.ts +802 -0
  65. package/src/features/worker-society/core/application/WorkerSocietyService.ts +428 -0
  66. package/src/features/worker-society/core/application/fakes.ts +101 -0
  67. package/src/features/worker-society/core/application/ports.ts +70 -0
  68. package/src/features/worker-society/core/domain/models/society.ts +141 -0
  69. package/src/features/worker-society/core/domain/policies/societyPolicies.test.ts +739 -0
  70. package/src/features/worker-society/core/domain/policies/societyPolicies.ts +496 -0
  71. package/src/features/worker-society/main/adapters/input/societyMcp.test.ts +317 -0
  72. package/src/features/worker-society/main/adapters/input/societyMcp.ts +257 -0
  73. package/src/features/worker-society/main/adapters/input/societyRoutes.test.ts +695 -0
  74. package/src/features/worker-society/main/adapters/input/societyRoutes.ts +194 -0
  75. package/src/features/worker-society/main/composition/societyComposition.test.ts +74 -0
  76. package/src/features/worker-society/main/composition/societyComposition.ts +70 -0
  77. package/src/features/worker-society/main/composition/workerSocietyPlugin.test.ts +69 -0
  78. package/src/features/worker-society/main/composition/workerSocietyPlugin.ts +67 -0
  79. package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.test.ts +132 -0
  80. package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.ts +84 -0
  81. package/src/features/worker-society/main/infrastructure/fsStores.test.ts +216 -0
  82. package/src/features/worker-society/main/infrastructure/fsStores.ts +113 -0
  83. package/src/features/worker-society/main/infrastructure/mergingProfileStore.test.ts +195 -0
  84. package/src/features/worker-society/main/infrastructure/mergingProfileStore.ts +96 -0
  85. package/src/features/worker-society/renderer/SocietyGraph.tsx +166 -0
  86. package/src/features/worker-society/renderer/SocietyNodeLabels.tsx +139 -0
  87. package/src/features/worker-society/renderer/SocietyNodeOverlay.tsx +339 -0
  88. package/src/features/worker-society/renderer/SocietyView.tsx +437 -0
  89. package/src/features/worker-society/renderer/index.ts +11 -0
  90. package/src/features/worker-society/renderer/societyApi.test.ts +259 -0
  91. package/src/features/worker-society/renderer/societyApi.ts +144 -0
  92. package/src/features/worker-society/renderer/societyGraphAdapter.test.ts +321 -0
  93. package/src/features/worker-society/renderer/societyGraphAdapter.ts +240 -0
  94. package/src/features/worker-society/renderer/societyOverlayActions.test.ts +57 -0
  95. package/src/features/worker-society/renderer/societyOverlayActions.ts +49 -0
  96. package/src/features/worker-society/renderer/societyStore.test.ts +218 -0
  97. package/src/features/worker-society/renderer/societyStore.ts +146 -0
  98. package/src/features/worker-society/renderer/societyViewUtils.test.ts +81 -0
  99. package/src/features/worker-society/renderer/societyViewUtils.ts +68 -0
  100. package/src/main/ipc/extensions.ts +27 -0
  101. package/src/main/server.ts +1709 -534
  102. package/src/main/services/ccConnect/CcConnectBridge.ts +26 -11
  103. package/src/main/services/ccConnect/CcConnectClient.ts +9 -2
  104. package/src/main/services/ccConnect/workDirReconcile.test.ts +57 -0
  105. package/src/main/services/ccConnect/workDirReconcile.ts +36 -0
  106. package/src/main/services/direct-cli/DirectCliSessionManager.test.ts +397 -0
  107. package/src/main/services/direct-cli/DirectCliSessionManager.ts +508 -0
  108. package/src/main/services/direct-cli/DirectCliSessionStore.test.ts +79 -0
  109. package/src/main/services/direct-cli/DirectCliSessionStore.ts +97 -0
  110. package/src/main/services/direct-cli/__tests__/directCliMessageId.test.ts +40 -0
  111. package/src/main/services/direct-cli/directCliMessageId.ts +21 -0
  112. package/src/main/services/direct-cli/index.ts +17 -0
  113. package/src/main/services/extensions/capability-packs/CapabilityPackLoaderService.ts +637 -0
  114. package/src/main/services/extensions/catalog/PluginCatalogService.ts +2 -2
  115. package/src/main/services/loop-assets/LoopAssetsScannerService.ts +657 -0
  116. package/src/main/services/runtime/providerAwareCliEnv.ts +33 -5
  117. package/src/main/services/session-intelligence/LocalSessionScanner.ts +156 -71
  118. package/src/main/services/session-intelligence/SessionUsageParser.ts +103 -8
  119. package/src/main/services/session-intelligence/UsageTelemetryService.ts +11 -0
  120. package/src/main/services/session-intelligence/__tests__/teamSessionListMapper.test.ts +104 -0
  121. package/src/main/services/session-intelligence/teamSessionListMapper.ts +78 -0
  122. package/src/main/services/system-manager/AdminLoopInitializer.ts +95 -0
  123. package/src/main/services/system-manager/BuiltinWorkflowSeeder.ts +679 -74
  124. package/src/main/services/system-manager/SystemManagerConfigService.ts +19 -1
  125. package/src/main/services/system-manager/WorkflowPromptService.ts +58 -5
  126. package/src/main/services/system-manager/__tests__/AdminLoopInitializer.test.ts +129 -0
  127. package/src/main/services/system-manager/__tests__/SystemManagerConfigService.test.ts +60 -0
  128. package/src/main/services/teams-mvp/CollaborationBoardService.ts +2 -0
  129. package/src/main/services/teams-mvp/OpsRunbookContext.ts +60 -0
  130. package/src/main/services/teams-mvp/TaskDispatchService.test.ts +305 -0
  131. package/src/main/services/teams-mvp/TaskDispatchService.ts +250 -131
  132. package/src/main/services/teams-mvp/TeamProvisioningService.ts +12 -2
  133. package/src/main/services/teams-mvp/TeamWorkspaceService.test.ts +207 -0
  134. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +104 -51
  135. package/src/main/services/teams-mvp/index.ts +6 -0
  136. package/src/main/utils/externalPlatformSessionRouting.ts +92 -0
  137. package/src/main/utils/toolApprovalRules.ts +151 -0
  138. package/src/renderer/App.tsx +24 -89
  139. package/src/renderer/api/httpClient.ts +115 -37
  140. package/src/renderer/api/providers.ts +5 -16
  141. package/src/renderer/components/chat/CommunityChatView.tsx +81 -0
  142. package/src/renderer/components/dashboard/DashboardView.tsx +130 -84
  143. package/src/renderer/components/extensions/ExtensionStoreView.tsx +39 -5
  144. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +2 -1
  145. package/src/renderer/components/extensions/capability-packs/CapabilityPacksPanel.tsx +170 -0
  146. package/src/renderer/components/layout/PaneContent.tsx +10 -2
  147. package/src/renderer/components/layout/SortableTab.tsx +4 -0
  148. package/src/renderer/components/layout/TabBarActions.tsx +13 -16
  149. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +4 -135
  150. package/src/renderer/components/schedules/SchedulesView.tsx +22 -14
  151. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +7 -6
  152. package/src/renderer/components/settings/SettingsTabs.tsx +24 -21
  153. package/src/renderer/components/settings/SettingsView.tsx +22 -13
  154. package/src/renderer/components/settings/components/SettingRow.tsx +13 -5
  155. package/src/renderer/components/settings/components/SettingsSectionCard.tsx +53 -0
  156. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +10 -6
  157. package/src/renderer/components/settings/components/SettingsSelect.tsx +12 -9
  158. package/src/renderer/components/settings/components/SettingsToggle.tsx +6 -5
  159. package/src/renderer/components/settings/components/index.ts +1 -0
  160. package/src/renderer/components/settings/sections/AdvancedSection.tsx +78 -59
  161. package/src/renderer/components/settings/sections/CliStatusSection.tsx +32 -44
  162. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  163. package/src/renderer/components/settings/sections/GeneralSection.tsx +216 -186
  164. package/src/renderer/components/settings/sections/PlatformsSection.tsx +25 -17
  165. package/src/renderer/components/settings/sections/TaskBusSection.tsx +63 -22
  166. package/src/renderer/components/sidebar/SidebarSessions.tsx +120 -80
  167. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +1 -1
  168. package/src/renderer/components/splash/splashScene.ts +6 -2
  169. package/src/renderer/components/system-manager/SystemManagerView.tsx +169 -255
  170. package/src/renderer/components/tasks/TasksView.tsx +63 -37
  171. package/src/renderer/components/team/CcSessionsSection.tsx +124 -89
  172. package/src/renderer/components/team/HarnessBrandLogos.tsx +318 -0
  173. package/src/renderer/components/team/HarnessSelect.tsx +25 -26
  174. package/src/renderer/components/team/TeamDetailView.tsx +137 -153
  175. package/src/renderer/components/team/TeamEmptyState.tsx +9 -37
  176. package/src/renderer/components/team/TeamListView.tsx +143 -30
  177. package/src/renderer/components/team/__tests__/CcSessionsSection.hasLocalFile.test.tsx +128 -0
  178. package/src/renderer/components/team/activity/ActivityItem.tsx +21 -9
  179. package/src/renderer/components/team/activity/ActivityTimeline.tsx +2 -2
  180. package/src/renderer/components/team/dialogs/AdvancedCliSection.tsx +1 -1
  181. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +13 -10
  182. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +156 -83
  183. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +9 -157
  184. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +19 -15
  185. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +48 -10
  186. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +11 -12
  187. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +39 -37
  188. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +434 -64
  189. package/src/renderer/components/team/dialogs/SendMessageDialog.tsx +12 -10
  190. package/src/renderer/components/team/dialogs/TaskDetailDialog.tsx +2 -2
  191. package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.bindProject.test.tsx +399 -0
  192. package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.chineseRepro.test.tsx +253 -0
  193. package/src/renderer/components/team/dialogs/platformAllowUtils.ts +91 -0
  194. package/src/renderer/components/team/dialogs/teammateRuntimeCompatibility.tsx +1 -1
  195. package/src/renderer/components/team/kanban/KanbanTaskCard.test.tsx +41 -0
  196. package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +41 -86
  197. package/src/renderer/components/team/loop-console/LoopCommandComposer.tsx +310 -0
  198. package/src/renderer/components/team/loop-console/LoopConsolePanel.tsx +372 -0
  199. package/src/renderer/components/team/loop-console/loopSendIntent.test.ts +85 -0
  200. package/src/renderer/components/team/loop-console/loopSendIntent.ts +221 -0
  201. package/src/renderer/components/team/loop-console/useLeadSessionToolActivity.ts +74 -0
  202. package/src/renderer/components/team/loop-console/useLoopCommandSuggestions.ts +165 -0
  203. package/src/renderer/components/team/loop-console/useLoopConsoleController.ts +266 -0
  204. package/src/renderer/components/team/members/LeadModelRow.test.tsx +1 -1
  205. package/src/renderer/components/team/members/LeadModelRow.tsx +5 -3
  206. package/src/renderer/components/team/members/MemberDetailDialog.tsx +11 -0
  207. package/src/renderer/components/team/members/MemberDetailStats.tsx +13 -3
  208. package/src/renderer/components/team/members/MemberDraftRow.test.tsx +1 -1
  209. package/src/renderer/components/team/members/MemberDraftRow.tsx +1 -1
  210. package/src/renderer/components/team/members/MemberMessagesTab.tsx +2 -2
  211. package/src/renderer/components/team/members/MemberStatsTab.tsx +1 -1
  212. package/src/renderer/components/team/messages/MessageComposer.tsx +150 -44
  213. package/src/renderer/components/team/messages/MessagesFilterPopover.tsx +2 -2
  214. package/src/renderer/components/team/messages/MessagesPanel.tsx +34 -28
  215. package/src/renderer/components/team/schedule/CcCronScheduleDialog.tsx +6 -6
  216. package/src/renderer/components/team/taskLogs/ExactTaskLogCard.tsx +2 -2
  217. package/src/renderer/components/team/taskLogs/TaskLogStreamSection.tsx +1 -1
  218. package/src/renderer/components/terminal/TerminalPanel.tsx +2 -3
  219. package/src/renderer/components/ui/MentionableTextarea.tsx +5 -1
  220. package/src/renderer/constants/teamColors.ts +5 -5
  221. package/src/renderer/hooks/useExtensionsTabState.ts +1 -1
  222. package/src/renderer/hooks/useMentionDetection.ts +5 -1
  223. package/src/renderer/hooks/useProjectWorkflowCommands.ts +57 -0
  224. package/src/renderer/hooks/useTeamSuggestions.ts +2 -0
  225. package/src/renderer/index.css +19 -2
  226. package/src/renderer/main.tsx +7 -1
  227. package/src/renderer/store/index.ts +18 -1
  228. package/src/renderer/store/slices/extensionsSlice.ts +83 -0
  229. package/src/renderer/store/slices/tabSlice.ts +61 -0
  230. package/src/renderer/store/slices/teamSlice.ts +138 -9
  231. package/src/renderer/types/mention.ts +8 -0
  232. package/src/renderer/types/tabs.ts +3 -1
  233. package/src/renderer/utils/__tests__/bindProjectSlug.test.ts +69 -0
  234. package/src/renderer/utils/__tests__/groupTransformer.test.ts +148 -0
  235. package/src/renderer/utils/__tests__/initialRoute.test.ts +101 -0
  236. package/src/renderer/utils/__tests__/leadToolActivity.test.ts +124 -0
  237. package/src/renderer/utils/__tests__/mergeTeamMessages.test.ts +81 -0
  238. package/src/renderer/utils/__tests__/teamMessageFiltering.test.ts +213 -0
  239. package/src/renderer/utils/__tests__/teamMessageKey.test.ts +75 -0
  240. package/src/renderer/utils/__tests__/workflowCommandExecution.test.ts +173 -0
  241. package/src/renderer/utils/__tests__/workflowCommandSuggestions.test.ts +59 -0
  242. package/src/renderer/utils/bindProjectSlug.ts +57 -0
  243. package/src/renderer/utils/capabilityCommandExecution.ts +113 -0
  244. package/src/renderer/utils/initialRoute.ts +89 -0
  245. package/src/renderer/utils/leadToolActivity.ts +117 -0
  246. package/src/renderer/utils/loopShortcutSuggestions.ts +106 -0
  247. package/src/renderer/utils/mentionSuggestions.ts +1 -1
  248. package/src/renderer/utils/slashCommandRegistry.ts +231 -0
  249. package/src/renderer/utils/teamMentionDirective.ts +31 -0
  250. package/src/renderer/utils/workflowCommandExecution.ts +96 -0
  251. package/src/renderer/utils/workflowCommandSuggestions.ts +49 -0
  252. package/src/shared/types/api.ts +79 -4
  253. package/src/shared/types/ccConnect.ts +1 -0
  254. package/src/shared/types/extensions/api.ts +19 -0
  255. package/src/shared/types/extensions/capabilityPack.ts +118 -0
  256. package/src/shared/types/extensions/index.ts +29 -1
  257. package/src/shared/types/index.ts +6 -0
  258. package/src/shared/types/loopAssets.ts +54 -0
  259. package/src/shared/types/providers.ts +0 -16
  260. package/src/shared/types/systemManager.ts +26 -1
  261. package/src/shared/types/team.ts +41 -5
  262. package/src/shared/types/terminal.ts +2 -36
  263. package/src/shared/types/worker.test.ts +28 -0
  264. package/src/shared/types/worker.ts +3 -0
  265. package/src/shared/utils/__tests__/effortLevels.test.ts +88 -0
  266. package/src/shared/utils/__tests__/providerBackend.test.ts +88 -0
  267. package/src/shared/utils/__tests__/providerLaunchArgs.test.ts +220 -0
  268. package/src/shared/utils/claudeStreamJson.test.ts +187 -0
  269. package/src/shared/utils/claudeStreamJson.ts +153 -0
  270. package/src/shared/utils/providerLaunchArgs.ts +217 -0
  271. package/src/shared/utils/slashCommands.ts +10 -0
  272. package/src/types/node-pty.d.ts +8 -0
  273. package/dist-renderer/assets/channel-Ch7JrfUu.js +0 -1
  274. package/dist-renderer/assets/classDiagram-2ON5EDUG-z9I4AnFy.js +0 -1
  275. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-z9I4AnFy.js +0 -1
  276. package/dist-renderer/assets/clone-Dfi1Jx6l.js +0 -1
  277. package/dist-renderer/assets/index-iyjkpSus.css +0 -32
  278. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DTUIBfce.js +0 -1
  279. package/src/main/services/system-manager/SystemManagerPtyService.ts +0 -233
  280. package/src/renderer/components/common/TerminalPane.tsx +0 -213
  281. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +0 -292
@@ -1,5 +1,8 @@
1
1
  import { useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
+ import { api } from '@renderer/api';
4
+ import { AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
5
+ import { HarnessSelect } from '@renderer/components/team/HarnessSelect';
3
6
  import { Button } from '@renderer/components/ui/button';
4
7
  import {
5
8
  Dialog,
@@ -9,17 +12,19 @@ import {
9
12
  DialogHeader,
10
13
  DialogTitle,
11
14
  } from '@renderer/components/ui/dialog';
12
- import { AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
13
- import { HarnessSelect } from '@renderer/components/team/HarnessSelect';
14
- import { Loader2, Settings2 } from 'lucide-react';
15
-
16
- import { api } from '@renderer/api';
17
15
  import { useStore } from '@renderer/store';
18
- import { isTeamProvisioningActive } from '@renderer/store/slices/teamSlice';
19
- import type { GlobalProvider } from '@shared/types';
20
- import type { CcAgentType } from '@shared/types/ccConnect';
21
- import { PERMISSION_MODE_OPTIONS } from './useTeamEditForm';
22
- import { PlatformBindingContent } from './PlatformBindingDialog';
16
+ import { Loader2, Plug2, Settings2, Wifi, WifiOff } from 'lucide-react';
17
+
18
+ import {
19
+ PlatformBindingContent,
20
+ type PlatformBindingCompleteOptions,
21
+ } from './PlatformBindingDialog';
22
+ import { buildPlatformAllowUpdatePayload, readStringRecord } from './platformAllowUtils';
23
+ import { platformMeta } from './platformMeta';
24
+
25
+ import type { CcAgentType, CcProjectPlatform } from '@shared/types/ccConnect';
26
+ import type { TeamUpdateConfigRequest } from '@shared/types/team';
27
+ import { SYSTEM_MANAGER_TEAM_NAME } from '@shared/types/team';
23
28
 
24
29
  // ── Section wrapper ──────────────────────────────────────────
25
30
  function FormSection({
@@ -32,10 +37,13 @@ function FormSection({
32
37
  children: React.ReactNode;
33
38
  }): React.JSX.Element {
34
39
  return (
35
- <div className="rounded-md border border-[var(--color-border)] p-3">
40
+ <div className="bg-[var(--color-surface-raised)]/55 relative overflow-hidden rounded-xl border border-[var(--color-border-subtle)] p-3 shadow-sm shadow-black/10">
41
+ <div className="pointer-events-none absolute inset-x-6 top-0 h-px bg-gradient-to-r from-transparent via-[var(--color-accent-border)] to-transparent" />
36
42
  <h3 className="text-sm font-medium text-[var(--color-text)]">{title}</h3>
37
43
  {description && (
38
- <p className="mt-0.5 text-xs text-[var(--color-text-muted)]">{description}</p>
44
+ <p className="mt-0.5 text-xs leading-relaxed text-[var(--color-text-muted)]">
45
+ {description}
46
+ </p>
39
47
  )}
40
48
  <div className="mt-3 space-y-3">{children}</div>
41
49
  </div>
@@ -43,9 +51,86 @@ function FormSection({
43
51
  }
44
52
 
45
53
  const inputCls =
46
- 'w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]';
54
+ 'w-full rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none transition-colors focus:border-[var(--color-accent-border)] focus:ring-1 focus:ring-[var(--color-accent-border)]';
47
55
  const labelCls = 'mb-1 block text-xs font-medium text-[var(--color-text-secondary)]';
48
56
 
57
+ const PERMISSION_MODE_OPTIONS = [
58
+ { value: 'default', label: '默认' },
59
+ { value: 'acceptEdits', label: '自动接受编辑' },
60
+ { value: 'bypassPermissions', label: '跳过权限确认' },
61
+ { value: 'plan', label: '计划模式' },
62
+ ] as const;
63
+
64
+ function getPlatformLabel(type: string): string {
65
+ if (type === 'feishu' || type === 'lark') return '飞书 / Lark';
66
+ if (type === 'weixin') return '微信';
67
+ return platformMeta[type]?.label ?? type;
68
+ }
69
+
70
+ function getPlatformAllowPlaceholder(platformType: string, kind: 'from' | 'chat'): string {
71
+ const fieldKey = kind === 'from' ? 'allow_from' : 'allow_chat';
72
+ const field = platformMeta[platformType]?.fields.find((item) => item.key === fieldKey);
73
+ if (field?.placeholder) return `留空表示未单独配置;${field.placeholder}`;
74
+ return kind === 'from'
75
+ ? '留空表示未单独配置;输入 * 表示允许所有用户'
76
+ : '留空表示未单独配置;输入 * 表示允许所有群聊/频道';
77
+ }
78
+
79
+ function uniquePlatformTypes(platforms: CcProjectPlatform[]): string[] {
80
+ return [
81
+ ...new Set(
82
+ platforms
83
+ .map((platform) => platform.type)
84
+ .filter((type) => Boolean(type) && type !== 'bridge')
85
+ ),
86
+ ];
87
+ }
88
+
89
+ function BoundPlatformList({ platforms }: { platforms: CcProjectPlatform[] }): React.JSX.Element {
90
+ if (platforms.length === 0) {
91
+ return (
92
+ <div className="rounded-lg border border-dashed border-[var(--color-border)] px-3 py-3 text-xs text-[var(--color-text-muted)]">
93
+ 暂无已绑定渠道。
94
+ </div>
95
+ );
96
+ }
97
+
98
+ return (
99
+ <div className="space-y-2">
100
+ {platforms.map((platform) => (
101
+ <div
102
+ key={`${platform.type}:${String(platform.connected)}`}
103
+ className="flex items-center justify-between gap-3 rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-surface)] px-3 py-2"
104
+ >
105
+ <div className="flex min-w-0 items-center gap-2.5">
106
+ <div className="flex size-7 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-[var(--color-accent)]">
107
+ <Plug2 className="size-3.5" />
108
+ </div>
109
+ <div className="min-w-0">
110
+ <div className="truncate text-xs font-medium text-[var(--color-text)]">
111
+ {getPlatformLabel(platform.type)}
112
+ </div>
113
+ <div className="mt-0.5 font-mono text-[10px] text-[var(--color-text-muted)]">
114
+ {platform.type}
115
+ </div>
116
+ </div>
117
+ </div>
118
+ <span
119
+ className={`flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-[10px] ${
120
+ platform.connected
121
+ ? 'bg-[var(--color-accent-soft)] text-[var(--color-accent)]'
122
+ : 'bg-white/5 text-[var(--color-text-muted)]'
123
+ }`}
124
+ >
125
+ {platform.connected ? <Wifi className="size-3" /> : <WifiOff className="size-3" />}
126
+ {platform.connected ? '已连接' : '已绑定未连接'}
127
+ </span>
128
+ </div>
129
+ ))}
130
+ </div>
131
+ );
132
+ }
133
+
49
134
  interface RuntimeConfigDialogProps {
50
135
  open: boolean;
51
136
  teamName: string;
@@ -57,24 +142,43 @@ export function RuntimeConfigDialog({
57
142
  teamName,
58
143
  onClose,
59
144
  }: RuntimeConfigDialogProps): React.JSX.Element {
145
+ const isAdminTeam = teamName === SYSTEM_MANAGER_TEAM_NAME;
60
146
  const { data, fetchTeams, selectTeam } = useStore((s) => ({
61
147
  data: s.selectedTeamName === teamName ? s.selectedTeamData : null,
62
148
  fetchTeams: s.fetchTeams,
63
149
  selectTeam: s.selectTeam,
64
150
  }));
65
- const isProvisioning = useStore((s) => isTeamProvisioningActive(s, teamName));
66
151
 
67
152
  // ── Derived defaults ─────────────────────────────────────────
68
153
  const defaults = useMemo(() => {
69
154
  const cfg = data?.config;
70
155
  const d = data as Record<string, unknown> | null;
156
+ const rawSettings = (data?.settings ?? {}) as Record<string, unknown>;
71
157
  return {
72
158
  agentType: cfg?.agentType ?? (d?.harness as string | undefined) ?? 'claudecode',
73
- workDir: (d?.workDir as string | undefined) ?? cfg?.projectPath ?? '',
159
+ workDir: cfg?.projectPath ?? (d?.workDir as string | undefined) ?? '',
74
160
  permissionMode: cfg?.permissionMode ?? (d?.permissionMode as string | undefined) ?? 'default',
75
- disabledCommands: Array.isArray(cfg?.disabledCommands)
76
- ? cfg.disabledCommands
77
- : [],
161
+ disabledCommands: Array.isArray(cfg?.disabledCommands) ? cfg.disabledCommands : [],
162
+ managedSources:
163
+ cfg?.managedSources ??
164
+ (typeof rawSettings.admin_from === 'string' ? rawSettings.admin_from : '*'),
165
+ language:
166
+ cfg?.language ?? (typeof rawSettings.language === 'string' ? rawSettings.language : 'zh'),
167
+ showContextIndicator:
168
+ cfg?.showContextIndicator ??
169
+ (typeof rawSettings.show_context_indicator === 'boolean'
170
+ ? rawSettings.show_context_indicator
171
+ : true),
172
+ replyFooter:
173
+ cfg?.replyFooter ??
174
+ (typeof rawSettings.reply_footer === 'boolean' ? rawSettings.reply_footer : true),
175
+ injectSender:
176
+ cfg?.injectSender ??
177
+ (typeof rawSettings.inject_sender === 'boolean' ? rawSettings.inject_sender : false),
178
+ platformAllowFrom:
179
+ cfg?.platformAllowFrom ?? readStringRecord(rawSettings.platform_allow_from),
180
+ platformAllowChat:
181
+ cfg?.platformAllowChat ?? readStringRecord(rawSettings.platform_allow_chat),
78
182
  providerRefs: data?.providerRefs ?? [],
79
183
  globalProviders: data?.globalProviders ?? [],
80
184
  bindProject: (d?.bindProject as string | undefined) ?? teamName,
@@ -89,10 +193,18 @@ export function RuntimeConfigDialog({
89
193
  defaults.disabledCommands.join(', ')
90
194
  );
91
195
  const [providerRef, setProviderRef] = useState(defaults.providerRefs[0] ?? '');
92
-
196
+ const [platformAllowFrom, setPlatformAllowFrom] = useState(defaults.platformAllowFrom);
197
+ const [platformAllowChat, setPlatformAllowChat] = useState(defaults.platformAllowChat);
198
+ const [language, setLanguage] = useState(defaults.language);
199
+ const [managedSources, setManagedSources] = useState(defaults.managedSources);
200
+ const [showContextIndicator, setShowContextIndicator] = useState(defaults.showContextIndicator);
201
+ const [replyFooter, setReplyFooter] = useState(defaults.replyFooter);
202
+ const [injectSender, setInjectSender] = useState(defaults.injectSender);
93
203
  const [savePhase, setSavePhase] = useState<'idle' | 'saving' | 'restarting' | 'done'>('idle');
94
204
  const [error, setError] = useState<string | null>(null);
95
205
  const saving = savePhase === 'saving' || savePhase === 'restarting';
206
+ const [bindingStep, setBindingStep] = useState<'runtime' | 'bind'>('runtime');
207
+ const [bindingSavePending, setBindingSavePending] = useState(false);
96
208
 
97
209
  const defaultsRef = useRef(defaults);
98
210
  if (defaults.agentType) defaultsRef.current = defaults;
@@ -108,11 +220,20 @@ export function RuntimeConfigDialog({
108
220
  const d = defaultsRef.current;
109
221
  setSavePhase('idle');
110
222
  setError(null);
223
+ setBindingStep('runtime');
224
+ setBindingSavePending(false);
111
225
  setAgentType(d.agentType);
112
226
  setPermissionMode(d.permissionMode);
113
227
  setWorkDir(d.workDir);
114
228
  setDisabledCommandsInput(d.disabledCommands.join(', '));
115
229
  setProviderRef(d.providerRefs[0] ?? '');
230
+ setPlatformAllowFrom(d.platformAllowFrom);
231
+ setPlatformAllowChat(d.platformAllowChat);
232
+ setLanguage(d.language);
233
+ setManagedSources(d.managedSources);
234
+ setShowContextIndicator(d.showContextIndicator);
235
+ setReplyFooter(d.replyFooter);
236
+ setInjectSender(d.injectSender);
116
237
  }, [open]);
117
238
 
118
239
  // ── Computed ─────────────────────────────────────────────────
@@ -127,42 +248,93 @@ export function RuntimeConfigDialog({
127
248
  [defaults.globalProviders, agentType]
128
249
  );
129
250
 
130
- const toggleProviderRef = (providerName: string): void => {
251
+ const boundPlatforms = useMemo<CcProjectPlatform[]>(
252
+ () => data?.platforms ?? [],
253
+ [data?.platforms]
254
+ );
255
+ const platformTypes = useMemo(() => uniquePlatformTypes(boundPlatforms), [boundPlatforms]);
256
+
257
+ const updatePlatformAllowValue = (
258
+ kind: 'from' | 'chat',
259
+ platformType: string,
260
+ value: string
261
+ ): void => {
262
+ markRuntimeEdited();
263
+ const setter = kind === 'from' ? setPlatformAllowFrom : setPlatformAllowChat;
264
+ setter((current) => ({ ...current, [platformType]: value }));
265
+ };
266
+
267
+ const markRuntimeEdited = (): void => {
131
268
  setError(null);
269
+ setSavePhase((phase) => (phase === 'done' ? 'idle' : phase));
270
+ };
271
+
272
+ const toggleProviderRef = (providerName: string): void => {
273
+ markRuntimeEdited();
132
274
  setProviderRef(providerRef === providerName ? '' : providerName);
133
275
  };
134
276
 
135
277
  // ── Save ─────────────────────────────────────────────────────
136
- const handleSave = (): void => {
137
- if (savePhase !== 'idle') return;
278
+ const buildConfigPayload = (): TeamUpdateConfigRequest => {
138
279
  const disabledCommands = disabledCommandsInput
139
280
  .split(',')
140
281
  .map((e) => e.trim())
141
282
  .filter((e) => e.length > 0);
142
283
 
284
+ const platformAllowFromPatch = buildPlatformAllowUpdatePayload(
285
+ defaults.platformAllowFrom,
286
+ platformAllowFrom
287
+ );
288
+ const platformAllowChatPatch = buildPlatformAllowUpdatePayload(
289
+ defaults.platformAllowChat,
290
+ platformAllowChat
291
+ );
292
+
293
+ return {
294
+ agentType: agentType.trim() || undefined,
295
+ workDir: workDir.trim() || undefined,
296
+ permissionMode: permissionMode.trim() || undefined,
297
+ disabledCommands,
298
+ platformAllowFrom: platformAllowFromPatch,
299
+ platformAllowChat: platformAllowChatPatch,
300
+ language: language.trim() || undefined,
301
+ managedSources: managedSources.trim() || undefined,
302
+ showContextIndicator,
303
+ replyFooter,
304
+ injectSender,
305
+ providerRefs: providerRef ? [providerRef] : [],
306
+ };
307
+ };
308
+
309
+ const saveRuntimeConfig = async (): Promise<void> => {
310
+ await api.teams.updateConfig(teamName, buildConfigPayload());
311
+ await Promise.all([fetchTeams(), selectTeam(teamName)]);
312
+ };
313
+
314
+ const handleSave = (): void => {
315
+ if (savePhase !== 'idle') return;
316
+
143
317
  setSavePhase('saving');
144
318
  setError(null);
145
319
 
146
320
  void (async () => {
147
321
  try {
148
- await api.teams.updateConfig(teamName, {
149
- agentType: agentType.trim() || undefined,
150
- workDir: workDir.trim() || undefined,
151
- permissionMode: permissionMode.trim() || undefined,
152
- disabledCommands,
153
- providerRefs: providerRef ? [providerRef] : [],
154
- });
155
- await Promise.all([fetchTeams(), selectTeam(teamName)]);
322
+ await saveRuntimeConfig();
156
323
 
157
- setSavePhase('restarting');
158
- try {
159
- await api.ccSettings.restart();
324
+ if (isAdminTeam) {
325
+ void Promise.all([fetchTeams(), selectTeam(teamName)]);
160
326
  setSavePhase('done');
161
- } catch (restartErr) {
162
- setError(
163
- `配置已保存,但重启失败:${restartErr instanceof Error ? restartErr.message : '未知错误'}`
164
- );
165
- setSavePhase('idle');
327
+ } else {
328
+ setSavePhase('restarting');
329
+ try {
330
+ await api.ccSettings.restart();
331
+ setSavePhase('done');
332
+ } catch (restartErr) {
333
+ setError(
334
+ `配置已保存,但重启失败:${restartErr instanceof Error ? restartErr.message : '未知错误'}`
335
+ );
336
+ setSavePhase('idle');
337
+ }
166
338
  }
167
339
  } catch (err) {
168
340
  setError(err instanceof Error ? err.message : '保存失败');
@@ -171,6 +343,45 @@ export function RuntimeConfigDialog({
171
343
  })();
172
344
  };
173
345
 
346
+ const handleStartBinding = (): void => {
347
+ if (saving || bindingSavePending) return;
348
+
349
+ setBindingSavePending(true);
350
+ setError(null);
351
+ void (async () => {
352
+ try {
353
+ await saveRuntimeConfig();
354
+ setBindingStep('bind');
355
+ } catch (err) {
356
+ setError(err instanceof Error ? err.message : '保存配置失败,无法进入渠道绑定');
357
+ } finally {
358
+ setBindingSavePending(false);
359
+ }
360
+ })();
361
+ };
362
+
363
+ const handleBindingComplete = (options?: PlatformBindingCompleteOptions): void => {
364
+ if (saving) return;
365
+
366
+ setSavePhase(options?.restartHandled ? 'saving' : 'restarting');
367
+ setError(null);
368
+ void (async () => {
369
+ try {
370
+ if (!options?.restartHandled && !isAdminTeam) {
371
+ await api.ccSettings.restart();
372
+ }
373
+ await Promise.all([fetchTeams(), selectTeam(teamName)]);
374
+ setBindingStep('runtime');
375
+ setSavePhase('done');
376
+ } catch (err) {
377
+ setError(`渠道已绑定,但重启失败:${err instanceof Error ? err.message : '未知错误'}`);
378
+ setBindingStep('runtime');
379
+ setSavePhase('idle');
380
+ void Promise.all([fetchTeams(), selectTeam(teamName)]).catch(() => undefined);
381
+ }
382
+ })();
383
+ };
384
+
174
385
  const saveLabel =
175
386
  savePhase === 'done'
176
387
  ? '已完成'
@@ -178,15 +389,15 @@ export function RuntimeConfigDialog({
178
389
  ? '正在重启...'
179
390
  : savePhase === 'saving'
180
391
  ? '保存中...'
181
- : '保存并重启';
182
-
183
- const [bindingStep, setBindingStep] = useState<'runtime' | 'bind'>('runtime');
392
+ : isAdminTeam
393
+ ? '保存'
394
+ : '保存并重启';
184
395
 
185
396
  return (
186
397
  <Dialog
187
- open={savePhase === 'saving' ? true : open}
398
+ open={saving || bindingSavePending ? true : open}
188
399
  onOpenChange={(nextOpen) => {
189
- if (saving) return;
400
+ if (saving || bindingSavePending) return;
190
401
  if (!nextOpen) onClose();
191
402
  }}
192
403
  >
@@ -197,7 +408,7 @@ export function RuntimeConfigDialog({
197
408
  运行时配置
198
409
  </DialogTitle>
199
410
  <DialogDescription>
200
- 修改 Agent 类型、渠道、消息设置等运行时参数。部分变更需要重启服务。
411
+ 修改 Agent 类型、渠道、Loop 动态设置等运行时参数。部分变更需要重启服务。
201
412
  </DialogDescription>
202
413
  </DialogHeader>
203
414
 
@@ -206,7 +417,9 @@ export function RuntimeConfigDialog({
206
417
  projectName={defaults.bindProject}
207
418
  workDir={workDir}
208
419
  agentType={agentType}
209
- onComplete={() => setBindingStep('runtime')}
420
+ platformAllowFrom={platformAllowFrom}
421
+ platformAllowChat={platformAllowChat}
422
+ onComplete={handleBindingComplete}
210
423
  onCancel={() => setBindingStep('runtime')}
211
424
  />
212
425
  ) : (
@@ -218,7 +431,10 @@ export function RuntimeConfigDialog({
218
431
  <label className={labelCls}>Agent 类型</label>
219
432
  <HarnessSelect
220
433
  value={agentType as CcAgentType}
221
- onChange={(v) => { setError(null); setAgentType(v); }}
434
+ onChange={(v) => {
435
+ markRuntimeEdited();
436
+ setAgentType(v);
437
+ }}
222
438
  className="w-full"
223
439
  />
224
440
  </div>
@@ -226,11 +442,16 @@ export function RuntimeConfigDialog({
226
442
  <label className={labelCls}>权限模式</label>
227
443
  <select
228
444
  value={permissionMode}
229
- onChange={(e) => { setError(null); setPermissionMode(e.target.value); }}
445
+ onChange={(e) => {
446
+ markRuntimeEdited();
447
+ setPermissionMode(e.target.value);
448
+ }}
230
449
  className={inputCls}
231
450
  >
232
451
  {PERMISSION_MODE_OPTIONS.map((opt) => (
233
- <option key={opt.value} value={opt.value}>{opt.label}</option>
452
+ <option key={opt.value} value={opt.value}>
453
+ {opt.label}
454
+ </option>
234
455
  ))}
235
456
  </select>
236
457
  </div>
@@ -240,7 +461,10 @@ export function RuntimeConfigDialog({
240
461
  <input
241
462
  type="text"
242
463
  value={workDir}
243
- onChange={(e) => { setError(null); setWorkDir(e.target.value); }}
464
+ onChange={(e) => {
465
+ markRuntimeEdited();
466
+ setWorkDir(e.target.value);
467
+ }}
244
468
  className={`${inputCls} font-mono`}
245
469
  placeholder="/Users/you/code/project"
246
470
  />
@@ -248,30 +472,160 @@ export function RuntimeConfigDialog({
248
472
  </FormSection>
249
473
 
250
474
  {/* 渠道 */}
251
- <FormSection title="渠道" description="绑定外部消息平台(飞书、Telegram 等)。">
475
+ <FormSection title="渠道" description="绑定外部协作平台(飞书、Telegram 等)。">
476
+ <BoundPlatformList platforms={boundPlatforms} />
477
+ {platformTypes.length > 0 ? (
478
+ <div className="space-y-2">
479
+ {platformTypes.map((platformType) => (
480
+ <details
481
+ key={platformType}
482
+ className="rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-surface)] px-3 py-2"
483
+ >
484
+ <summary className="cursor-pointer text-xs font-medium text-[var(--color-text)]">
485
+ {getPlatformLabel(platformType)} 入口权限
486
+ </summary>
487
+ <div className="mt-3 grid gap-3 md:grid-cols-2">
488
+ <div>
489
+ <label className={labelCls}>允许用户</label>
490
+ <input
491
+ type="text"
492
+ value={platformAllowFrom[platformType] ?? ''}
493
+ onChange={(event) =>
494
+ updatePlatformAllowValue('from', platformType, event.target.value)
495
+ }
496
+ className={`${inputCls} font-mono`}
497
+ placeholder={getPlatformAllowPlaceholder(platformType, 'from')}
498
+ />
499
+ </div>
500
+ <div>
501
+ <label className={labelCls}>允许群聊/频道</label>
502
+ <input
503
+ type="text"
504
+ value={platformAllowChat[platformType] ?? ''}
505
+ onChange={(event) =>
506
+ updatePlatformAllowValue('chat', platformType, event.target.value)
507
+ }
508
+ className={`${inputCls} font-mono`}
509
+ placeholder={getPlatformAllowPlaceholder(platformType, 'chat')}
510
+ />
511
+ </div>
512
+ </div>
513
+ </details>
514
+ ))}
515
+ </div>
516
+ ) : null}
517
+ <p className="text-[11px] leading-relaxed text-[var(--color-text-muted)]">
518
+ 按渠道控制运行时入口。留空代表未单独配置,不等于允许所有;只有显式填写 *
519
+ 才表示放行所有。
520
+ </p>
252
521
  <Button
253
522
  variant="outline"
254
523
  size="sm"
255
- onClick={() => setBindingStep('bind')}
524
+ onClick={handleStartBinding}
525
+ disabled={saving || bindingSavePending}
256
526
  >
257
- 绑定新渠道
527
+ {bindingSavePending && <Loader2 size={14} className="mr-1.5 animate-spin" />}
528
+ {bindingSavePending ? '保存配置中...' : '绑定新渠道'}
258
529
  </Button>
259
530
  </FormSection>
260
531
 
532
+ {/* Loop 动态设置 — 语言/管理来源/消息格式,保存即生效 (#21) */}
533
+ <FormSection
534
+ title="Loop 动态设置"
535
+ description="语言、管理来源与消息格式,保存即生效(无需重启)。"
536
+ >
537
+ <div className="grid gap-3 md:grid-cols-2">
538
+ <div>
539
+ <label className={labelCls}>语言</label>
540
+ <input
541
+ type="text"
542
+ value={language}
543
+ onChange={(e) => {
544
+ markRuntimeEdited();
545
+ setLanguage(e.target.value);
546
+ }}
547
+ className={inputCls}
548
+ placeholder="zh"
549
+ data-testid="loop-language"
550
+ />
551
+ </div>
552
+ <div>
553
+ <label className={labelCls}>管理来源</label>
554
+ <input
555
+ type="text"
556
+ value={managedSources}
557
+ onChange={(e) => {
558
+ markRuntimeEdited();
559
+ setManagedSources(e.target.value);
560
+ }}
561
+ className={inputCls}
562
+ placeholder="user1,user2 或 *"
563
+ data-testid="loop-managed-sources"
564
+ />
565
+ </div>
566
+ </div>
567
+ <div className="flex flex-wrap gap-2">
568
+ <label className="flex cursor-pointer items-center gap-2 rounded-lg border border-[var(--color-border-subtle)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)]">
569
+ <input
570
+ type="checkbox"
571
+ checked={showContextIndicator}
572
+ onChange={(e) => {
573
+ markRuntimeEdited();
574
+ setShowContextIndicator(e.target.checked);
575
+ }}
576
+ className="size-3.5"
577
+ data-testid="loop-show-context-indicator"
578
+ />
579
+ 上下文指示
580
+ </label>
581
+ <label className="flex cursor-pointer items-center gap-2 rounded-lg border border-[var(--color-border-subtle)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)]">
582
+ <input
583
+ type="checkbox"
584
+ checked={replyFooter}
585
+ onChange={(e) => {
586
+ markRuntimeEdited();
587
+ setReplyFooter(e.target.checked);
588
+ }}
589
+ className="size-3.5"
590
+ data-testid="loop-reply-footer"
591
+ />
592
+ 回复尾部信息
593
+ </label>
594
+ <label className="flex cursor-pointer items-center gap-2 rounded-lg border border-[var(--color-border-subtle)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)]">
595
+ <input
596
+ type="checkbox"
597
+ checked={injectSender}
598
+ onChange={(e) => {
599
+ markRuntimeEdited();
600
+ setInjectSender(e.target.checked);
601
+ }}
602
+ className="size-3.5"
603
+ data-testid="loop-inject-sender"
604
+ />
605
+ 注入发送者
606
+ </label>
607
+ </div>
608
+ </FormSection>
609
+
261
610
  {/* Provider */}
262
- <div className="rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.02] p-3">
611
+ <div className="bg-[var(--color-surface-raised)]/55 relative overflow-hidden rounded-xl border border-[var(--color-border-subtle)] p-3 shadow-sm shadow-black/10">
612
+ <div className="pointer-events-none absolute inset-x-6 top-0 h-px bg-gradient-to-r from-transparent via-[var(--color-accent-border)] to-transparent" />
263
613
  <div className="flex items-start justify-between gap-3">
264
614
  <div>
265
615
  <p className="text-xs font-medium text-[var(--color-text)]">Provider(可选)</p>
266
616
  <p className="mt-1 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
267
- 留空时使用本机 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType} 默认配置。
617
+ 留空时使用本机 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType}{' '}
618
+ 默认配置。
268
619
  </p>
269
620
  </div>
270
621
  {providerRef ? (
271
622
  <button
272
623
  type="button"
273
624
  className="shrink-0 rounded-md border border-[var(--color-border)] px-2 py-1 text-[11px] text-[var(--color-text-muted)] hover:bg-white/5"
274
- onClick={() => setProviderRef('')}
625
+ onClick={() => {
626
+ markRuntimeEdited();
627
+ setProviderRef('');
628
+ }}
275
629
  >
276
630
  使用本机默认
277
631
  </button>
@@ -284,7 +638,10 @@ export function RuntimeConfigDialog({
284
638
  const at = agentType as CcAgentType;
285
639
  const endpoint = provider.endpoints?.[at] ?? provider.base_url ?? '默认端点';
286
640
  const model =
287
- provider.agent_models?.[at] ?? provider.model ?? provider.models?.[0]?.model ?? '未指定模型';
641
+ provider.agent_models?.[at] ??
642
+ provider.model ??
643
+ provider.models?.[0]?.model ??
644
+ '未指定模型';
288
645
  return (
289
646
  <button
290
647
  key={provider.name}
@@ -292,20 +649,24 @@ export function RuntimeConfigDialog({
292
649
  onClick={() => toggleProviderRef(provider.name)}
293
650
  className={`w-full rounded-lg border px-3 py-2 text-left transition-colors ${
294
651
  checked
295
- ? 'border-indigo-400/60 bg-indigo-500/10'
296
- : 'border-[var(--color-border-subtle)] bg-black/10 hover:border-[var(--color-border)] hover:bg-white/[0.04]'
652
+ ? 'shadow-[var(--color-accent-glow)]/20 border-[var(--color-accent-border)] bg-[var(--color-accent-muted)] shadow-sm'
653
+ : 'border-[var(--color-border-subtle)] bg-black/10 hover:border-[var(--color-border)] hover:bg-[var(--color-accent-soft)]'
297
654
  }`}
298
655
  >
299
656
  <div className="flex items-center justify-between gap-3">
300
657
  <div className="min-w-0">
301
- <p className="truncate text-xs font-medium text-[var(--color-text)]">{provider.name}</p>
658
+ <p className="truncate text-xs font-medium text-[var(--color-text)]">
659
+ {provider.name}
660
+ </p>
302
661
  <p className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
303
662
  {model} · {endpoint}
304
663
  </p>
305
664
  </div>
306
665
  <span
307
666
  className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] ${
308
- checked ? 'bg-indigo-400/20 text-indigo-200' : 'bg-white/5 text-[var(--color-text-muted)]'
667
+ checked
668
+ ? 'bg-[var(--color-accent-soft)] text-[var(--color-accent)]'
669
+ : 'bg-white/5 text-[var(--color-text-muted)]'
309
670
  }`}
310
671
  >
311
672
  {checked ? '已绑定' : '可绑定'}
@@ -316,7 +677,8 @@ export function RuntimeConfigDialog({
316
677
  })
317
678
  ) : (
318
679
  <div className="rounded-md border border-dashed border-[var(--color-border)] px-3 py-3 text-xs text-[var(--color-text-muted)]">
319
- 暂无适用于 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType} 的全局 Provider。
680
+ 暂无适用于 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType} 的全局
681
+ Provider。
320
682
  </div>
321
683
  )}
322
684
  </div>
@@ -329,7 +691,10 @@ export function RuntimeConfigDialog({
329
691
  <input
330
692
  type="text"
331
693
  value={disabledCommandsInput}
332
- onChange={(e) => { setError(null); setDisabledCommandsInput(e.target.value); }}
694
+ onChange={(e) => {
695
+ markRuntimeEdited();
696
+ setDisabledCommandsInput(e.target.value);
697
+ }}
333
698
  className={inputCls}
334
699
  placeholder="restart, upgrade, cron"
335
700
  />
@@ -342,13 +707,18 @@ export function RuntimeConfigDialog({
342
707
 
343
708
  {bindingStep === 'runtime' && (
344
709
  <DialogFooter>
345
- <Button variant="outline" size="sm" onClick={onClose} disabled={saving}>
710
+ <Button
711
+ variant="outline"
712
+ size="sm"
713
+ onClick={onClose}
714
+ disabled={saving || bindingSavePending}
715
+ >
346
716
  {savePhase === 'done' ? '关闭' : '取消'}
347
717
  </Button>
348
718
  <Button
349
719
  size="sm"
350
720
  onClick={handleSave}
351
- disabled={saving || savePhase === 'done'}
721
+ disabled={saving || bindingSavePending || savePhase === 'done'}
352
722
  >
353
723
  {saving && <Loader2 size={14} className="mr-1.5 animate-spin" />}
354
724
  {saveLabel}