@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
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Worker Society — 输入适配器:Fastify REST 路由(/api/society/*)。
3
+ *
4
+ * 把 WorkerSocietyService 的 use case 暴露为 HTTP 接口,供前端 / 外部调用。
5
+ * 命令(发布、自荐、选派、交付…)走 service;纯查询(列表/详情/feed)直接读 store/gateway。
6
+ *
7
+ * 路由风格对齐 server.ts 既有约定(app.get/post,try/catch 返回兜底,body 用 Record 解析)。
8
+ */
9
+ import type { FastifyInstance } from 'fastify';
10
+
11
+ import type { AgentCapability } from '../../../core/domain/models/society';
12
+ import type {
13
+ PublishNeedCommand,
14
+ RegisterProfileCommand,
15
+ } from '../../../core/application/WorkerSocietyService';
16
+ import type { SocietyComponents } from '../../composition/societyComposition';
17
+
18
+ export function registerSocietyRoutes(app: FastifyInstance, c: SocietyComponents): void {
19
+ // ── workers(发现 / 档案)──────────────────────────────────────────────
20
+ app.get('/api/society/workers', async () => {
21
+ try {
22
+ return await c.service.discoverWorkers();
23
+ } catch {
24
+ return [];
25
+ }
26
+ });
27
+
28
+ app.get('/api/society/workers/:workerId', async (request, reply) => {
29
+ const { workerId } = request.params as { workerId: string };
30
+ const profile = await c.service.getProfile(workerId);
31
+ if (!profile) return reply.code(404).send({ error: 'worker_not_found' });
32
+ return profile;
33
+ });
34
+
35
+ app.post('/api/society/workers/register', async (request, reply) => {
36
+ const body = (request.body ?? {}) as Record<string, unknown>;
37
+ const workerId = String(body.workerId ?? '').trim();
38
+ const name = String(body.name ?? '').trim();
39
+ if (!workerId || !name) {
40
+ return reply.code(400).send({ error: 'workerId and name required' });
41
+ }
42
+ const cmd: RegisterProfileCommand = {
43
+ workerId,
44
+ name,
45
+ kind: body.kind as RegisterProfileCommand['kind'],
46
+ harness: body.harness as string | undefined,
47
+ capabilities: body.capabilities as AgentCapability[] | undefined,
48
+ interests: body.interests as string[] | undefined,
49
+ maxConcurrent: body.maxConcurrent as number | undefined,
50
+ reputation: body.reputation as number | undefined,
51
+ description: body.description as string | undefined,
52
+ };
53
+ return await c.service.registerProfile(cmd);
54
+ });
55
+
56
+ // ── needs(广场公告板)─────────────────────────────────────────────────
57
+ app.get('/api/society/needs/open', async () => {
58
+ try {
59
+ return await c.needs.listOpen();
60
+ } catch {
61
+ return [];
62
+ }
63
+ });
64
+
65
+ // 画布/实时视图用:仍在生命周期内的需求(选派后/执行中/待审核也保留)。
66
+ app.get('/api/society/needs/active', async () => {
67
+ try {
68
+ return await c.needs.listActive();
69
+ } catch {
70
+ return [];
71
+ }
72
+ });
73
+
74
+ app.get('/api/society/needs', async () => {
75
+ try {
76
+ return await c.needs.list();
77
+ } catch {
78
+ return [];
79
+ }
80
+ });
81
+
82
+ app.get('/api/society/needs/:needId', async (request) => {
83
+ const { needId } = request.params as { needId: string };
84
+ return (await c.needs.get(needId)) ?? null;
85
+ });
86
+
87
+ app.post('/api/society/needs', async (request, reply) => {
88
+ const body = (request.body ?? {}) as Record<string, unknown>;
89
+ const postedBy = String(body.postedBy ?? '').trim();
90
+ const subject = String(body.subject ?? '').trim();
91
+ if (!postedBy || !subject) {
92
+ return reply.code(400).send({ error: 'postedBy and subject required' });
93
+ }
94
+ const cmd: PublishNeedCommand = {
95
+ postedBy,
96
+ subject,
97
+ description: body.description as string | undefined,
98
+ requiredCapabilities: Array.isArray(body.requiredCapabilities)
99
+ ? (body.requiredCapabilities as string[])
100
+ : [],
101
+ priority: body.priority as number | undefined,
102
+ deadline: body.deadline as string | undefined,
103
+ };
104
+ const { need } = await c.service.publishNeed(cmd);
105
+ return need;
106
+ });
107
+
108
+ app.post('/api/society/needs/:needId/volunteer', async (request) => {
109
+ const { needId } = request.params as { needId: string };
110
+ const body = (request.body ?? {}) as Record<string, unknown>;
111
+ return await c.service.volunteerFor(
112
+ needId,
113
+ String(body.workerId ?? '').trim(),
114
+ body.note as string | undefined
115
+ );
116
+ });
117
+
118
+ app.post('/api/society/needs/:needId/select', async (request) => {
119
+ const { needId } = request.params as { needId: string };
120
+ return await c.service.selectAssignee(needId);
121
+ });
122
+
123
+ app.post('/api/society/needs/:needId/start', async (request) => {
124
+ const { needId } = request.params as { needId: string };
125
+ const body = (request.body ?? {}) as Record<string, unknown>;
126
+ return await c.service.startNeed(needId, String(body.workerId ?? '').trim());
127
+ });
128
+
129
+ app.post('/api/society/needs/:needId/deliver', async (request) => {
130
+ const { needId } = request.params as { needId: string };
131
+ const body = (request.body ?? {}) as Record<string, unknown>;
132
+ return await c.service.deliverNeed(needId, String(body.result ?? '').trim());
133
+ });
134
+
135
+ app.post('/api/society/needs/:needId/accept', async (request) => {
136
+ const { needId } = request.params as { needId: string };
137
+ return await c.service.acceptDelivery(needId);
138
+ });
139
+
140
+ app.post('/api/society/needs/:needId/revision', async (request) => {
141
+ const { needId } = request.params as { needId: string };
142
+ return await c.service.requestRevision(needId);
143
+ });
144
+
145
+ app.post('/api/society/needs/:needId/cancel', async (request) => {
146
+ const { needId } = request.params as { needId: string };
147
+ return await c.service.cancelNeed(needId);
148
+ });
149
+
150
+ // ── 自治驱动(去中心化:让 worker 主动自荐,反派单)─────────────────────
151
+ app.post('/api/society/autonomy/tick', async (request) => {
152
+ const body = (request.body ?? {}) as Record<string, unknown>;
153
+ const numOrUndef = (k: string): number | undefined =>
154
+ typeof body[k] === 'number' ? (body[k] as number) : undefined;
155
+ const applied = await c.service.runAutonomyTick({
156
+ maxVolunteersPerNeed: numOrUndef('maxVolunteersPerNeed'),
157
+ maxNeedsPerWorker: numOrUndef('maxNeedsPerWorker'),
158
+ });
159
+ return { ok: true, applied };
160
+ });
161
+
162
+ app.post('/api/society/autonomy/auto-select', async () => {
163
+ const selected = await c.service.autoSelectPending();
164
+ return { ok: true, selected };
165
+ });
166
+
167
+ // ── 社交:关系 / 消息 / 活动流 ─────────────────────────────────────────
168
+ app.get('/api/society/relationships', async () => {
169
+ try {
170
+ return await c.relationships.list();
171
+ } catch {
172
+ return [];
173
+ }
174
+ });
175
+
176
+ app.post('/api/society/messages', async (request, reply) => {
177
+ const body = (request.body ?? {}) as Record<string, unknown>;
178
+ const fromWorker = String(body.fromWorker ?? '').trim();
179
+ const toWorker = String(body.toWorker ?? '').trim();
180
+ const text = String(body.text ?? '').trim();
181
+ if (!fromWorker || !toWorker || !text) {
182
+ return reply.code(400).send({ error: 'fromWorker, toWorker and text required' });
183
+ }
184
+ return await c.service.sendSocialMessage(fromWorker, toWorker, text);
185
+ });
186
+
187
+ app.get('/api/society/feed', async () => {
188
+ try {
189
+ return await c.gateway.recent(50);
190
+ } catch {
191
+ return [];
192
+ }
193
+ });
194
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Composition root 集成测试 —— 证明真实适配器(FS store + cross-team 网关 + 系统时钟)
3
+ * 组合后能跑通完整自治流程,且状态跨「重启」(新建实例)持久化。
4
+ *
5
+ * 这是 worker-society 从纯库走向「持久化社交平台」的关键证据。
6
+ */
7
+ import { mkdtemp, rm } from 'node:fs/promises';
8
+ import { homedir, tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
10
+
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
+
13
+ import { createWorkerSociety, defaultSocietyRoot } from './societyComposition';
14
+
15
+ describe('createWorkerSociety (composition root)', () => {
16
+ let root: string;
17
+ beforeEach(async () => {
18
+ root = await mkdtemp(join(tmpdir(), 'ws-comp-'));
19
+ });
20
+ afterEach(async () => {
21
+ await rm(root, { recursive: true, force: true });
22
+ });
23
+
24
+ it('wires a full self-organization flow that persists across a simulated restart', async () => {
25
+ const { service } = createWorkerSociety(root);
26
+ await service.registerProfile({
27
+ workerId: 'poster',
28
+ name: 'Poster',
29
+ capabilities: [{ skill: 'pm', description: 'pm' }],
30
+ });
31
+ await service.registerProfile({
32
+ workerId: 'dev',
33
+ name: 'Dev',
34
+ capabilities: [{ skill: 'code', description: 'code' }],
35
+ reputation: 60,
36
+ });
37
+
38
+ const { need } = await service.publishNeed({
39
+ postedBy: 'poster',
40
+ subject: 'build feature X',
41
+ requiredCapabilities: ['code'],
42
+ });
43
+ await service.volunteerFor(need.needId, 'dev');
44
+ await service.selectAssignee(need.needId);
45
+ await service.startNeed(need.needId, 'dev');
46
+ await service.deliverNeed(need.needId, 'v1');
47
+ await service.acceptDelivery(need.needId);
48
+
49
+ // 新实例模拟重启:dev 声誉上升、关系形成、需求关闭 —— 全部已落盘。
50
+ const { service: reloaded, gateway } = createWorkerSociety(root);
51
+ const dev = await reloaded.getProfile('dev');
52
+ expect(dev?.reputation).toBeGreaterThan(60);
53
+ expect((await reloaded.getRelationships()).length).toBeGreaterThan(0);
54
+ const closedNeed = await reloaded.getProfile('poster'); // 触发一次 store 读
55
+ expect(closedNeed).toBeTruthy();
56
+ // 自荐/选派消息经 cross-team 网关持久化到 messages.jsonl
57
+ expect((await gateway.recent(10)).length).toBeGreaterThan(0);
58
+ });
59
+
60
+ it('exposes stores + gateway + service from a single root', () => {
61
+ const c = createWorkerSociety(root);
62
+ expect(c.service).toBeTruthy();
63
+ expect(c.gateway).toBeTruthy();
64
+ expect(c.profiles).toBeTruthy();
65
+ expect(c.needs).toBeTruthy();
66
+ expect(c.relationships).toBeTruthy();
67
+ });
68
+
69
+ it('defaultSocietyRoot resolves to ~/.hermit/society (the canonical on-disk data root)', () => {
70
+ // createWorkerSociety 的默认参数 = defaultSocietyRoot(),但既有测试都传显式 tmpdir →
71
+ // 该导出从未被调用(FNDA:0)。这是声誉/关系/需求/消息跨重启落盘的规范路径,锁定防漂移。
72
+ expect(defaultSocietyRoot()).toBe(join(homedir(), '.hermit', 'society'));
73
+ });
74
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Worker Society — 组合根(composition root)。
3
+ *
4
+ * 把领域/应用层与基础设施层装配成一个可被 server.ts / MCP / 前端调用的活实例:
5
+ * - FS store(~/.hermit/society/*.json)让声誉/关系/需求持久。
6
+ * - CrossTeamMessageGateway 让 worker 间消息走 hermit cross-team 协议并持久化。
7
+ * - SystemClock 提供真实时间(应用层允许使用 Date;只有 workflow 脚本受限)。
8
+ *
9
+ * 镜像 server.ts:440-441(taskDispatch 构造)的注入式构造风格。
10
+ * rootDir 可注入(生产默认 ~/.hermit/society,测试用临时目录)。
11
+ */
12
+ import { homedir } from 'node:os';
13
+ import { join } from 'node:path';
14
+
15
+ import type { DiscoverableWorker } from '@shared/types/worker';
16
+
17
+ import type { ClockPort, WorkerProfileStore } from '../../core/application/ports';
18
+ import { WorkerSocietyService } from '../../core/application/WorkerSocietyService';
19
+ import { CrossTeamMessageGateway } from '../infrastructure/crossTeamMessageGateway';
20
+ import { MergingProfileStore } from '../infrastructure/mergingProfileStore';
21
+ import { FsNeedStore, FsProfileStore, FsRelationshipStore } from '../infrastructure/fsStores';
22
+
23
+ /** 默认社会数据根目录:~/.hermit/society。 */
24
+ export function defaultSocietyRoot(): string {
25
+ return join(homedir(), '.hermit', 'society');
26
+ }
27
+
28
+ /** 真实系统时钟(ISO 字符串)。 */
29
+ class SystemClock implements ClockPort {
30
+ now(): string {
31
+ return new Date().toISOString();
32
+ }
33
+ }
34
+
35
+ export interface SocietyComponents {
36
+ service: WorkerSocietyService;
37
+ gateway: CrossTeamMessageGateway;
38
+ /** 花名册存储:注入 realWorkersProvider 时为 MergingProfileStore(真实员工 + overlay),否则 FsProfileStore。 */
39
+ profiles: WorkerProfileStore;
40
+ needs: FsNeedStore;
41
+ relationships: FsRelationshipStore;
42
+ }
43
+
44
+ /**
45
+ * 真实员工来源(注入点)。society 的成员花名册以 hermit 真实数字员工为单一事实源——
46
+ * 由 server.ts 注入 listDiscoverableWorkers(GET /api/workers 同款),让社会层身份与真实团队一致;
47
+ * 社会属性(能力/声誉/并发)由 ~/.hermit/society/profiles.json overlay 叠加(见 MergingProfileStore)。
48
+ * 缺省不注入 → 退化为纯 FsProfileStore(既有单测 / 离线场景零变化)。
49
+ */
50
+ export interface WorkerSocietyDepsInput {
51
+ realWorkersProvider?: () => Promise<DiscoverableWorker[]>;
52
+ }
53
+
54
+ /** 装配 worker-society 全栈。同一 rootDir 可多次调用以「重载」磁盘状态。 */
55
+ export function createWorkerSociety(
56
+ rootDir: string = defaultSocietyRoot(),
57
+ deps: WorkerSocietyDepsInput = {}
58
+ ): SocietyComponents {
59
+ const clock = new SystemClock();
60
+ // 注入了 realWorkersProvider → 用 MergingProfileStore 把真实员工与 overlay 合并;
61
+ // 否则用纯 FsProfileStore(离线/测试场景,成员即 profiles.json 本身,零行为变化)。
62
+ const profiles: WorkerProfileStore = deps.realWorkersProvider
63
+ ? new MergingProfileStore(new FsProfileStore(rootDir), deps.realWorkersProvider)
64
+ : new FsProfileStore(rootDir);
65
+ const needs = new FsNeedStore(rootDir);
66
+ const relationships = new FsRelationshipStore(rootDir);
67
+ const gateway = new CrossTeamMessageGateway(rootDir, clock);
68
+ const service = new WorkerSocietyService(profiles, needs, relationships, gateway, clock);
69
+ return { service, gateway, profiles, needs, relationships };
70
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * workerSocietyPlugin —— 插件描述符单测(test-first)。
3
+ *
4
+ * 锁定:稳定 id、指向 hermit /mcp 的 HTTP-SSE 端点、工具列表与 SOCIETY_MCP_TOOLS 同源
5
+ * (无漂移)、library 条目形状匹配 McpLibraryService.upsert 契约。
6
+ */
7
+ import { describe, expect, it } from 'vitest';
8
+
9
+ import { SOCIETY_MCP_TOOLS } from '../adapters/input/societyMcp';
10
+ import {
11
+ WORKER_SOCIETY_PLUGIN,
12
+ WORKER_SOCIETY_PLUGIN_ID,
13
+ buildWorkerSocietyMcpLibraryEntry,
14
+ } from './workerSocietyPlugin';
15
+
16
+ describe('WORKER_SOCIETY_PLUGIN descriptor', () => {
17
+ it('has a stable plugin id matching the `openhermit add` key', () => {
18
+ expect(WORKER_SOCIETY_PLUGIN_ID).toBe('worker-society');
19
+ expect(WORKER_SOCIETY_PLUGIN.id).toBe('worker-society');
20
+ });
21
+
22
+ it('points at the hermit MCP HTTP-SSE endpoint', () => {
23
+ expect(WORKER_SOCIETY_PLUGIN.kind).toBe('mcp-library');
24
+ expect(WORKER_SOCIETY_PLUGIN.mcpEndpoint).toBe('/mcp');
25
+ expect(WORKER_SOCIETY_PLUGIN.transportType).toBe('sse');
26
+ });
27
+
28
+ it('exposes the live society_* tool list with no drift vs SOCIETY_MCP_TOOLS', () => {
29
+ expect(WORKER_SOCIETY_PLUGIN.tools).toEqual(SOCIETY_MCP_TOOLS.map((t) => t.name));
30
+ expect(WORKER_SOCIETY_PLUGIN.tools.length).toBeGreaterThan(0);
31
+ expect(WORKER_SOCIETY_PLUGIN.tools).toContain('society_register_worker');
32
+ expect(WORKER_SOCIETY_PLUGIN.tools).toContain('society_run_autonomy_tick');
33
+ expect(WORKER_SOCIETY_PLUGIN.tools).toContain('society_auto_select');
34
+ expect(WORKER_SOCIETY_PLUGIN.tools.every((n) => n.startsWith('society_'))).toBe(true);
35
+ });
36
+
37
+ it('is plain serializable metadata (no functions, safe to log/POST)', () => {
38
+ const json = JSON.parse(JSON.stringify(WORKER_SOCIETY_PLUGIN));
39
+ expect(json.id).toBe('worker-society');
40
+ expect(json.tools).toEqual(WORKER_SOCIETY_PLUGIN.tools);
41
+ });
42
+ });
43
+
44
+ describe('buildWorkerSocietyMcpLibraryEntry', () => {
45
+ it('defaults to 127.0.0.1:5680/mcp over SSE', () => {
46
+ const entry = buildWorkerSocietyMcpLibraryEntry();
47
+ expect(entry.name).toBe('worker-society');
48
+ expect(entry.installSpec).toEqual({
49
+ type: 'http',
50
+ url: 'http://127.0.0.1:5680/mcp',
51
+ transportType: 'sse',
52
+ });
53
+ });
54
+
55
+ it('respects custom host and port', () => {
56
+ const entry = buildWorkerSocietyMcpLibraryEntry('0.0.0.0', 8080);
57
+ expect(entry.installSpec.url).toBe('http://0.0.0.0:8080/mcp');
58
+ expect(entry.installSpec.transportType).toBe('sse');
59
+ });
60
+
61
+ it('matches the McpLibraryService.upsert body shape', () => {
62
+ const entry = buildWorkerSocietyMcpLibraryEntry();
63
+ // upsert 期望:{ name, description?, installSpec: { type, url, transportType } }
64
+ expect(typeof entry.name).toBe('string');
65
+ expect(typeof entry.description).toBe('string');
66
+ expect(entry.installSpec.type).toBe('http');
67
+ expect(typeof entry.installSpec.url).toBe('string');
68
+ });
69
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * workerSocietyPlugin —— 把 worker-society 声明为 hermit 的「可安装插件」。
3
+ *
4
+ * 关键事实:hermit 自己就是 MCP-over-HTTP-SSE 服务端(`GET/POST /mcp`),society_*
5
+ * 工具已经通过该端点对外暴露(server.ts 把 SOCIETY_MCP_TOOLS 接进 /mcp 分发)。
6
+ * 所以「安装 worker 社会」最贴合 hermit 架构的做法 = 把这个端点注册进 hermit 的
7
+ * MCP library(`~/.hermit/mcp-library.json`),用户的 coding agent(Claude Code 等)
8
+ * 即可获得 society_* 工具,参与去中心化自治社会。这正是 hermit「定义一次、给任意
9
+ * worker 启用」的 MCP 安装模型(见 McpLibraryService)。
10
+ *
11
+ * 纯描述符 + 构造器,无副作用,便于单测;`openhermit add worker-society` 据此生成
12
+ * MCP library 条目并 POST 到 `/api/extensions/mcp/library`。
13
+ */
14
+ import { SOCIETY_MCP_TOOLS } from '../adapters/input/societyMcp';
15
+
16
+ /** 稳定插件 id,也是 `openhermit add <id>` 的键。 */
17
+ export const WORKER_SOCIETY_PLUGIN_ID = 'worker-society';
18
+
19
+ export interface WorkerSocietyPluginDescriptor {
20
+ /** 稳定插件 id。 */
21
+ id: string;
22
+ name: string;
23
+ description: string;
24
+ /** 安装种类:注册为 MCP library 条目(HTTP-SSE 服务端)。 */
25
+ kind: 'mcp-library';
26
+ /** society 对外暴露的 MCP 端点路径(挂在 hermit 主服务上)。 */
27
+ mcpEndpoint: string;
28
+ /** MCP 传输类型:HTTP-SSE。 */
29
+ transportType: 'sse';
30
+ /** 该插件给 agent 带来的 MCP 工具名列表(实时取自 SOCIETY_MCP_TOOLS,避免漂移)。 */
31
+ tools: string[];
32
+ }
33
+
34
+ /** worker-society 的插件描述符(纯数据)。 */
35
+ export const WORKER_SOCIETY_PLUGIN: WorkerSocietyPluginDescriptor = {
36
+ id: WORKER_SOCIETY_PLUGIN_ID,
37
+ name: 'worker-society',
38
+ description:
39
+ '去中心化 worker 自治社会:agent 通过 society_* 工具发布需求、自荐、择优选派、积累声誉与关系,替代中心化派单。',
40
+ kind: 'mcp-library',
41
+ mcpEndpoint: '/mcp',
42
+ transportType: 'sse',
43
+ tools: SOCIETY_MCP_TOOLS.map((t) => t.name),
44
+ };
45
+
46
+ /**
47
+ * 构造注册进 MCP library 所需的条目(指向某 host:port 上运行中的 hermit /mcp)。
48
+ * 形状匹配 McpLibraryService.upsert 的 McpLibraryUpsertRequest(不含 id → 新建)。
49
+ */
50
+ export function buildWorkerSocietyMcpLibraryEntry(
51
+ host = '127.0.0.1',
52
+ port = 5680
53
+ ): {
54
+ name: string;
55
+ description: string;
56
+ installSpec: { type: 'http'; url: string; transportType: 'sse' };
57
+ } {
58
+ return {
59
+ name: WORKER_SOCIETY_PLUGIN.name,
60
+ description: WORKER_SOCIETY_PLUGIN.description,
61
+ installSpec: {
62
+ type: 'http',
63
+ url: `http://${host}:${port}${WORKER_SOCIETY_PLUGIN.mcpEndpoint}`,
64
+ transportType: WORKER_SOCIETY_PLUGIN.transportType,
65
+ },
66
+ };
67
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * CrossTeamMessageGateway 测试 —— 消息持久化 + cross-team 协议兼容(TDD 先行)。
3
+ *
4
+ * 关键不变量:
5
+ * - send 持久化到 messages.jsonl 并返回 delivered。
6
+ * - formatted 文本可被 hermit 的 parseCrossTeamPrefix 回解析 → 与既有消息总线兼容。
7
+ * - recent(n) 返回按发送顺序的最后 n 条;跨实例可从磁盘重载(前端活动流所需)。
8
+ */
9
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+
13
+ import { parseCrossTeamPrefix } from '@shared/constants/crossTeam';
14
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
15
+
16
+ import { FakeClock } from '../../core/application/fakes';
17
+ import { CrossTeamMessageGateway } from './crossTeamMessageGateway';
18
+
19
+ describe('CrossTeamMessageGateway', () => {
20
+ let root: string;
21
+ let clock: FakeClock;
22
+ beforeEach(async () => {
23
+ root = await mkdtemp(join(tmpdir(), 'ws-msg-'));
24
+ clock = new FakeClock('2026-06-13T10:00:00.000Z');
25
+ });
26
+ afterEach(async () => {
27
+ await rm(root, { recursive: true, force: true });
28
+ });
29
+
30
+ it('send returns delivered:true and records the message', async () => {
31
+ const gw = new CrossTeamMessageGateway(root, clock);
32
+ const res = await gw.send({ fromWorker: 'a', toWorker: 'b', text: 'hi' });
33
+ expect(res.delivered).toBe(true);
34
+ const recent = await gw.recent(10);
35
+ expect(recent).toHaveLength(1);
36
+ expect(recent[0]).toMatchObject({ fromWorker: 'a', toWorker: 'b', text: 'hi' });
37
+ });
38
+
39
+ it('formats the payload with the hermit cross-team prefix (protocol-compatible)', async () => {
40
+ const gw = new CrossTeamMessageGateway(root, clock);
41
+ await gw.send({ fromWorker: 'alice', toWorker: 'bob', text: 'need a hand?' });
42
+ const [m] = await gw.recent(1);
43
+ const parsed = parseCrossTeamPrefix(m.formatted);
44
+ expect(parsed?.from).toBe('alice');
45
+ expect(parsed?.chainDepth).toBe(0);
46
+ expect(m.formatted).toContain('need a hand?');
47
+ });
48
+
49
+ it('preserves needId when present', async () => {
50
+ const gw = new CrossTeamMessageGateway(root, clock);
51
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: 't', needId: 'need-1' });
52
+ expect((await gw.recent(1))[0].needId).toBe('need-1');
53
+ });
54
+
55
+ it('accumulates messages in send order; recent(n) returns the last n', async () => {
56
+ const gw = new CrossTeamMessageGateway(root, clock);
57
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: '1' });
58
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: '2' });
59
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: '3' });
60
+ expect((await gw.recent(2)).map((m) => m.text)).toEqual(['2', '3']);
61
+ });
62
+
63
+ it('returns empty before any message is sent', async () => {
64
+ const gw = new CrossTeamMessageGateway(root, clock);
65
+ expect(await gw.recent(10)).toEqual([]);
66
+ });
67
+
68
+ it('persists across instances (reload feed from disk)', async () => {
69
+ const a = new CrossTeamMessageGateway(root, clock);
70
+ await a.send({ fromWorker: 'a', toWorker: 'b', text: 'persist me' });
71
+ const b = new CrossTeamMessageGateway(root, clock);
72
+ expect((await b.recent(10)).map((m) => m.text)).toEqual(['persist me']);
73
+ });
74
+
75
+ it('stamps deliveredAt from the injected clock', async () => {
76
+ const gw = new CrossTeamMessageGateway(root, clock);
77
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: 'hi' });
78
+ expect((await gw.recent(1))[0].deliveredAt).toBe('2026-06-13T10:00:00.000Z');
79
+ });
80
+
81
+ it('tolerates a corrupt/partial line without losing the valid history (no silent feed wipe)', async () => {
82
+ // 模拟 append 中途崩溃留下的半行/坏行:messages.jsonl 里混入一行非法 JSON。
83
+ // 旧实现:JSON.parse 抛错 → 外层 catch → 整个 feed 返回 [](全量丢失)。
84
+ // 期望:跳过坏行,保留所有合法记录。
85
+ const gw = new CrossTeamMessageGateway(root, clock);
86
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: 'keep-1' });
87
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: 'keep-2' });
88
+ // 直接往文件里塞一行损坏内容(模拟崩溃后的半行)。
89
+ await writeFile(join(root, 'messages.jsonl'), '{not valid json\n', {
90
+ flag: 'a',
91
+ encoding: 'utf8',
92
+ });
93
+
94
+ const recent = await gw.recent(50);
95
+ expect(recent.map((m) => m.text)).toEqual(['keep-1', 'keep-2']);
96
+ });
97
+
98
+ it('recent(0) / negative / non-finite limit returns [] (never the whole unbounded history)', async () => {
99
+ // 安全不变量:limit<=0 或非法时绝不能回全量(MCP society_get_feed 的 limit 来自 agent,
100
+ // num('0')→0 → recent(0) 旧实现 slice(-0)===slice(0) 会倒出整条历史 = 无界读)。
101
+ const gw = new CrossTeamMessageGateway(root, clock);
102
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: '1' });
103
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: '2' });
104
+
105
+ expect(await gw.recent(0)).toEqual([]);
106
+ expect(await gw.recent(-3)).toEqual([]);
107
+ expect(await gw.recent(Number.NaN)).toEqual([]);
108
+ // 正常 limit 不受影响:
109
+ expect((await gw.recent(2)).map((m) => m.text)).toEqual(['1', '2']);
110
+ });
111
+
112
+ it('falls back to a Date/Math-based id when crypto.randomUUID is unavailable', async () => {
113
+ // L83-84:globalThis.crypto?.randomUUID 缺失时(旧 Node / 受限运行时),randomId() 降级为
114
+ // Date.now()+Math.random() 的 base36 串——仍生成 msg- 前缀、唯一的 id,send 不抛。
115
+ const gw = new CrossTeamMessageGateway(root, clock);
116
+ vi.stubGlobal('crypto', undefined); // 摘掉 crypto.randomUUID → 走 L83 降级臂
117
+ try {
118
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: 'm1' });
119
+ await gw.send({ fromWorker: 'a', toWorker: 'b', text: 'm2' });
120
+ } finally {
121
+ vi.unstubAllGlobals(); // 恢复 crypto,防污染后续测试
122
+ }
123
+ const recent = await gw.recent(10);
124
+ const [id1, id2] = recent.map((m) => m.id);
125
+ expect(id1).toMatch(/^msg-/); // 降级 id 仍带前缀
126
+ // 降级 = msg-<ts36>-<rand36>(2 个 '-'、3 段);UUID = msg-<uuid>(5 个 '-'、6 段)。
127
+ // 段数断言稳健区分降级臂 vs crypto 臂(不靠 hex 字符,免 flaky)。
128
+ expect(id1.split('-')).toHaveLength(3);
129
+ expect(id2.split('-')).toHaveLength(3);
130
+ expect(id1).not.toBe(id2); // 降级 id 仍唯一
131
+ });
132
+ });