@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,321 @@
1
+ /**
2
+ * SocietyGraphAdapter — projection tests (test-first).
3
+ *
4
+ * Verifies the pure mapping from worker-society domain (workers / active needs /
5
+ * relationships) into the reusable @claude-teams/agent-graph port model, so the
6
+ * cyan/space holographic graph can render the decentralized agora.
7
+ *
8
+ * Determinism: no Math.random, no FS/network — same input ⇒ identical output.
9
+ */
10
+ import { describe, expect, it } from 'vitest';
11
+
12
+ import { projectSocietyGraph } from './societyGraphAdapter';
13
+ import type { PublishedNeed, Relationship, WorkerProfile } from '../core/domain/models/society';
14
+
15
+ const TEAM = 'worker-society';
16
+
17
+ function worker(over: Partial<WorkerProfile> & { workerId: string }): WorkerProfile {
18
+ return {
19
+ name: over.workerId,
20
+ kind: 'atomic',
21
+ capabilities: [],
22
+ interests: [],
23
+ maxConcurrent: 2,
24
+ activeTaskCount: 0,
25
+ reputation: 50,
26
+ status: 'online',
27
+ ...over,
28
+ };
29
+ }
30
+
31
+ function need(over: Partial<PublishedNeed> & { needId: string }): PublishedNeed {
32
+ return {
33
+ postedBy: 'user',
34
+ subject: over.subject ?? `need-${over.needId}`,
35
+ requiredCapabilities: [],
36
+ priority: 5,
37
+ status: 'open',
38
+ volunteers: [],
39
+ revisionCount: 0,
40
+ createdAt: '2026-01-01T00:00:00.000Z',
41
+ ...over,
42
+ };
43
+ }
44
+
45
+ const rel = (
46
+ fromWorker: string,
47
+ toWorker: string,
48
+ over: Partial<Relationship> = {}
49
+ ): Relationship => ({
50
+ fromWorker,
51
+ toWorker,
52
+ collaborations: 3,
53
+ successes: 2,
54
+ trust: 0.6,
55
+ lastInteractedAt: '2026-01-01T00:00:00.000Z',
56
+ ...over,
57
+ });
58
+
59
+ describe('projectSocietyGraph', () => {
60
+ it('returns an empty, dead port when there is no society at all', () => {
61
+ const out = projectSocietyGraph({ workers: [], needs: [], relationships: [] });
62
+ expect(out.nodes).toEqual([]);
63
+ expect(out.edges).toEqual([]);
64
+ expect(out.particles).toEqual([]);
65
+ expect(out.teamName).toBe(TEAM);
66
+ expect(out.isAlive).toBe(false);
67
+ });
68
+
69
+ it('places a synthetic Agora hub at the center and parents every worker to it', () => {
70
+ const out = projectSocietyGraph({
71
+ workers: [worker({ workerId: 'alice' }), worker({ workerId: 'bob' })],
72
+ needs: [],
73
+ relationships: [],
74
+ });
75
+ const agora = out.nodes.find((n) => n.kind === 'lead');
76
+ expect(agora).toMatchObject({ kind: 'lead', state: 'active' });
77
+ expect(agora?.id).toBe(`agora:${TEAM}`);
78
+
79
+ // both workers are member nodes
80
+ const members = out.nodes.filter((n) => n.kind === 'member');
81
+ expect(members.map((m) => m.id).sort()).toEqual(['worker:alice', 'worker:bob']);
82
+
83
+ // each worker has a parent-child edge from the agora
84
+ for (const memberId of ['worker:alice', 'worker:bob']) {
85
+ expect(
86
+ out.edges.some(
87
+ (e) => e.source === `agora:${TEAM}` && e.target === memberId && e.type === 'parent-child'
88
+ )
89
+ ).toBe(true);
90
+ }
91
+ expect(out.isAlive).toBe(true);
92
+ });
93
+
94
+ it('maps worker activity to node state: idle when free, active when carrying tasks', () => {
95
+ const out = projectSocietyGraph({
96
+ workers: [
97
+ worker({ workerId: 'idle1', activeTaskCount: 0, status: 'online' }),
98
+ worker({ workerId: 'busy1', activeTaskCount: 1, status: 'online' }),
99
+ worker({ workerId: 'busy2', status: 'busy' }),
100
+ worker({ workerId: 'off1', status: 'offline' }),
101
+ ],
102
+ needs: [],
103
+ relationships: [],
104
+ });
105
+ const state = (id: string) => out.nodes.find((n) => n.id === id)?.state;
106
+ expect(state('worker:idle1')).toBe('idle');
107
+ expect(state('worker:busy1')).toBe('active');
108
+ expect(state('worker:busy2')).toBe('active');
109
+ // offline workers stay visible but idle (not terminated/hidden)
110
+ expect(state('worker:off1')).toBe('idle');
111
+ });
112
+
113
+ it('turns an assigned, in-progress need into a task orbiting its assignee with a flowing particle', () => {
114
+ const out = projectSocietyGraph({
115
+ workers: [worker({ workerId: 'alice' })],
116
+ needs: [need({ needId: 'n1', status: 'in_progress', assignee: 'alice' })],
117
+ relationships: [],
118
+ });
119
+ const task = out.nodes.find((n) => n.id === 'need:n1');
120
+ expect(task).toMatchObject({
121
+ kind: 'task',
122
+ state: 'active',
123
+ ownerId: 'worker:alice',
124
+ taskStatus: 'in_progress',
125
+ });
126
+
127
+ // ownership edge assignee -> task
128
+ const ownEdge = out.edges.find(
129
+ (e) => e.source === 'worker:alice' && e.target === 'need:n1' && e.type === 'ownership'
130
+ );
131
+ expect(ownEdge).toBeTruthy();
132
+
133
+ // a particle travels that edge (work in flight)
134
+ const particle = out.particles.find((p) => p.edgeId === ownEdge!.id);
135
+ expect(particle).toMatchObject({ kind: 'task_assign', progress: 0 });
136
+ expect(particle?.color).toMatch(/^#[0-9a-f]{6}$/i);
137
+ });
138
+
139
+ it('anchors an open, unclaimed need to the agora (not orphaned): waiting task, no owner/particle', () => {
140
+ const out = projectSocietyGraph({
141
+ workers: [worker({ workerId: 'alice' })],
142
+ needs: [need({ needId: 'n-open', status: 'open' })],
143
+ relationships: [],
144
+ });
145
+ const task = out.nodes.find((n) => n.id === 'need:n-open');
146
+ expect(task).toMatchObject({ kind: 'task', state: 'waiting', ownerId: null });
147
+ // 无指派人 → 锚定到广场(parent-child),不再是孤立浮点;但仍无 ownership 边、无粒子。
148
+ expect(
149
+ out.edges.find(
150
+ (e) =>
151
+ e.source === `agora:${TEAM}` && e.target === 'need:n-open' && e.type === 'parent-child'
152
+ )
153
+ ).toBeTruthy();
154
+ expect(out.edges.some((e) => e.type === 'ownership' && e.target === 'need:n-open')).toBe(false);
155
+ expect(out.particles.some((p) => p.edgeId.includes('need:n-open'))).toBe(false);
156
+ });
157
+
158
+ it("joins a visible need's required capabilities into the task sublabel", () => {
159
+ // L166 真臂:`need.requiredCapabilities.length > 0 ? join(' · ') : undefined`。既有可见 need
160
+ // 测试都用 need() 默认 [](假臂→sublabel undefined),真臂(带能力→拼成 sublabel)未覆盖。
161
+ const out = projectSocietyGraph({
162
+ workers: [worker({ workerId: 'alice' })],
163
+ needs: [
164
+ need({ needId: 'n-cap', status: 'open', requiredCapabilities: ['frontend', 'design'] }),
165
+ ],
166
+ relationships: [],
167
+ });
168
+ expect(out.nodes.find((n) => n.id === 'need:n-cap')?.sublabel).toBe('frontend · design');
169
+ });
170
+
171
+ it('maps delivered needs to a complete task state with no in-flight particle', () => {
172
+ const out = projectSocietyGraph({
173
+ workers: [worker({ workerId: 'alice' })],
174
+ needs: [need({ needId: 'n-done', status: 'delivered', assignee: 'alice' })],
175
+ relationships: [],
176
+ });
177
+ const task = out.nodes.find((n) => n.id === 'need:n-done');
178
+ expect(task).toMatchObject({
179
+ kind: 'task',
180
+ state: 'complete',
181
+ taskStatus: 'completed',
182
+ ownerId: 'worker:alice',
183
+ });
184
+ expect(out.particles.length).toBe(0);
185
+ });
186
+
187
+ it('drops closed / expired / cancelled needs entirely', () => {
188
+ const out = projectSocietyGraph({
189
+ workers: [worker({ workerId: 'alice' })],
190
+ needs: [
191
+ need({ needId: 'gone', status: 'closed', assignee: 'alice' }),
192
+ need({ needId: 'stale', status: 'expired' }),
193
+ need({ needId: 'axed', status: 'cancelled' }),
194
+ ],
195
+ relationships: [],
196
+ });
197
+ expect(out.nodes.some((n) => n.id.startsWith('need:'))).toBe(false);
198
+ expect(out.particles.length).toBe(0);
199
+ });
200
+
201
+ it('renders a relationship as a single related edge even when both directions exist', () => {
202
+ const out = projectSocietyGraph({
203
+ workers: [worker({ workerId: 'alice' }), worker({ workerId: 'bob' })],
204
+ needs: [],
205
+ relationships: [
206
+ rel('alice', 'bob', { collaborations: 4 }),
207
+ rel('bob', 'alice', { collaborations: 2 }),
208
+ ],
209
+ });
210
+ const related = out.edges.filter((e) => e.type === 'related');
211
+ expect(related).toHaveLength(1);
212
+ const edge = related[0];
213
+ expect([edge.source, edge.target].sort()).toEqual(['worker:alice', 'worker:bob']);
214
+ });
215
+
216
+ it('skips relationships that reference unknown workers', () => {
217
+ const out = projectSocietyGraph({
218
+ workers: [worker({ workerId: 'alice' })],
219
+ needs: [],
220
+ relationships: [rel('alice', 'ghost'), rel('ghost1', 'ghost2')],
221
+ });
222
+ expect(out.edges.filter((e) => e.type === 'related')).toHaveLength(0);
223
+ });
224
+
225
+ it('skips a self-relationship (fromWorker === toWorker) — no self-loop edge', () => {
226
+ // L198 真臂 `if (r.fromWorker === r.toWorker) continue`:自指关系跳过,不产生自环 related 边。
227
+ // 既有关系测试都用不同 worker(假臂:alice↔bob / alice→ghost),自指真臂未覆盖。
228
+ const out = projectSocietyGraph({
229
+ workers: [worker({ workerId: 'alice' })],
230
+ needs: [],
231
+ relationships: [rel('alice', 'alice')],
232
+ });
233
+ expect(out.edges.filter((e) => e.type === 'related')).toHaveLength(0);
234
+ });
235
+
236
+ it('produces a radial layout port whose owner order is the worker node ids', () => {
237
+ const out = projectSocietyGraph({
238
+ workers: [worker({ workerId: 'alice' }), worker({ workerId: 'bob' })],
239
+ needs: [],
240
+ relationships: [],
241
+ });
242
+ expect(out.layout?.mode).toBe('radial');
243
+ expect(out.layout?.ownerOrder.sort()).toEqual(['worker:alice', 'worker:bob']);
244
+ // every owner in the order has a matching member node
245
+ for (const ownerId of out.layout?.ownerOrder ?? []) {
246
+ expect(out.nodes.some((n) => n.id === ownerId && n.kind === 'member')).toBe(true);
247
+ }
248
+ });
249
+
250
+ it('is deterministic: identical input yields identical output', () => {
251
+ const input = {
252
+ workers: [worker({ workerId: 'alice' }), worker({ workerId: 'bob', activeTaskCount: 1 })],
253
+ needs: [need({ needId: 'n1', status: 'in_progress', assignee: 'bob' })],
254
+ relationships: [rel('alice', 'bob')],
255
+ };
256
+ const a = projectSocietyGraph(input);
257
+ const b = projectSocietyGraph(input);
258
+ expect(b).toEqual(a);
259
+ });
260
+
261
+ it('attaches a resolved avatar url to worker nodes when a resolver is provided', () => {
262
+ const out = projectSocietyGraph(
263
+ {
264
+ workers: [worker({ workerId: 'alice' }), worker({ workerId: 'bob' })],
265
+ needs: [],
266
+ relationships: [],
267
+ },
268
+ { resolveAvatarUrl: (id) => `img:${id}.png` }
269
+ );
270
+ const avatar = (id: string) => out.nodes.find((n) => n.id === id)?.avatarUrl;
271
+ expect(avatar('worker:alice')).toBe('img:alice.png');
272
+ expect(avatar('worker:bob')).toBe('img:bob.png');
273
+ // lead hub has no avatar (engine draws it)
274
+ expect(out.nodes.find((n) => n.kind === 'lead')?.avatarUrl).toBeUndefined();
275
+ });
276
+
277
+ it('omits avatarUrl when no resolver is provided', () => {
278
+ const out = projectSocietyGraph({
279
+ workers: [worker({ workerId: 'alice' })],
280
+ needs: [],
281
+ relationships: [],
282
+ });
283
+ expect(out.nodes.find((n) => n.id === 'worker:alice')?.avatarUrl).toBeUndefined();
284
+ });
285
+
286
+ it('ignores an unknown-worker assignee — task anchors to the agora, no ownership edge, no particle', () => {
287
+ // A need whose assignee points to a workerId that is not registered
288
+ // (e.g. worker unregistered mid-flight, or a stale assignee ref).
289
+ const out = projectSocietyGraph({
290
+ workers: [worker({ workerId: 'alice' })],
291
+ needs: [need({ needId: 'orphan', status: 'in_progress', assignee: 'ghost' })],
292
+ relationships: [],
293
+ });
294
+ const task = out.nodes.find((n) => n.id === 'need:orphan');
295
+ expect(task).toMatchObject({ kind: 'task', state: 'active', ownerId: null });
296
+ // 无有效指派人 → 不产生指向不存在的 worker:ghost 的 ownership 边,改锚定到广场。
297
+ expect(out.edges.some((e) => e.type === 'ownership')).toBe(false);
298
+ expect(
299
+ out.edges.some(
300
+ (e) =>
301
+ e.source === `agora:${TEAM}` && e.target === 'need:orphan' && e.type === 'parent-child'
302
+ )
303
+ ).toBe(true);
304
+ // no particle can travel an ownership edge that does not exist
305
+ expect(out.particles.some((p) => p.id.includes('orphan'))).toBe(false);
306
+ });
307
+
308
+ it('only one particle per in-flight need and none for needs lacking an assignee', () => {
309
+ const out = projectSocietyGraph({
310
+ workers: [worker({ workerId: 'alice' }), worker({ workerId: 'bob' })],
311
+ needs: [
312
+ need({ needId: 'a', status: 'in_progress', assignee: 'alice' }),
313
+ need({ needId: 'b', status: 'assigned', assignee: 'bob' }),
314
+ need({ needId: 'c', status: 'open' }),
315
+ ],
316
+ relationships: [],
317
+ });
318
+ expect(out.particles).toHaveLength(2);
319
+ expect(out.particles.every((p) => p.kind === 'task_assign')).toBe(true);
320
+ });
321
+ });
@@ -0,0 +1,240 @@
1
+ /**
2
+ * SocietyGraphAdapter — projects worker-society domain → @claude-teams/agent-graph.
3
+ *
4
+ * The reusable graph engine (packages/agent-graph) already powers the team graph in
5
+ * cyan/space holographic style. This adapter feeds it the *decentralized agora*:
6
+ * - a synthetic 「广场 / Agora」 hub (kind 'lead') sits at the radial center,
7
+ * - each worker becomes a member node parented to the agora,
8
+ * - each active need becomes a task orbiting its assignee (ownership edge),
9
+ * - each relationship becomes a 'related' edge between two workers,
10
+ * - each in-flight assignment spawns a particle along the ownership edge.
11
+ *
12
+ * Pure & deterministic: no Math.random, no FS/network/React — fully unit-testable.
13
+ * The cyan palette comes from the engine itself; we do not override colors, so the
14
+ * worker society renders with the same 1:1 holographic theme as the team graph.
15
+ * Worker avatars are injected by the host (SocietyGraph) via resolveAvatarUrl so the
16
+ * adapter itself stays renderer-agnostic (no asset imports here).
17
+ */
18
+ import type {
19
+ GraphDataPort,
20
+ GraphEdge,
21
+ GraphLayoutPort,
22
+ GraphNode,
23
+ GraphNodeState,
24
+ GraphParticle,
25
+ } from '@claude-teams/agent-graph';
26
+
27
+ import type {
28
+ NeedStatus,
29
+ PublishedNeed,
30
+ Relationship,
31
+ WorkerProfile,
32
+ } from '../core/domain/models/society';
33
+
34
+ /** Stable society/team name used for node ids and domain refs. */
35
+ export const SOCIETY_GRAPH_TEAM = 'worker-society';
36
+ const AGORA_ID = `agora:${SOCIETY_GRAPH_TEAM}`;
37
+ const AGORA_LABEL = '广场';
38
+
39
+ /** Engine cyan (matches packages/agent-graph COLORS.active) for in-flight particles. */
40
+ const PARTICLE_CYAN = '#66ccff';
41
+
42
+ /** Needs still worth showing on the graph (closed/expired/cancelled drop out). */
43
+ const VISIBLE_NEED_STATUSES: ReadonlySet<NeedStatus> = new Set([
44
+ 'open',
45
+ 'assigned',
46
+ 'in_progress',
47
+ 'delivered',
48
+ ]);
49
+
50
+ /** Needs whose assignee is actively working — emit a flowing particle. */
51
+ const IN_FLIGHT_NEED_STATUSES: ReadonlySet<NeedStatus> = new Set(['assigned', 'in_progress']);
52
+
53
+ export interface SocietyGraphSnapshot {
54
+ workers: WorkerProfile[];
55
+ /** Active needs (open/assigned/in_progress/delivered). */
56
+ needs: PublishedNeed[];
57
+ relationships: Relationship[];
58
+ }
59
+
60
+ export interface SocietyGraphProjectOptions {
61
+ /** Optional avatar resolver — given a workerId, return a stable avatar URL. */
62
+ resolveAvatarUrl?: (workerId: string) => string | undefined;
63
+ }
64
+
65
+ /** Node id for a worker. */
66
+ export function workerNodeId(workerId: string): string {
67
+ return `worker:${workerId}`;
68
+ }
69
+
70
+ /** Node id for a need. */
71
+ export function needNodeId(needId: string): string {
72
+ return `need:${needId}`;
73
+ }
74
+
75
+ function workerState(worker: WorkerProfile): GraphNodeState {
76
+ if (worker.status === 'offline') return 'idle';
77
+ if (worker.activeTaskCount > 0) return 'active';
78
+ if (worker.status === 'busy') return 'active';
79
+ return 'idle';
80
+ }
81
+
82
+ function needState(status: NeedStatus): GraphNodeState {
83
+ switch (status) {
84
+ case 'in_progress':
85
+ return 'active';
86
+ case 'delivered':
87
+ return 'complete';
88
+ default:
89
+ return 'waiting';
90
+ }
91
+ }
92
+
93
+ function needTaskStatus(status: NeedStatus): 'pending' | 'in_progress' | 'completed' | 'deleted' {
94
+ switch (status) {
95
+ case 'in_progress':
96
+ return 'in_progress';
97
+ case 'delivered':
98
+ return 'completed';
99
+ default:
100
+ return 'pending';
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Project a worker-society snapshot into a GraphDataPort snapshot for the engine.
106
+ * Returns an empty/dead port when the society has no workers and no needs.
107
+ */
108
+ export function projectSocietyGraph(
109
+ snapshot: SocietyGraphSnapshot,
110
+ options: SocietyGraphProjectOptions = {}
111
+ ): GraphDataPort {
112
+ const { workers, needs, relationships } = snapshot;
113
+ const { resolveAvatarUrl } = options;
114
+
115
+ if (workers.length === 0 && needs.length === 0) {
116
+ return { nodes: [], edges: [], particles: [], teamName: SOCIETY_GRAPH_TEAM, isAlive: false };
117
+ }
118
+
119
+ const nodes: GraphNode[] = [];
120
+ const edges: GraphEdge[] = [];
121
+ const particles: GraphParticle[] = [];
122
+
123
+ const knownWorkerIds = new Set(workers.map((w) => w.workerId));
124
+
125
+ // ── Agora hub (radial center) ──────────────────────────────────────────────
126
+ nodes.push({
127
+ id: AGORA_ID,
128
+ kind: 'lead',
129
+ label: AGORA_LABEL,
130
+ state: 'active',
131
+ domainRef: { kind: 'lead', teamName: SOCIETY_GRAPH_TEAM, memberName: 'agora' },
132
+ });
133
+
134
+ // ── Workers → member nodes, parented to the agora ──────────────────────────
135
+ for (const w of workers) {
136
+ const memberId = workerNodeId(w.workerId);
137
+ nodes.push({
138
+ id: memberId,
139
+ kind: 'member',
140
+ label: w.name,
141
+ state: workerState(w),
142
+ role: w.kind,
143
+ runtimeLabel: w.harness,
144
+ avatarUrl: resolveAvatarUrl?.(w.workerId),
145
+ domainRef: { kind: 'member', teamName: SOCIETY_GRAPH_TEAM, memberName: w.workerId },
146
+ });
147
+ edges.push({
148
+ id: `edge:parent:${AGORA_ID}:${memberId}`,
149
+ source: AGORA_ID,
150
+ target: memberId,
151
+ type: 'parent-child',
152
+ });
153
+ }
154
+
155
+ // ── Needs → task nodes orbiting their assignee ─────────────────────────────
156
+ for (const need of needs) {
157
+ if (!VISIBLE_NEED_STATUSES.has(need.status)) continue;
158
+ const assigneeId =
159
+ need.assignee && knownWorkerIds.has(need.assignee) ? workerNodeId(need.assignee) : null;
160
+ const taskId = needNodeId(need.needId);
161
+
162
+ nodes.push({
163
+ id: taskId,
164
+ kind: 'task',
165
+ label: need.subject,
166
+ sublabel:
167
+ need.requiredCapabilities.length > 0 ? need.requiredCapabilities.join(' · ') : undefined,
168
+ state: needState(need.status),
169
+ taskStatus: needTaskStatus(need.status),
170
+ ownerId: assigneeId,
171
+ domainRef: { kind: 'task', teamName: SOCIETY_GRAPH_TEAM, taskId: need.needId },
172
+ });
173
+
174
+ if (assigneeId) {
175
+ // 已指派 → ownership 边接到具体 worker;进行中再叠一条流动粒子。
176
+ const ownEdgeId = `edge:own:${assigneeId}:${taskId}`;
177
+ edges.push({
178
+ id: ownEdgeId,
179
+ source: assigneeId,
180
+ target: taskId,
181
+ type: 'ownership',
182
+ });
183
+
184
+ // In-flight assignment → a particle flowing along the ownership edge.
185
+ if (IN_FLIGHT_NEED_STATUSES.has(need.status)) {
186
+ particles.push({
187
+ id: `particle:need:${need.needId}`,
188
+ edgeId: ownEdgeId,
189
+ progress: 0,
190
+ kind: 'task_assign',
191
+ color: PARTICLE_CYAN,
192
+ });
193
+ }
194
+ } else {
195
+ // 无指派人 → 锚定到广场(agora)。否则 open 需求在力导布局里没有边可依附,成为孤立
196
+ // 浮点,视觉上「看不出跟谁关联」。用 parent-child 表达「待认领,挂在广场」——ownership
197
+ // 语义无主不成立,故不用 ownership;粒子也只属于进行中的已指派任务。
198
+ edges.push({
199
+ id: `edge:agora:${taskId}`,
200
+ source: AGORA_ID,
201
+ target: taskId,
202
+ type: 'parent-child',
203
+ });
204
+ }
205
+ }
206
+
207
+ // ── Relationships → 'related' edges (bidirectional-deduped) ────────────────
208
+ const seenRelated = new Set<string>();
209
+ for (const r of relationships) {
210
+ if (r.fromWorker === r.toWorker) continue;
211
+ if (!knownWorkerIds.has(r.fromWorker) || !knownWorkerIds.has(r.toWorker)) continue;
212
+ const [a, b] =
213
+ r.fromWorker <= r.toWorker ? [r.fromWorker, r.toWorker] : [r.toWorker, r.fromWorker];
214
+ const key = `${a}:${b}`;
215
+ if (seenRelated.has(key)) continue;
216
+ seenRelated.add(key);
217
+ edges.push({
218
+ id: `edge:rel:${a}:${b}`,
219
+ source: workerNodeId(a),
220
+ target: workerNodeId(b),
221
+ type: 'related',
222
+ });
223
+ }
224
+
225
+ const layout: GraphLayoutPort = {
226
+ version: 'stable-slots-v1',
227
+ mode: 'radial',
228
+ ownerOrder: workers.map((w) => workerNodeId(w.workerId)),
229
+ slotAssignments: {},
230
+ };
231
+
232
+ return {
233
+ nodes,
234
+ edges,
235
+ particles,
236
+ teamName: SOCIETY_GRAPH_TEAM,
237
+ isAlive: workers.length > 0,
238
+ layout,
239
+ };
240
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * societyOverlayActions 测试 —— 纯图谱交互模型的核心规则(TDD 先行)。
3
+ *
4
+ * 删掉看板后,节点点开弹出的小卡片是唯一的交互入口。该模块决定:
5
+ * 1. 一个 need 处于某状态时,卡片上该出现哪些生命周期动作按钮;
6
+ * 2. 弹卡定位如何不被屏幕边缘裁切。
7
+ * 这两条规则是「纯图谱」UX 的全部决策点,必须测全。
8
+ */
9
+ import { describe, expect, it } from 'vitest';
10
+
11
+ import { needLifecycleActions, type NeedOverlayAction } from './societyOverlayActions';
12
+
13
+ describe('needLifecycleActions', () => {
14
+ it('open need with volunteers → 选派最优 + 触发自治', () => {
15
+ expect(needLifecycleActions('open', true)).toEqual<NeedOverlayAction[]>([
16
+ 'selectAssignee',
17
+ 'triggerAutonomy',
18
+ ]);
19
+ });
20
+
21
+ it('open need with no volunteers → 仅触发自治(让 worker 自荐,无手选)', () => {
22
+ expect(needLifecycleActions('open', false)).toEqual<NeedOverlayAction[]>(['triggerAutonomy']);
23
+ });
24
+
25
+ it('assigned → 开始执行', () => {
26
+ expect(needLifecycleActions('assigned', false)).toEqual<NeedOverlayAction[]>(['startNeed']);
27
+ });
28
+
29
+ it('in_progress → 标记交付', () => {
30
+ expect(needLifecycleActions('in_progress', false)).toEqual<NeedOverlayAction[]>([
31
+ 'deliverNeed',
32
+ ]);
33
+ });
34
+
35
+ it('delivered → 通过审核', () => {
36
+ expect(needLifecycleActions('delivered', false)).toEqual<NeedOverlayAction[]>([
37
+ 'acceptDelivery',
38
+ ]);
39
+ });
40
+
41
+ it('closed/expired/cancelled → 无动作(不会出现在图谱上,但要安全兜底)', () => {
42
+ expect(needLifecycleActions('closed', false)).toEqual<NeedOverlayAction[]>([]);
43
+ expect(needLifecycleActions('expired', false)).toEqual<NeedOverlayAction[]>([]);
44
+ expect(needLifecycleActions('cancelled', false)).toEqual<NeedOverlayAction[]>([]);
45
+ });
46
+
47
+ it('volunteers flag only matters for open needs', () => {
48
+ // assigned/in_progress/delivered 不因是否有自荐者而改变动作。
49
+ expect(needLifecycleActions('assigned', true)).toEqual(needLifecycleActions('assigned', false));
50
+ expect(needLifecycleActions('in_progress', true)).toEqual(
51
+ needLifecycleActions('in_progress', false)
52
+ );
53
+ expect(needLifecycleActions('delivered', true)).toEqual(
54
+ needLifecycleActions('delivered', false)
55
+ );
56
+ });
57
+ });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * societyOverlayActions —— 纯图谱交互模型的决策核心(纯函数)。
3
+ *
4
+ * 删掉看板后,点开 worker/need 节点弹出的卡片是唯一的交互入口。本模块封装与渲染无关、
5
+ * 可单测的决策:needLifecycleActions —— 某 need 处于某状态时,卡片上出现哪些动作按钮。
6
+ * 自荐改由「触发自治」自动完成(反派单),所以 open 状态不再有「自荐」手选,只有
7
+ * 「选派最优(若有自荐者)」与「触发自治」。
8
+ *
9
+ * 弹卡定位由引擎 GraphView 的 Floating UI(computePosition + flip + shift + offset)负责,
10
+ * 不在本模块——故无 clamp 函数(曾有的 clampOverlayPosition 与引擎叠加致 double-offset)。
11
+ */
12
+ import type { NeedStatus } from '../core/domain/models/society';
13
+
14
+ /** 节点弹卡上可出现的 need 生命周期动作(与 store 的命令一一对应)。 */
15
+ export type NeedOverlayAction =
16
+ | 'selectAssignee' // open + 有自荐者 → 选派最优
17
+ | 'triggerAutonomy' // open → 让 worker 自荐/选派(反派单)
18
+ | 'startNeed' // assigned → 开始执行
19
+ | 'deliverNeed' // in_progress → 标记交付
20
+ | 'acceptDelivery'; // delivered → 通过审核
21
+
22
+ /**
23
+ * 按 need 状态返回卡片应展示的生命周期动作(顺序即按钮顺序)。
24
+ *
25
+ * - open:有自荐者→「选派最优」+「触发自治」;无自荐者→仅「触发自治」。
26
+ * - assigned/in_progress/delivered:各一个推进动作,与是否有自荐者无关。
27
+ * - closed/expired/cancelled:图谱不渲染这些节点,但返回空数组做安全兜底。
28
+ */
29
+ export function needLifecycleActions(
30
+ status: NeedStatus,
31
+ hasVolunteers: boolean
32
+ ): NeedOverlayAction[] {
33
+ switch (status) {
34
+ case 'open':
35
+ return hasVolunteers ? ['selectAssignee', 'triggerAutonomy'] : ['triggerAutonomy'];
36
+ case 'assigned':
37
+ return ['startNeed'];
38
+ case 'in_progress':
39
+ return ['deliverNeed'];
40
+ case 'delivered':
41
+ return ['acceptDelivery'];
42
+ case 'closed':
43
+ case 'expired':
44
+ case 'cancelled':
45
+ return [];
46
+ default:
47
+ return [];
48
+ }
49
+ }