@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,84 @@
1
+ /**
2
+ * Worker Society — 基础设施层:cross-team 协议格式化的持久化消息网关。
3
+ *
4
+ * 把应用层 MessageGateway 落地为「调用 hermit 接口」的具象实现:
5
+ * - 用 hermit 的 formatCrossTeamText 给消息打上 cross-team 前缀,使其与既有
6
+ * cross-team 消息总线(parseCrossTeamPrefix)完全兼容——worker 间社交消息可被
7
+ * hermit 现有渲染/中继逻辑识别。
8
+ * - 以 append-only JSONL(messages.jsonl)持久化,供前端活动流 / /api/society/feed 读取。
9
+ * - 注入 ClockPort 保证可测与确定性。
10
+ */
11
+ import { appendFile, mkdir, readFile } from 'node:fs/promises';
12
+ import { dirname, join } from 'node:path';
13
+
14
+ import { formatCrossTeamText } from '@shared/constants/crossTeam';
15
+ import type { ClockPort, MessageGateway, SocialMessageOut } from '../../core/application/ports';
16
+
17
+ /** 一条已持久化的社交消息记录(含 cross-team 格式化文本)。 */
18
+ export interface SocialMessageRecord {
19
+ id: string;
20
+ fromWorker: string;
21
+ toWorker: string;
22
+ text: string;
23
+ /** 经 formatCrossTeamText 格式化的文本,可被 parseCrossTeamPrefix 回解析。 */
24
+ formatted: string;
25
+ needId?: string;
26
+ deliveredAt: string;
27
+ }
28
+
29
+ export class CrossTeamMessageGateway implements MessageGateway {
30
+ private readonly file: string;
31
+ constructor(
32
+ rootDir: string,
33
+ private readonly clock: ClockPort
34
+ ) {
35
+ this.file = join(rootDir, 'messages.jsonl');
36
+ }
37
+
38
+ async send(msg: SocialMessageOut): Promise<{ delivered: boolean }> {
39
+ await mkdir(dirname(this.file), { recursive: true });
40
+ const record: SocialMessageRecord = {
41
+ id: `msg-${randomId()}`,
42
+ fromWorker: msg.fromWorker,
43
+ toWorker: msg.toWorker,
44
+ text: msg.text,
45
+ formatted: formatCrossTeamText(msg.fromWorker, 0, msg.text),
46
+ needId: msg.needId,
47
+ deliveredAt: this.clock.now(),
48
+ };
49
+ await appendFile(this.file, `${JSON.stringify(record)}\n`, 'utf8');
50
+ return { delivered: true };
51
+ }
52
+
53
+ /** 读取全部持久化消息(按发送顺序)。文件缺失返回空;单行损坏(崩溃残留的半行)被跳过,不丢失其余合法记录。 */
54
+ async all(): Promise<SocialMessageRecord[]> {
55
+ try {
56
+ const raw = await readFile(this.file, 'utf8');
57
+ const records: SocialMessageRecord[] = [];
58
+ for (const line of raw.split('\n')) {
59
+ if (!line.trim()) continue;
60
+ try {
61
+ records.push(JSON.parse(line) as SocialMessageRecord);
62
+ } catch {
63
+ // 跳过损坏/半行(append 中途崩溃的残留),不因一行坏数据抹掉整个 feed。
64
+ }
65
+ }
66
+ return records;
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+
72
+ /** 读取最近的 N 条消息(供前端活动流)。limit<=0 或非有限数返回空——绝不回全量(防无界读)。 */
73
+ async recent(limit: number): Promise<SocialMessageRecord[]> {
74
+ if (!Number.isFinite(limit) || limit <= 0) return [];
75
+ const all = await this.all();
76
+ return all.slice(-limit);
77
+ }
78
+ }
79
+
80
+ function randomId(): string {
81
+ const g = globalThis as { crypto?: { randomUUID?: () => string } };
82
+ if (g.crypto?.randomUUID) return g.crypto.randomUUID();
83
+ return `${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;
84
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * FsStores 测试 —— 基础设施层持久化(TDD 先行)。
3
+ *
4
+ * 用 os.tmpdir() 隔离每个用例,断言:
5
+ * - 基本增删查(get / list / upsert / delete / listOpen / bulkSet)
6
+ * - 跨实例持久化(new 一个同 rootDir 的 store 仍能读到磁盘数据)—— 这是「社交平台」声誉/关系
7
+ * 跨重启存活的核心不变量。
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 { afterEach, beforeEach, describe, expect, it } from 'vitest';
14
+
15
+ import type { PublishedNeed, Relationship, WorkerProfile } from '../../core/domain/models/society';
16
+ import { FsNeedStore, FsProfileStore, FsRelationshipStore } from './fsStores';
17
+
18
+ function profile(workerId: string, reputation = 50): WorkerProfile {
19
+ return {
20
+ workerId,
21
+ name: workerId.toUpperCase(),
22
+ kind: 'composite',
23
+ capabilities: [],
24
+ interests: [],
25
+ maxConcurrent: 3,
26
+ activeTaskCount: 0,
27
+ reputation,
28
+ status: 'online',
29
+ };
30
+ }
31
+
32
+ function need(needId: string, status: PublishedNeed['status'] = 'open'): PublishedNeed {
33
+ return {
34
+ needId,
35
+ postedBy: 'user',
36
+ subject: needId,
37
+ requiredCapabilities: [],
38
+ priority: 5,
39
+ status,
40
+ volunteers: [],
41
+ createdAt: '2026-06-13T10:00:00.000Z',
42
+ revisionCount: 0,
43
+ };
44
+ }
45
+
46
+ function rel(from: string, to: string): Relationship {
47
+ return {
48
+ fromWorker: from,
49
+ toWorker: to,
50
+ collaborations: 1,
51
+ successes: 1,
52
+ trust: 1,
53
+ lastInteractedAt: '2026-06-13T10:00:00.000Z',
54
+ };
55
+ }
56
+
57
+ describe('FsProfileStore', () => {
58
+ let root: string;
59
+ beforeEach(async () => {
60
+ root = await mkdtemp(join(tmpdir(), 'ws-profile-'));
61
+ });
62
+ afterEach(async () => {
63
+ await rm(root, { recursive: true, force: true });
64
+ });
65
+
66
+ it('returns undefined for a missing worker', async () => {
67
+ const store = new FsProfileStore(root);
68
+ expect(await store.get('ghost')).toBeUndefined();
69
+ });
70
+ it('upserts and retrieves a profile', async () => {
71
+ const store = new FsProfileStore(root);
72
+ await store.upsert(profile('w1', 60));
73
+ const got = await store.get('w1');
74
+ expect(got?.workerId).toBe('w1');
75
+ expect(got?.reputation).toBe(60);
76
+ });
77
+ it('upsert overwrites an existing profile', async () => {
78
+ const store = new FsProfileStore(root);
79
+ await store.upsert(profile('w1', 50));
80
+ await store.upsert({ ...profile('w1'), name: 'Renamed', reputation: 90 });
81
+ const got = await store.get('w1');
82
+ expect(got?.name).toBe('Renamed');
83
+ expect(got?.reputation).toBe(90);
84
+ });
85
+ it('lists all profiles', async () => {
86
+ const store = new FsProfileStore(root);
87
+ await store.upsert(profile('a'));
88
+ await store.upsert(profile('b'));
89
+ const list = await store.list();
90
+ expect(list.map((p) => p.workerId).sort()).toEqual(['a', 'b']);
91
+ });
92
+ it('deletes a profile', async () => {
93
+ const store = new FsProfileStore(root);
94
+ await store.upsert(profile('w1'));
95
+ await store.delete('w1');
96
+ expect(await store.get('w1')).toBeUndefined();
97
+ });
98
+ it('delete is a no-op for a worker that was never stored (idempotent)', async () => {
99
+ // L65 真臂 `if (!(workerId in map)) return`:删一个不存在的 worker(未注册/已删过)应是
100
+ // 幂等 no-op——不抛、不写、不误伤既有数据。既有 delete 测删的是已存在的 w1(假臂)。
101
+ const store = new FsProfileStore(root);
102
+ await store.upsert(profile('w1'));
103
+ await store.delete('ghost'); // ghost 不在 map → 提前 return,无写
104
+ expect((await store.get('w1'))?.workerId).toBe('w1'); // 既有数据未被误伤
105
+ expect(await store.get('ghost')).toBeUndefined();
106
+ });
107
+ it('persists across instances (reload from disk)', async () => {
108
+ const a = new FsProfileStore(root);
109
+ await a.upsert(profile('w1', 77));
110
+ const b = new FsProfileStore(root);
111
+ expect((await b.get('w1'))?.reputation).toBe(77);
112
+ });
113
+ it('survives a missing/corrupt file by returning empty data', async () => {
114
+ // 全新 rootDir 下文件不存在 → list 不抛、返回空。
115
+ const store = new FsProfileStore(root);
116
+ expect(await store.list()).toEqual([]);
117
+ });
118
+ });
119
+
120
+ describe('FsNeedStore', () => {
121
+ let root: string;
122
+ beforeEach(async () => {
123
+ root = await mkdtemp(join(tmpdir(), 'ws-need-'));
124
+ });
125
+ afterEach(async () => {
126
+ await rm(root, { recursive: true, force: true });
127
+ });
128
+
129
+ it('returns undefined for a missing need', async () => {
130
+ expect(await new FsNeedStore(root).get('nope')).toBeUndefined();
131
+ });
132
+ it('upserts and retrieves a need', async () => {
133
+ const store = new FsNeedStore(root);
134
+ await store.upsert(need('n1'));
135
+ expect((await store.get('n1'))?.subject).toBe('n1');
136
+ });
137
+ it('lists all needs', async () => {
138
+ const store = new FsNeedStore(root);
139
+ await store.upsert(need('n1'));
140
+ await store.upsert(need('n2', 'closed'));
141
+ expect((await store.list()).map((n) => n.needId).sort()).toEqual(['n1', 'n2']);
142
+ });
143
+ it('listOpen returns only open needs', async () => {
144
+ const store = new FsNeedStore(root);
145
+ await store.upsert(need('open1', 'open'));
146
+ await store.upsert(need('closed1', 'closed'));
147
+ await store.upsert(need('prog1', 'in_progress'));
148
+ expect((await store.listOpen()).map((n) => n.needId)).toEqual(['open1']);
149
+ });
150
+ it('listActive returns needs across the open→delivered lifecycle (excludes terminal)', async () => {
151
+ const store = new FsNeedStore(root);
152
+ await store.upsert(need('open1', 'open'));
153
+ await store.upsert(need('asg1', 'assigned'));
154
+ await store.upsert(need('prog1', 'in_progress'));
155
+ await store.upsert(need('deliv1', 'delivered'));
156
+ await store.upsert(need('closed1', 'closed'));
157
+ await store.upsert(need('expired1', 'expired'));
158
+ await store.upsert(need('cancel1', 'cancelled'));
159
+ const active = (await store.listActive()).map((n) => n.needId).sort();
160
+ // 画布要看到完整生命周期:选派后/执行中/待审核都保留;仅排除已终结状态。
161
+ expect(active).toEqual(['asg1', 'deliv1', 'open1', 'prog1']);
162
+ });
163
+ it('upsert overwrites a need (e.g. status transition open→closed)', async () => {
164
+ const store = new FsNeedStore(root);
165
+ await store.upsert(need('n1', 'open'));
166
+ await store.upsert({ ...need('n1'), status: 'closed' });
167
+ expect((await store.get('n1'))?.status).toBe('closed');
168
+ expect(await store.listOpen()).toHaveLength(0);
169
+ });
170
+ it('persists across instances', async () => {
171
+ const a = new FsNeedStore(root);
172
+ await a.upsert(need('n1'));
173
+ const b = new FsNeedStore(root);
174
+ expect((await b.get('n1'))?.needId).toBe('n1');
175
+ });
176
+ });
177
+
178
+ describe('FsRelationshipStore', () => {
179
+ let root: string;
180
+ beforeEach(async () => {
181
+ root = await mkdtemp(join(tmpdir(), 'ws-rel-'));
182
+ });
183
+ afterEach(async () => {
184
+ await rm(root, { recursive: true, force: true });
185
+ });
186
+
187
+ it('starts empty when no file exists', async () => {
188
+ expect(await new FsRelationshipStore(root).list()).toEqual([]);
189
+ });
190
+ it('survives a corrupt relationships.json by returning [] (covers readJson catch branch)', async () => {
191
+ // 写入半截/非法 JSON —— 模拟崩溃导致的半写文件;list 不抛、降级为空。
192
+ await writeFile(join(root, 'relationships.json'), '{ not valid json', 'utf8');
193
+ expect(await new FsRelationshipStore(root).list()).toEqual([]);
194
+ });
195
+ it('bulkSet round-trips relationships', async () => {
196
+ const store = new FsRelationshipStore(root);
197
+ await store.bulkSet([rel('a', 'b'), rel('b', 'c')]);
198
+ expect((await store.list()).map((r) => `${r.fromWorker}->${r.toWorker}`).sort()).toEqual([
199
+ 'a->b',
200
+ 'b->c',
201
+ ]);
202
+ });
203
+ it('bulkSet replaces (not appends) the full set', async () => {
204
+ const store = new FsRelationshipStore(root);
205
+ await store.bulkSet([rel('a', 'b')]);
206
+ await store.bulkSet([rel('c', 'd')]);
207
+ expect(await store.list()).toHaveLength(1);
208
+ expect((await store.list())[0].fromWorker).toBe('c');
209
+ });
210
+ it('persists across instances', async () => {
211
+ const a = new FsRelationshipStore(root);
212
+ await a.bulkSet([rel('a', 'b')]);
213
+ const b = new FsRelationshipStore(root);
214
+ expect(await b.list()).toHaveLength(1);
215
+ });
216
+ });
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Worker Society — 基础设施层:文件系统持久化 store。
3
+ *
4
+ * 把应用层 ports(WorkerProfileStore / NeedStore / RelationshipStore)落地为
5
+ * `~/.hermit/society/` 下的 JSON 文件,使声誉 / 关系 / 需求跨重启持久——
6
+ * 这是「worker 社交平台」区别于一次性内存调度的核心。
7
+ *
8
+ * 设计:
9
+ * - 注入 rootDir(生产用 ~/.hermit/society,测试用 os.tmpdir() 隔离)。
10
+ * - 原子写:先写 .tmp 再 rename,避免半写损坏。
11
+ * - 容错读:文件不存在或损坏时返回空数据,不抛——保证服务在冷启动时可启动。
12
+ * - 不引入任何业务规则(纯 I/O),业务规则全部留在 core/domain。
13
+ */
14
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
15
+ import { dirname, join } from 'node:path';
16
+
17
+ import type { PublishedNeed, Relationship, WorkerProfile } from '../../core/domain/models/society';
18
+ import type {
19
+ NeedStore,
20
+ RelationshipStore,
21
+ WorkerProfileStore,
22
+ } from '../../core/application/ports';
23
+ import { ACTIVE_NEED_STATUSES } from '../../core/domain/policies/societyPolicies';
24
+
25
+ async function readJson<T>(filePath: string, fallback: T): Promise<T> {
26
+ try {
27
+ const raw = await readFile(filePath, 'utf8');
28
+ return JSON.parse(raw) as T;
29
+ } catch {
30
+ // 文件不存在或解析失败 → 返回 fallback,保证冷启动可用。
31
+ return fallback;
32
+ }
33
+ }
34
+
35
+ async function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {
36
+ await mkdir(dirname(filePath), { recursive: true });
37
+ const tmp = `${filePath}.tmp`;
38
+ await writeFile(tmp, JSON.stringify(value, null, 2), 'utf8');
39
+ await rename(tmp, filePath);
40
+ }
41
+
42
+ type ProfileMap = Record<string, WorkerProfile>;
43
+ type NeedMap = Record<string, PublishedNeed>;
44
+
45
+ /** 声誉/能力档案的文件持久化(profiles.json)。 */
46
+ export class FsProfileStore implements WorkerProfileStore {
47
+ private readonly file: string;
48
+ constructor(rootDir: string) {
49
+ this.file = join(rootDir, 'profiles.json');
50
+ }
51
+ async get(workerId: string): Promise<WorkerProfile | undefined> {
52
+ return (await readJson<ProfileMap>(this.file, {}))[workerId];
53
+ }
54
+ async list(): Promise<WorkerProfile[]> {
55
+ return Object.values(await readJson<ProfileMap>(this.file, {}));
56
+ }
57
+ async upsert(profile: WorkerProfile): Promise<WorkerProfile> {
58
+ const map = await readJson<ProfileMap>(this.file, {});
59
+ map[profile.workerId] = profile;
60
+ await writeJsonAtomic(this.file, map);
61
+ return profile;
62
+ }
63
+ async delete(workerId: string): Promise<void> {
64
+ const map = await readJson<ProfileMap>(this.file, {});
65
+ if (!(workerId in map)) return;
66
+ delete map[workerId];
67
+ await writeJsonAtomic(this.file, map);
68
+ }
69
+ }
70
+
71
+ /** 广场需求(任务帖)的文件持久化(needs.json)。 */
72
+ export class FsNeedStore implements NeedStore {
73
+ private readonly file: string;
74
+ constructor(rootDir: string) {
75
+ this.file = join(rootDir, 'needs.json');
76
+ }
77
+ async get(needId: string): Promise<PublishedNeed | undefined> {
78
+ return (await readJson<NeedMap>(this.file, {}))[needId];
79
+ }
80
+ async list(): Promise<PublishedNeed[]> {
81
+ return Object.values(await readJson<NeedMap>(this.file, {}));
82
+ }
83
+ async listOpen(): Promise<PublishedNeed[]> {
84
+ const map = await readJson<NeedMap>(this.file, {});
85
+ return Object.values(map).filter((n) => n.status === 'open');
86
+ }
87
+ async listActive(): Promise<PublishedNeed[]> {
88
+ // 活跃需求 = 仍在交互生命周期内(未终结):选派后/执行中/待审核也保留,
89
+ // 这样画布上的 worker 会在整个生命周期停在任务锚点,而非选派后即丢下任务飘走。
90
+ const map = await readJson<NeedMap>(this.file, {});
91
+ return Object.values(map).filter((n) => ACTIVE_NEED_STATUSES.includes(n.status));
92
+ }
93
+ async upsert(need: PublishedNeed): Promise<PublishedNeed> {
94
+ const map = await readJson<NeedMap>(this.file, {});
95
+ map[need.needId] = need;
96
+ await writeJsonAtomic(this.file, map);
97
+ return need;
98
+ }
99
+ }
100
+
101
+ /** worker 间关系图(有向边)的文件持久化(relationships.json)。 */
102
+ export class FsRelationshipStore implements RelationshipStore {
103
+ private readonly file: string;
104
+ constructor(rootDir: string) {
105
+ this.file = join(rootDir, 'relationships.json');
106
+ }
107
+ async list(): Promise<Relationship[]> {
108
+ return readJson<Relationship[]>(this.file, []);
109
+ }
110
+ async bulkSet(relationships: Relationship[]): Promise<void> {
111
+ await writeJsonAtomic(this.file, relationships);
112
+ }
113
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * MergingProfileStore 测试 —— 「真实数字员工 ↔ 社会层 overlay」合并的不变量。
3
+ *
4
+ * 核心断言:花名册以真实员工为单一事实源;身份/在线态恒取真实,社会属性取 overlay 或默认;
5
+ * overlay 里真实员工已不存在的孤儿(旧假数据)在读取时被丢弃。
6
+ */
7
+ import { describe, expect, it } from 'vitest';
8
+
9
+ import type { DiscoverableWorker } from '@shared/types/worker';
10
+ import type { WorkerProfile } from '../../core/domain/models/society';
11
+
12
+ import { MergingProfileStore, mergeRealWorker } from './mergingProfileStore';
13
+ import type { WorkerProfileStore } from '../../core/application/ports';
14
+
15
+ // ── 工厂 ──────────────────────────────────────────────────────────────────────
16
+
17
+ function realWorker(overrides: Partial<DiscoverableWorker> = {}): DiscoverableWorker {
18
+ return {
19
+ workerId: 'hermit-dev',
20
+ name: 'Hermit 开发',
21
+ kind: 'composite',
22
+ harness: 'claudecode',
23
+ location: 'local',
24
+ status: 'online',
25
+ capabilities: [
26
+ { skill: 'claudecode', description: 'claudecode' },
27
+ { skill: 'general', description: 'general' },
28
+ ],
29
+ description: '本地 hermit 团队',
30
+ workDir: '/Users/yancyyu/code/hermit',
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ function overlay(workerId: string, overrides: Partial<WorkerProfile> = {}): WorkerProfile {
36
+ return {
37
+ workerId,
38
+ name: 'SHOULD BE OVERWRITTEN',
39
+ kind: 'atomic',
40
+ harness: 'SHOULD-BE-OVERWRITTEN',
41
+ capabilities: [],
42
+ interests: [],
43
+ maxConcurrent: 3,
44
+ activeTaskCount: 0,
45
+ reputation: 50,
46
+ status: 'busy',
47
+ ...overrides,
48
+ };
49
+ }
50
+
51
+ /** 内存版 overlay store,供 MergingProfileStore 装饰。 */
52
+ function fakeBase(
53
+ initial: WorkerProfile[] = []
54
+ ): WorkerProfileStore & { data: Map<string, WorkerProfile> } {
55
+ const data = new Map(initial.map((p) => [p.workerId, p]));
56
+ return {
57
+ data,
58
+ get: async (id) => data.get(id),
59
+ list: async () => [...data.values()],
60
+ upsert: async (p) => {
61
+ data.set(p.workerId, p);
62
+ return p;
63
+ },
64
+ delete: async (id) => {
65
+ data.delete(id);
66
+ },
67
+ };
68
+ }
69
+
70
+ // ── mergeRealWorker(纯函数)──────────────────────────────────────────────────
71
+
72
+ describe('mergeRealWorker', () => {
73
+ it('身份字段恒取真实员工(overlay 的 name/kind/harness 被忽略)', () => {
74
+ const m = mergeRealWorker(
75
+ realWorker(),
76
+ overlay('hermit-dev', { name: 'FAKE', kind: 'atomic', harness: 'FAKE' })
77
+ );
78
+ expect(m.workerId).toBe('hermit-dev');
79
+ expect(m.name).toBe('Hermit 开发');
80
+ expect(m.kind).toBe('composite');
81
+ expect(m.harness).toBe('claudecode');
82
+ expect(m.workDir).toBe('/Users/yancyyu/code/hermit'); // 绑定目录恒取真实员工
83
+ expect(m.description).toBe('本地 hermit 团队');
84
+ });
85
+
86
+ it('status 由真实员工决定:online→online', () => {
87
+ expect(mergeRealWorker(realWorker({ status: 'online' })).status).toBe('online');
88
+ });
89
+
90
+ it('status 由真实员工决定:offline(及其它值)→offline,不合成 busy', () => {
91
+ expect(mergeRealWorker(realWorker({ status: 'offline' })).status).toBe('offline');
92
+ // overlay 的 'busy' 不能渗透——真实员工只有 online/offline。
93
+ expect(
94
+ mergeRealWorker(realWorker({ status: 'online' }), overlay('hermit-dev', { status: 'busy' }))
95
+ .status
96
+ ).toBe('online');
97
+ });
98
+
99
+ it('overlay 非空能力时覆盖真实员工的推断能力', () => {
100
+ const m = mergeRealWorker(
101
+ realWorker(),
102
+ overlay('hermit-dev', { capabilities: [{ skill: 'react', description: 'react' }] })
103
+ );
104
+ expect(m.capabilities.map((c) => c.skill)).toEqual(['react']);
105
+ });
106
+
107
+ it('overlay 缺省 / 能力为空时回退到真实员工被推断的能力', () => {
108
+ expect(mergeRealWorker(realWorker()).capabilities.map((c) => c.skill)).toEqual([
109
+ 'claudecode',
110
+ 'general',
111
+ ]);
112
+ expect(
113
+ mergeRealWorker(realWorker(), overlay('hermit-dev', { capabilities: [] })).capabilities.map(
114
+ (c) => c.skill
115
+ )
116
+ ).toEqual(['claudecode', 'general']);
117
+ });
118
+
119
+ it('真实员工无能力且无 overlay → 能力为空(不凭空造)', () => {
120
+ expect(mergeRealWorker(realWorker({ capabilities: undefined })).capabilities).toEqual([]);
121
+ });
122
+
123
+ it('reputation 默认 50;overlay 给值时夹取到 [0,100]', () => {
124
+ expect(mergeRealWorker(realWorker()).reputation).toBe(50);
125
+ expect(
126
+ mergeRealWorker(realWorker(), overlay('hermit-dev', { reputation: 150 })).reputation
127
+ ).toBe(100);
128
+ expect(
129
+ mergeRealWorker(realWorker(), overlay('hermit-dev', { reputation: -20 })).reputation
130
+ ).toBe(0);
131
+ expect(
132
+ mergeRealWorker(realWorker(), overlay('hermit-dev', { reputation: 80 })).reputation
133
+ ).toBe(80);
134
+ });
135
+
136
+ it('maxConcurrent 默认 2;overlay 覆盖', () => {
137
+ expect(mergeRealWorker(realWorker()).maxConcurrent).toBe(2);
138
+ expect(
139
+ mergeRealWorker(realWorker(), overlay('hermit-dev', { maxConcurrent: 5 })).maxConcurrent
140
+ ).toBe(5);
141
+ });
142
+
143
+ it('interests / activeTaskCount 默认空 / 0', () => {
144
+ const m = mergeRealWorker(realWorker());
145
+ expect(m.interests).toEqual([]);
146
+ expect(m.activeTaskCount).toBe(0);
147
+ });
148
+ });
149
+
150
+ // ── MergingProfileStore(list/get 写入语义)────────────────────────────────────
151
+
152
+ describe('MergingProfileStore', () => {
153
+ it('list() 合并真实员工 + overlay,并丢弃 overlay 里的孤儿(假数据)', async () => {
154
+ const base = fakeBase([
155
+ overlay('hermit-dev', { reputation: 77 }), // 真实员工 → 合并
156
+ overlay('frontend', { reputation: 99 }), // 真实列表里没有 → 孤儿丢弃
157
+ ]);
158
+ const store = new MergingProfileStore(base, async () => [realWorker()]);
159
+
160
+ const list = await store.list();
161
+ expect(list).toHaveLength(1);
162
+ expect(list[0].workerId).toBe('hermit-dev');
163
+ expect(list[0].reputation).toBe(77); // overlay 的声誉生效
164
+ expect(list.map((w) => w.workerId)).not.toContain('frontend');
165
+ });
166
+
167
+ it('get() 对真实员工返回合并档案;对非真实员工返回 undefined', async () => {
168
+ const base = fakeBase([overlay('hermit-dev', { maxConcurrent: 4 })]);
169
+ const store = new MergingProfileStore(base, async () => [realWorker()]);
170
+
171
+ const got = await store.get('hermit-dev');
172
+ expect(got?.name).toBe('Hermit 开发'); // 真实身份
173
+ expect(got?.maxConcurrent).toBe(4); // overlay 社会属性
174
+
175
+ expect(await store.get('frontend')).toBeUndefined(); // 非真实 → 不可见
176
+ });
177
+
178
+ it('upsert 只写 overlay(身份字段下次读取被真实员工覆盖)', async () => {
179
+ const base = fakeBase();
180
+ const store = new MergingProfileStore(base, async () => [realWorker()]);
181
+
182
+ await store.upsert(overlay('hermit-dev', { reputation: 88, name: 'STALE-NAME' }));
183
+ expect(base.data.get('hermit-dev')?.reputation).toBe(88); // 写进了 overlay
184
+ const got = await store.get('hermit-dev');
185
+ expect(got?.reputation).toBe(88); // overlay 生效
186
+ expect(got?.name).toBe('Hermit 开发'); // 但身份仍取真实员工(STALE-NAME 被忽略)
187
+ });
188
+
189
+ it('delete 委托给 base', async () => {
190
+ const base = fakeBase([overlay('hermit-dev')]);
191
+ const store = new MergingProfileStore(base, async () => [realWorker()]);
192
+ await store.delete('hermit-dev');
193
+ expect(base.data.has('hermit-dev')).toBe(false);
194
+ });
195
+ });