@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,802 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import { WorkerSocietyService, estimateFit } from './WorkerSocietyService';
4
+ import type { AgentCapability, PublishedNeed, WorkerProfile } from '../domain/models/society';
5
+
6
+ // Fakes(仿 TaskDispatchService.test.ts 的 FakeWorkspace 模式)—— 放在独立 fakes.ts
7
+ // 中以便后续 adapter / renderer 测试复用。
8
+ import {
9
+ FakeClock,
10
+ FakeMessageGateway,
11
+ FakeNeedStore,
12
+ FakeProfileStore,
13
+ FakeRelationshipStore,
14
+ MemoryEventSink,
15
+ } from './fakes';
16
+
17
+ function cap(skill: string): AgentCapability {
18
+ return { skill, description: skill };
19
+ }
20
+
21
+ function makeService() {
22
+ const clock = new FakeClock('2026-06-13T10:00:00.000Z');
23
+ const profiles = new FakeProfileStore();
24
+ const needs = new FakeNeedStore();
25
+ const relationships = new FakeRelationshipStore();
26
+ const messages = new FakeMessageGateway();
27
+ const events = new MemoryEventSink();
28
+ const service = new WorkerSocietyService(profiles, needs, relationships, messages, clock, events);
29
+ return { service, clock, profiles, needs, relationships, messages, events };
30
+ }
31
+
32
+ async function seedTwoWorkers(service: WorkerSocietyService) {
33
+ await service.registerProfile({
34
+ workerId: 'poster',
35
+ name: 'Poster',
36
+ capabilities: [cap('pm')],
37
+ interests: [],
38
+ maxConcurrent: 2,
39
+ });
40
+ await service.registerProfile({
41
+ workerId: 'designer',
42
+ name: 'Designer',
43
+ capabilities: [cap('design'), cap('frontend')],
44
+ interests: ['design'],
45
+ maxConcurrent: 2,
46
+ reputation: 70,
47
+ });
48
+ }
49
+
50
+ // ── 注册 / 发现 ───────────────────────────────────────────────────
51
+
52
+ describe('WorkerSocietyService.registerProfile', () => {
53
+ it('creates a profile with defaults', async () => {
54
+ const { service } = makeService();
55
+ const p = await service.registerProfile({
56
+ workerId: 'w1',
57
+ name: 'W1',
58
+ capabilities: [cap('x')],
59
+ });
60
+ expect(p.workerId).toBe('w1');
61
+ expect(p.reputation).toBe(50);
62
+ expect(p.maxConcurrent).toBe(3);
63
+ expect(p.status).toBe('online');
64
+ });
65
+ it('preserves activeTaskCount/reputation on re-register', async () => {
66
+ const { service, profiles } = makeService();
67
+ await service.registerProfile({ workerId: 'w1', name: 'W1', reputation: 80 });
68
+ await profiles.upsert({ ...(await service.getProfile('w1'))!, activeTaskCount: 2 });
69
+ const updated = await service.registerProfile({ workerId: 'w1', name: 'W1-new' });
70
+ expect(updated.name).toBe('W1-new');
71
+ expect(updated.activeTaskCount).toBe(2);
72
+ expect(updated.reputation).toBe(80);
73
+ });
74
+ it('clamps reputation to [0,100] on register (enforces the domain invariant at the input boundary)', async () => {
75
+ // REPUTATION_MIN/MAX=0/100 是领域不变量;applyReputationDelta 在每次 delta 时夹取,
76
+ // 但 registerProfile(输入边界)此前透传——注册 reputation:150 / -20 会存出界值,
77
+ // 违反不变量(下游 computeFitScore 虽 clamp01 兜底,但持久化的原值仍是错的)。
78
+ const { service } = makeService();
79
+ const hi = await service.registerProfile({ workerId: 'hi', name: 'Hi', reputation: 150 });
80
+ expect(hi.reputation).toBe(100); // 上界夹取
81
+ const lo = await service.registerProfile({ workerId: 'lo', name: 'Lo', reputation: -20 });
82
+ expect(lo.reputation).toBe(0); // 下界夹取
83
+ });
84
+ it('clamps maxConcurrent to a minimum of 1 (0/negative would otherwise brick the worker)', async () => {
85
+ // isAtCapacity = activeTaskCount >= maxConcurrent:maxConcurrent:0 → 0>=0 永真 → worker 被自荐/选派
86
+ // 闸门(isAtCapacity)永久拒之门外,静默不可用。无「maxConcurrent=0=暂停」语义——不可用由 status 建模。
87
+ // loadFairness(societyPolicies L102)已对 maxConcurrent>0 做防御,反证 ≤0 是可达输入。
88
+ const { service } = makeService();
89
+ const zero = await service.registerProfile({
90
+ workerId: 'w0',
91
+ name: 'W0',
92
+ capabilities: [cap('design')],
93
+ maxConcurrent: 0,
94
+ });
95
+ expect(zero.maxConcurrent).toBe(1); // 0 → 1(否则 isAtCapacity 永真)
96
+ const neg = await service.registerProfile({
97
+ workerId: 'wneg',
98
+ name: 'Wneg',
99
+ capabilities: [cap('design')],
100
+ maxConcurrent: -5,
101
+ });
102
+ expect(neg.maxConcurrent).toBe(1); // 负数 → 1
103
+ });
104
+ });
105
+
106
+ describe('WorkerSocietyService.discoverWorkers', () => {
107
+ it('returns workers matching capability, ranked by reputation', async () => {
108
+ const { service } = makeService();
109
+ await service.registerProfile({
110
+ workerId: 'a',
111
+ name: 'A',
112
+ capabilities: [cap('design')],
113
+ reputation: 60,
114
+ });
115
+ await service.registerProfile({
116
+ workerId: 'b',
117
+ name: 'B',
118
+ capabilities: [cap('design')],
119
+ reputation: 90,
120
+ });
121
+ await service.registerProfile({ workerId: 'c', name: 'C', capabilities: [cap('devops')] });
122
+ const res = await service.discoverWorkers({ anyCapability: ['design'] });
123
+ expect(res.map((w) => w.workerId)).toEqual(['b', 'a']);
124
+ });
125
+ });
126
+
127
+ // ── 取消需求 ──────────────────────────────────────────────────────
128
+
129
+ describe('WorkerSocietyService.cancelNeed', () => {
130
+ it('cancels an open need and persists status "cancelled" (neutral — no reputation change)', async () => {
131
+ const { service, needs } = makeService();
132
+ await seedTwoWorkers(service);
133
+ const { need } = await service.publishNeed({
134
+ postedBy: 'poster',
135
+ subject: 'X',
136
+ requiredCapabilities: ['design'],
137
+ });
138
+ const res = await service.cancelNeed(need.needId);
139
+ expect(res).toEqual({ ok: true });
140
+ expect((await needs.get(need.needId))?.status).toBe('cancelled');
141
+ });
142
+
143
+ it('returns need_not_found for an unknown need id', async () => {
144
+ const { service } = makeService();
145
+ expect(await service.cancelNeed('does-not-exist')).toEqual({
146
+ ok: false,
147
+ reason: 'need_not_found',
148
+ });
149
+ });
150
+
151
+ it('refuses to cancel an in-progress need (illegal transition)', async () => {
152
+ const { service } = makeService();
153
+ await seedTwoWorkers(service);
154
+ const { need } = await service.publishNeed({
155
+ postedBy: 'poster',
156
+ subject: 'X',
157
+ requiredCapabilities: ['design'],
158
+ });
159
+ await service.volunteerFor(need.needId, 'designer');
160
+ await service.selectAssignee(need.needId);
161
+ await service.startNeed(need.needId, 'designer');
162
+ const res = await service.cancelNeed(need.needId);
163
+ expect(res.ok).toBe(false);
164
+ expect(res.reason).toBe('illegal:in_progress->cancelled');
165
+ });
166
+ });
167
+
168
+ describe('WorkerSocietyService cancel slot release', () => {
169
+ it('releases the assignee activeTaskCount slot when an assigned need is cancelled', async () => {
170
+ // assigned→cancelled 是合法转换(ALLOWED_TRANSITIONS);selectAssignee 已占用一个并发槽,
171
+ // cancel 必须释放——否则 activeTaskCount 永久虚高,最终 isAtCapacity 误判、worker 接不了新活。
172
+ const { service } = makeService();
173
+ await seedTwoWorkers(service);
174
+ const { need } = await service.publishNeed({
175
+ postedBy: 'poster',
176
+ subject: 'X',
177
+ requiredCapabilities: ['design'],
178
+ });
179
+ await service.volunteerFor(need.needId, 'designer');
180
+ await service.selectAssignee(need.needId); // designer.activeTaskCount 0 → 1(占用槽)
181
+ expect((await service.getProfile('designer'))!.activeTaskCount).toBe(1);
182
+
183
+ expect(await service.cancelNeed(need.needId)).toMatchObject({ ok: true }); // assigned→cancelled
184
+
185
+ expect((await service.getProfile('designer'))!.activeTaskCount).toBe(0); // 槽已释放
186
+ });
187
+ });
188
+
189
+ // ── 自组织全流程 ──────────────────────────────────────────────────
190
+
191
+ describe('WorkerSocietyService self-organization happy path', () => {
192
+ it('publish → volunteer → select → start → deliver → accept closes the loop', async () => {
193
+ const { service, events, needs } = makeService();
194
+ await seedTwoWorkers(service);
195
+
196
+ const { need } = await service.publishNeed({
197
+ postedBy: 'poster',
198
+ subject: 'Hero banner',
199
+ requiredCapabilities: ['design'],
200
+ });
201
+ expect(need.status).toBe('open');
202
+
203
+ const vol = await service.volunteerFor(need.needId, 'designer');
204
+ expect(vol.ok).toBe(true);
205
+
206
+ const sel = await service.selectAssignee(need.needId);
207
+ expect(sel.ok).toBe(true);
208
+ expect(sel.assignee).toBe('designer');
209
+
210
+ expect(await service.startNeed(need.needId, 'designer')).toMatchObject({ ok: true });
211
+ expect(await service.deliverNeed(need.needId, 'banner v1')).toMatchObject({ ok: true });
212
+ expect(await service.acceptDelivery(need.needId)).toMatchObject({ ok: true });
213
+
214
+ const final = await needs.get(need.needId);
215
+ expect(final?.status).toBe('closed');
216
+
217
+ // 声誉上升、并发槽释放
218
+ const designer = await service.getProfile('designer');
219
+ expect(designer?.reputation).toBeGreaterThan(70);
220
+ expect(designer?.activeTaskCount).toBe(0);
221
+
222
+ // 双向关系形成且 trust=1
223
+ const rels = await service.getRelationships();
224
+ const d2p = rels.find((r) => r.fromWorker === 'designer' && r.toWorker === 'poster');
225
+ const p2d = rels.find((r) => r.fromWorker === 'poster' && r.toWorker === 'designer');
226
+ expect(d2p?.trust).toBe(1);
227
+ expect(p2d?.trust).toBe(1);
228
+
229
+ // 事件流覆盖完整生命周期
230
+ const types = events.all().map((e) => e.type);
231
+ expect(types).toContain('need_published');
232
+ expect(types).toContain('volunteered');
233
+ expect(types).toContain('assigned');
234
+ expect(types).toContain('closed');
235
+ expect(types).toContain('relationship_strengthened');
236
+ });
237
+
238
+ it('emits an outbound message to the poster when a worker volunteers', async () => {
239
+ const { service, messages } = makeService();
240
+ await seedTwoWorkers(service);
241
+ const { need } = await service.publishNeed({
242
+ postedBy: 'poster',
243
+ subject: 'X',
244
+ requiredCapabilities: ['design'],
245
+ });
246
+ await service.volunteerFor(need.needId, 'designer');
247
+ expect(messages.sent).toHaveLength(1);
248
+ expect(messages.sent[0]).toMatchObject({ fromWorker: 'designer', toWorker: 'poster' });
249
+ });
250
+ });
251
+
252
+ // ── 守卫 ──────────────────────────────────────────────────────────
253
+
254
+ describe('WorkerSocietyService guards', () => {
255
+ // at-capacity 拒绝由 societyPolicies.volunteerFor 穷举覆盖;此处保留 duplicate 验证 reason 透传。
256
+ it('volunteer fails on duplicate', async () => {
257
+ const { service } = makeService();
258
+ await seedTwoWorkers(service);
259
+ const { need } = await service.publishNeed({
260
+ postedBy: 'poster',
261
+ subject: 'X',
262
+ requiredCapabilities: ['design'],
263
+ });
264
+ await service.volunteerFor(need.needId, 'designer');
265
+ const again = await service.volunteerFor(need.needId, 'designer');
266
+ expect(again.ok).toBe(false);
267
+ expect(again.reason).toBe('already_volunteered');
268
+ });
269
+ it('rejects an unknown worker with worker_not_found (need exists, worker does not)', async () => {
270
+ // L151:need 查找通过(L149)后,profiles.get(workerId) 返回 undefined → worker_not_found。
271
+ // publishNeed 不校验 postedBy,故无需 seed 任何 worker——直接发需求、用脏 workerId 自荐。
272
+ const { service } = makeService();
273
+ const { need } = await service.publishNeed({
274
+ postedBy: 'user',
275
+ subject: 'X',
276
+ requiredCapabilities: ['design'],
277
+ });
278
+ expect(await service.volunteerFor(need.needId, 'ghost-worker')).toEqual({
279
+ ok: false,
280
+ reason: 'worker_not_found',
281
+ });
282
+ });
283
+ it('selectAssignee fails with no eligible volunteer', async () => {
284
+ const { service } = makeService();
285
+ await seedTwoWorkers(service);
286
+ const { need } = await service.publishNeed({
287
+ postedBy: 'poster',
288
+ subject: 'X',
289
+ requiredCapabilities: ['design'],
290
+ });
291
+ const out = await service.selectAssignee(need.needId);
292
+ expect(out.ok).toBe(false);
293
+ expect(out.reason).toBe('no_eligible_volunteer');
294
+ });
295
+ it('start rejects a non-assignee', async () => {
296
+ const { service } = makeService();
297
+ await seedTwoWorkers(service);
298
+ await service.registerProfile({
299
+ workerId: 'other',
300
+ name: 'Other',
301
+ capabilities: [cap('design')],
302
+ });
303
+ const { need } = await service.publishNeed({
304
+ postedBy: 'poster',
305
+ subject: 'X',
306
+ requiredCapabilities: ['design'],
307
+ });
308
+ await service.volunteerFor(need.needId, 'designer');
309
+ await service.selectAssignee(need.needId);
310
+ const out = await service.startNeed(need.needId, 'other');
311
+ expect(out.ok).toBe(false);
312
+ expect(out.reason).toBe('not_assignee');
313
+ });
314
+ it('deliver before start is rejected', async () => {
315
+ const { service } = makeService();
316
+ await seedTwoWorkers(service);
317
+ const { need } = await service.publishNeed({
318
+ postedBy: 'poster',
319
+ subject: 'X',
320
+ requiredCapabilities: ['design'],
321
+ });
322
+ await service.volunteerFor(need.needId, 'designer');
323
+ await service.selectAssignee(need.needId);
324
+ const out = await service.deliverNeed(need.needId, 'too early');
325
+ expect(out.ok).toBe(false);
326
+ });
327
+ // accept-before-deliver 的非法状态迁移由 societyPolicies.transitionNeed 覆盖(service 仅转发结果)。
328
+ });
329
+
330
+ // need_not_found 守卫:每个生命周期方法的第一行都是 `if (!need) return need_not_found`。
331
+ // cancelNeed 的该分支已测(见上 L143),但其余 6 个方法此前只喂真实 needId(测的是别的错误
332
+ // 分支:already_volunteered / no_eligible_volunteer / not_assignee / 非法迁移),从未传入
333
+ // 不存在的 needId —— 输入校验契约(MCP/REST 收到脏 needId 时该回什么)留了 6 处空缺。
334
+ // need 查找在所有方法里都是第一步,故无需 seed 任何 worker / need(最小、零冗余)。
335
+ describe('WorkerSocietyService need_not_found guard on lifecycle methods', () => {
336
+ it('volunteerFor rejects an unknown need', async () => {
337
+ const { service } = makeService();
338
+ expect(await service.volunteerFor('ghost-need', 'designer')).toEqual({
339
+ ok: false,
340
+ reason: 'need_not_found',
341
+ });
342
+ });
343
+ it('selectAssignee rejects an unknown need', async () => {
344
+ const { service } = makeService();
345
+ expect(await service.selectAssignee('ghost-need')).toEqual({
346
+ ok: false,
347
+ reason: 'need_not_found',
348
+ });
349
+ });
350
+ it('startNeed rejects an unknown need', async () => {
351
+ const { service } = makeService();
352
+ expect(await service.startNeed('ghost-need', 'designer')).toEqual({
353
+ ok: false,
354
+ reason: 'need_not_found',
355
+ });
356
+ });
357
+ it('deliverNeed rejects an unknown need', async () => {
358
+ const { service } = makeService();
359
+ expect(await service.deliverNeed('ghost-need', 'done')).toEqual({
360
+ ok: false,
361
+ reason: 'need_not_found',
362
+ });
363
+ });
364
+ it('acceptDelivery rejects an unknown need', async () => {
365
+ const { service } = makeService();
366
+ expect(await service.acceptDelivery('ghost-need')).toEqual({
367
+ ok: false,
368
+ reason: 'need_not_found',
369
+ });
370
+ });
371
+ it('requestRevision rejects an unknown need', async () => {
372
+ const { service } = makeService();
373
+ expect(await service.requestRevision('ghost-need')).toEqual({
374
+ ok: false,
375
+ reason: 'need_not_found',
376
+ });
377
+ });
378
+ });
379
+
380
+ // 非法迁移转发契约:selectAssignee / startNeed / acceptDelivery / requestRevision 在 need 处于
381
+ // 非法状态时,必须把 transitionNeed 的失败 reason 原样转发(不吞、不抛)。deliverNeed 的同源
382
+ // 分支已被「deliver before start」覆盖(L232),这 4 个对称未覆盖(L188/216/248/271)。
383
+ describe('WorkerSocietyService forwards transitionNeed failures (illegal-state contract)', () => {
384
+ it('selectAssignee on an already-assigned need forwards the illegal-transition reason', async () => {
385
+ // assigned→assigned 非法(ALLOWED_TRANSITIONS['assigned']=[in_progress,cancelled])。
386
+ const { service } = makeService();
387
+ await seedTwoWorkers(service);
388
+ const { need } = await service.publishNeed({
389
+ postedBy: 'poster',
390
+ subject: 'X',
391
+ requiredCapabilities: ['design'],
392
+ });
393
+ await service.volunteerFor(need.needId, 'designer');
394
+ await service.selectAssignee(need.needId); // open→assigned
395
+ const again = await service.selectAssignee(need.needId); // assigned→assigned 非法 → L188
396
+ expect(again.ok).toBe(false);
397
+ expect(again.reason).toBe('illegal:assigned->assigned');
398
+ });
399
+ it('startNeed on an already-in-progress need forwards the illegal-transition reason', async () => {
400
+ // 需 assignee===byWorker(过 L214 not_assignee 检查)且 in_progress→in_progress 非法 → L216。
401
+ const { service } = makeService();
402
+ await seedTwoWorkers(service);
403
+ const { need } = await service.publishNeed({
404
+ postedBy: 'poster',
405
+ subject: 'X',
406
+ requiredCapabilities: ['design'],
407
+ });
408
+ await service.volunteerFor(need.needId, 'designer');
409
+ await service.selectAssignee(need.needId);
410
+ await service.startNeed(need.needId, 'designer'); // assigned→in_progress
411
+ const again = await service.startNeed(need.needId, 'designer'); // in_progress→in_progress 非法 → L216
412
+ expect(again.ok).toBe(false);
413
+ expect(again.reason).toBe('illegal:in_progress->in_progress');
414
+ });
415
+ it('acceptDelivery on a not-yet-delivered need forwards the illegal-transition reason', async () => {
416
+ // in_progress→closed 非法(in_progress 只能 →delivered)→ L248。
417
+ const { service } = makeService();
418
+ await seedTwoWorkers(service);
419
+ const { need } = await service.publishNeed({
420
+ postedBy: 'poster',
421
+ subject: 'X',
422
+ requiredCapabilities: ['design'],
423
+ });
424
+ await service.volunteerFor(need.needId, 'designer');
425
+ await service.selectAssignee(need.needId);
426
+ await service.startNeed(need.needId, 'designer'); // →in_progress(未交付)
427
+ const out = await service.acceptDelivery(need.needId); // in_progress→closed 非法 → L248
428
+ expect(out.ok).toBe(false);
429
+ expect(out.reason).toBe('illegal:in_progress->closed');
430
+ });
431
+ it('requestRevision on a not-yet-delivered need forwards the illegal-transition reason', async () => {
432
+ // requestRevision 仅 delivered 态合法(delivered→in_progress);in_progress 上调 → 非法 → L271。
433
+ const { service } = makeService();
434
+ await seedTwoWorkers(service);
435
+ const { need } = await service.publishNeed({
436
+ postedBy: 'poster',
437
+ subject: 'X',
438
+ requiredCapabilities: ['design'],
439
+ });
440
+ await service.volunteerFor(need.needId, 'designer');
441
+ await service.selectAssignee(need.needId);
442
+ await service.startNeed(need.needId, 'designer'); // →in_progress(未交付)
443
+ const out = await service.requestRevision(need.needId); // 非法 → L271
444
+ expect(out.ok).toBe(false);
445
+ expect(out.reason).toMatch(/^illegal:/); // 转发了 transitionNeed 的失败 reason(不吞)
446
+ });
447
+ });
448
+
449
+ // ── 退回 / 过期 ──────────────────────────────────────────────────
450
+
451
+ describe('WorkerSocietyService revision & expiry', () => {
452
+ it('revision returns work to in_progress and lowers reputation', async () => {
453
+ const { service, needs } = makeService();
454
+ await seedTwoWorkers(service);
455
+ const { need } = await service.publishNeed({
456
+ postedBy: 'poster',
457
+ subject: 'X',
458
+ requiredCapabilities: ['design'],
459
+ });
460
+ await service.volunteerFor(need.needId, 'designer');
461
+ await service.selectAssignee(need.needId);
462
+ await service.startNeed(need.needId, 'designer');
463
+ await service.deliverNeed(need.needId, 'v1');
464
+ const before = (await service.getProfile('designer'))!.reputation;
465
+ await service.requestRevision(need.needId);
466
+ const after = (await service.getProfile('designer'))!.reputation;
467
+ expect((await needs.get(need.needId))?.status).toBe('in_progress');
468
+ expect(after).toBeLessThan(before);
469
+ });
470
+ it('revision beyond limit flags escalation', async () => {
471
+ const { service, needs } = makeService();
472
+ await seedTwoWorkers(service);
473
+ const { need } = await service.publishNeed({
474
+ postedBy: 'poster',
475
+ subject: 'X',
476
+ requiredCapabilities: ['design'],
477
+ });
478
+ await service.volunteerFor(need.needId, 'designer');
479
+ await service.selectAssignee(need.needId);
480
+ await service.startNeed(need.needId, 'designer');
481
+ // drive revisionCount to the limit
482
+ for (let i = 0; i <= 3; i++) {
483
+ await service.deliverNeed(need.needId, `v${i}`);
484
+ await service.requestRevision(need.needId);
485
+ }
486
+ // last requestRevision when revisionCount was already 3 → escalated
487
+ // (we just assert the flow doesn't throw and need is back in_progress)
488
+ expect((await needs.get(need.needId))?.status).toBe('in_progress');
489
+ });
490
+ it('expireNeeds sweeps past-deadline open needs', async () => {
491
+ const { service, clock, needs } = makeService();
492
+ await seedTwoWorkers(service);
493
+ const { need } = await service.publishNeed({
494
+ postedBy: 'poster',
495
+ subject: 'X',
496
+ requiredCapabilities: ['design'],
497
+ deadline: '2026-06-13T09:00:00.000Z',
498
+ });
499
+ clock.set('2026-06-13T12:00:00.000Z');
500
+ const n = await service.expireNeeds();
501
+ expect(n).toBe(1);
502
+ expect((await needs.get(need.needId))?.status).toBe('expired');
503
+ });
504
+ });
505
+
506
+ // ── 社交消息 ─────────────────────────────────────────────────────
507
+
508
+ describe('WorkerSocietyService.sendSocialMessage', () => {
509
+ it('delivers a free worker-to-worker message and emits a message event', async () => {
510
+ const { service, messages, events } = makeService();
511
+ await seedTwoWorkers(service);
512
+ const out = await service.sendSocialMessage(
513
+ 'designer',
514
+ 'poster',
515
+ 'hey, can you clarify the brief?'
516
+ );
517
+ expect(out.ok).toBe(true);
518
+ expect(messages.sent).toHaveLength(1);
519
+ expect(events.all().some((e) => e.type === 'message')).toBe(true);
520
+ });
521
+ it('rejects unknown sender', async () => {
522
+ const { service } = makeService();
523
+ const out = await service.sendSocialMessage('nobody', 'poster', 'hi');
524
+ expect(out.ok).toBe(false);
525
+ expect(out.reason).toBe('worker_not_found');
526
+ });
527
+ it('delivers a message from the human operator "user" (not a registered worker)', async () => {
528
+ // 'user' 是人类操作者的约定 id(图谱 overlay「发消息」from='user'、need 的 postedBy='user')。
529
+ // 它不是注册 worker,但作为消息发送方必须被允许——否则点 worker「发消息」会静默失败。
530
+ const { service, messages, events } = makeService();
531
+ await seedTwoWorkers(service);
532
+ const out = await service.sendSocialMessage('user', 'designer', 'please review');
533
+ expect(out.ok).toBe(true);
534
+ expect(messages.sent).toHaveLength(1);
535
+ expect(events.all().some((e) => e.type === 'message')).toBe(true);
536
+ });
537
+ });
538
+
539
+ // ── 自治驱动(runAutonomyTick)────────────────────────────────────
540
+
541
+ describe('WorkerSocietyService.runAutonomyTick', () => {
542
+ it('makes matching online workers autonomously volunteer for open needs', async () => {
543
+ const { service, needs } = makeService();
544
+ await seedTwoWorkers(service); // poster(pm) + designer(design,frontend, rep 70)
545
+ const { need } = await service.publishNeed({
546
+ postedBy: 'poster',
547
+ subject: 'Hero banner',
548
+ requiredCapabilities: ['design'],
549
+ });
550
+ const applied = await service.runAutonomyTick();
551
+ expect(applied).toBeGreaterThanOrEqual(1);
552
+ const updated = await needs.get(need.needId);
553
+ expect(updated?.volunteers.map((v) => v.workerId)).toContain('designer');
554
+ });
555
+
556
+ // 决策边界场景(no-op / at-capacity / per-need cap)已由 societyPolicies.test.ts
557
+ // 的 autonomousVolunteers 用例穷举覆盖;service 层 runAutonomyTick 为纯委托 + 落库
558
+ // (见 WorkerSocietyService L307-319),仅保留持久化与选项透传(maxNeedsPerWorker)用例。
559
+
560
+ it('applies maxNeedsPerWorker so one worker can volunteer for multiple needs in a tick', async () => {
561
+ const { service, needs } = makeService();
562
+ await seedTwoWorkers(service); // designer 具 design + frontend
563
+ const { need: a } = await service.publishNeed({
564
+ postedBy: 'poster',
565
+ subject: 'A',
566
+ requiredCapabilities: ['design'],
567
+ });
568
+ const { need: b } = await service.publishNeed({
569
+ postedBy: 'poster',
570
+ subject: 'B',
571
+ requiredCapabilities: ['frontend'],
572
+ });
573
+ const applied = await service.runAutonomyTick({ maxNeedsPerWorker: 2 });
574
+ expect(applied).toBe(2); // designer 同时认领两个需求
575
+ expect((await needs.get(a.needId))?.volunteers.map((v) => v.workerId)).toContain('designer');
576
+ expect((await needs.get(b.needId))?.volunteers.map((v) => v.workerId)).toContain('designer');
577
+ });
578
+ });
579
+
580
+ // ── 自治选派(autoSelectPending)──────────────────────────────────
581
+
582
+ describe('WorkerSocietyService.autoSelectPending', () => {
583
+ it('auto-selects the best volunteer for each open need that has volunteers', async () => {
584
+ const { service, needs } = makeService();
585
+ await seedTwoWorkers(service); // designer 不具备 video
586
+ await service.registerProfile({
587
+ workerId: 'hi',
588
+ name: 'Hi',
589
+ capabilities: [cap('video')],
590
+ reputation: 80,
591
+ });
592
+ await service.registerProfile({
593
+ workerId: 'lo',
594
+ name: 'Lo',
595
+ capabilities: [cap('video')],
596
+ reputation: 30,
597
+ });
598
+ const { need } = await service.publishNeed({
599
+ postedBy: 'poster',
600
+ subject: 'Edit video',
601
+ requiredCapabilities: ['video'],
602
+ });
603
+ await service.volunteerFor(need.needId, 'hi');
604
+ await service.volunteerFor(need.needId, 'lo');
605
+
606
+ expect(await service.autoSelectPending()).toBe(1);
607
+ const got = await needs.get(need.needId);
608
+ expect(got?.status).toBe('assigned');
609
+ expect(got?.assignee).toBe('hi'); // rep 80 > 30(无兴趣混淆)→ 最高适配胜出
610
+ });
611
+
612
+ it('skips open needs with no volunteers', async () => {
613
+ const { service } = makeService();
614
+ await seedTwoWorkers(service);
615
+ await service.publishNeed({
616
+ postedBy: 'poster',
617
+ subject: 'X',
618
+ requiredCapabilities: ['design'],
619
+ });
620
+ expect(await service.autoSelectPending()).toBe(0);
621
+ });
622
+
623
+ it('is idempotent: does not re-select a need that is no longer open', async () => {
624
+ const { service } = makeService();
625
+ await seedTwoWorkers(service);
626
+ const { need } = await service.publishNeed({
627
+ postedBy: 'poster',
628
+ subject: 'X',
629
+ requiredCapabilities: ['design'],
630
+ });
631
+ await service.volunteerFor(need.needId, 'designer');
632
+ expect(await service.autoSelectPending()).toBe(1); // open → assigned
633
+ expect(await service.autoSelectPending()).toBe(0); // 已 assigned,不再 open,跳过
634
+ });
635
+
636
+ it('selects across multiple needs in one sweep', async () => {
637
+ const { service } = makeService();
638
+ await seedTwoWorkers(service);
639
+ const { need: a } = await service.publishNeed({
640
+ postedBy: 'poster',
641
+ subject: 'A',
642
+ requiredCapabilities: ['design'],
643
+ });
644
+ const { need: b } = await service.publishNeed({
645
+ postedBy: 'poster',
646
+ subject: 'B',
647
+ requiredCapabilities: ['frontend'],
648
+ });
649
+ await service.volunteerFor(a.needId, 'designer');
650
+ await service.volunteerFor(b.needId, 'designer');
651
+ expect(await service.autoSelectPending()).toBe(2);
652
+ });
653
+
654
+ it('does not over-allocate: a maxConcurrent=1 worker best for two needs gets only one in a single sweep', async () => {
655
+ // 不变式:selectAssignee 每轮重读 profiles.list()(WorkerSocietyService L177),故处理第二个 need 时
656
+ // 看到 solo 已满载(activeTaskCount=1 >= maxConcurrent=1)→ 从合格集合剔除 → 不再选派。
657
+ // 若有人把它「优化」成在 autoSelectPending 外缓存一次 profile 列表,solo 会被超额分配
658
+ // (activeTaskCount=2 > maxConcurrent=1)——本用例锁住这个微妙正确性,防静默回归。
659
+ // 注:characterization(绿现),非 bug;与 iter-3/7/8 同类。
660
+ const { service, needs } = makeService();
661
+ await service.registerProfile({
662
+ workerId: 'poster',
663
+ name: 'Poster',
664
+ capabilities: [cap('pm')],
665
+ });
666
+ await service.registerProfile({
667
+ workerId: 'solo',
668
+ name: 'Solo',
669
+ capabilities: [cap('design'), cap('frontend')],
670
+ reputation: 90,
671
+ maxConcurrent: 1,
672
+ });
673
+ const { need: a } = await service.publishNeed({
674
+ postedBy: 'poster',
675
+ subject: 'A',
676
+ requiredCapabilities: ['design'],
677
+ });
678
+ const { need: b } = await service.publishNeed({
679
+ postedBy: 'poster',
680
+ subject: 'B',
681
+ requiredCapabilities: ['frontend'],
682
+ });
683
+ await service.volunteerFor(a.needId, 'solo');
684
+ await service.volunteerFor(b.needId, 'solo');
685
+
686
+ expect(await service.autoSelectPending()).toBe(1); // solo 只接 1 个(maxConcurrent=1)
687
+ expect((await service.getProfile('solo'))!.activeTaskCount).toBe(1); // 未超额分配
688
+ // 另一个 need 仍 open(solo 满载、无他人可选)
689
+ const statuses = [await needs.get(a.needId), await needs.get(b.needId)].map((n) => n!.status);
690
+ expect(statuses.sort()).toEqual(['assigned', 'open']);
691
+ });
692
+ });
693
+
694
+ // ── 完整自治回路(端到端)────────────────────────────────────────
695
+
696
+ describe('WorkerSocietyService full autonomous flow', () => {
697
+ it('tick (volunteer) → auto-select → start → deliver → accept, no human dispatcher', async () => {
698
+ const { service, needs } = makeService();
699
+ await seedTwoWorkers(service);
700
+ const { need } = await service.publishNeed({
701
+ postedBy: 'poster',
702
+ subject: 'Hero banner',
703
+ requiredCapabilities: ['design'],
704
+ });
705
+
706
+ // 自治两步:worker 自发投标 → 按适配度选派(全程无人工指派)。
707
+ expect(await service.runAutonomyTick()).toBeGreaterThanOrEqual(1);
708
+ expect(await service.autoSelectPending()).toBeGreaterThanOrEqual(1);
709
+
710
+ const assigned = await needs.get(need.needId);
711
+ expect(assigned?.status).toBe('assigned');
712
+ const assignee = assigned!.assignee!;
713
+
714
+ // 被选派者自主推进执行 → 交付 → 审核。
715
+ expect(await service.startNeed(need.needId, assignee)).toMatchObject({ ok: true });
716
+ expect(await service.deliverNeed(need.needId, 'banner v1')).toMatchObject({ ok: true });
717
+ expect(await service.acceptDelivery(need.needId)).toMatchObject({ ok: true });
718
+ expect((await needs.get(need.needId))?.status).toBe('closed');
719
+ });
720
+ });
721
+
722
+ describe('estimateFit', () => {
723
+ // estimateFit 是 computeFitScore 的 .score 便捷包装(供前端预估,零依赖纯函数)——
724
+ // 此前零调用、零测试。用「能力匹配者适配度严格高于不匹配者」这一非循环性质,锁定它正确委托 computeFitScore。
725
+ const profile = (capabilities: AgentCapability[]): WorkerProfile => ({
726
+ workerId: 'w',
727
+ name: 'W',
728
+ kind: 'atomic',
729
+ capabilities,
730
+ interests: [],
731
+ maxConcurrent: 3,
732
+ activeTaskCount: 0,
733
+ reputation: 50,
734
+ status: 'online',
735
+ });
736
+ const need: PublishedNeed = {
737
+ needId: 'n',
738
+ postedBy: 'user',
739
+ subject: 's',
740
+ requiredCapabilities: ['design'],
741
+ priority: 5,
742
+ status: 'open',
743
+ volunteers: [],
744
+ createdAt: '2026-01-01',
745
+ revisionCount: 0,
746
+ };
747
+
748
+ it('scores a capability-matching worker strictly higher than a non-matching one', () => {
749
+ // 两 worker 仅能力不同(其余负载/声誉/关系/兴趣全同),分差 = capability 权重 × 匹配差 > 0。
750
+ const match = estimateFit(need, profile([cap('design')]));
751
+ const miss = estimateFit(need, profile([cap('devops')]));
752
+ expect(match).toBeGreaterThan(0);
753
+ expect(match).toBeLessThanOrEqual(1);
754
+ expect(match).toBeGreaterThan(miss);
755
+ });
756
+ });
757
+
758
+ describe('WorkerSocietyService.volunteerFor note', () => {
759
+ // L168-170 的 `note ? '…备注:{note}' : '…'` 真臂——经 grep 确认零测试传 note,补 characterization。
760
+ it('includes the note in the volunteer message when one is provided', async () => {
761
+ const { service, messages } = makeService();
762
+ await seedTwoWorkers(service);
763
+ const { need } = await service.publishNeed({
764
+ postedBy: 'poster',
765
+ subject: '带备注的需求',
766
+ requiredCapabilities: ['design'],
767
+ });
768
+ const out = await service.volunteerFor(need.needId, 'designer', '想做这块');
769
+ expect(out).toMatchObject({ ok: true });
770
+ expect(messages.sent.some((m) => m.text.includes('备注:想做这块'))).toBe(true);
771
+ });
772
+ });
773
+
774
+ // crypto 降级:randomId() L416 在 globalThis.crypto?.randomUUID 缺失时(旧 Node / 受限运行时)
775
+ // 必须降级到 Date.now()+Math.random() 的 base36 串——publishNeed(L122 need + L409 evt 都调 randomId)
776
+ // 仍能生成 need- 前缀、唯一的 id,不抛。与 crossTeamMessageGateway iter-34 同源模式。
777
+ describe('WorkerSocietyService randomId crypto fallback', () => {
778
+ it('falls back to a Date/Math-based need id when crypto.randomUUID is unavailable', async () => {
779
+ const { service } = makeService();
780
+ vi.stubGlobal('crypto', undefined); // 摘掉 crypto.randomUUID → 走 L417 降级臂
781
+ try {
782
+ const { need: a } = await service.publishNeed({
783
+ postedBy: 'user',
784
+ subject: 'a',
785
+ requiredCapabilities: [],
786
+ });
787
+ const { need: b } = await service.publishNeed({
788
+ postedBy: 'user',
789
+ subject: 'b',
790
+ requiredCapabilities: [],
791
+ });
792
+ expect(a.needId).toMatch(/^need-/); // 降级 id 仍带前缀
793
+ // 降级 = need-<ts36>-<rand36>(2 个 '-'、3 段);UUID = need-<uuid>(5 个 '-'、6 段)。
794
+ // 段数断言稳健区分降级臂 vs crypto 臂(不靠 hex 字符,免 flaky)。
795
+ expect(a.needId.split('-')).toHaveLength(3);
796
+ expect(b.needId.split('-')).toHaveLength(3);
797
+ expect(a.needId).not.toBe(b.needId); // 降级 id 仍唯一(Math.random)
798
+ } finally {
799
+ vi.unstubAllGlobals(); // 恢复 crypto,防污染后续测试
800
+ }
801
+ });
802
+ });