@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
@@ -72,6 +72,7 @@ const logger = createLogger('teamSlice');
72
72
 
73
73
  const TEAM_GET_DATA_TIMEOUT_MS = 30_000;
74
74
  const TEAM_FETCH_TIMEOUT_MS = 30_000;
75
+ export const TEAM_MESSAGES_PAGE_LIMIT = 20;
75
76
  const MEMBER_SPAWN_STATUSES_IPC_RETRY_BACKOFF_MS = 5_000;
76
77
  const TEAM_REFRESH_BURST_WINDOW_MS = 4_000;
77
78
  const MEMBER_SPAWN_UI_EQUAL_WARN_THROTTLE_MS = 2_000;
@@ -796,6 +797,8 @@ export interface TeamMessagesCacheEntry {
796
797
  loadingOlder: boolean;
797
798
  headHydrated: boolean;
798
799
  olderHydrated: boolean;
800
+ /** Epoch ms of last manual clear; messages with timestamp ≤ this are hidden from the view. */
801
+ clearedAt: number | null;
799
802
  }
800
803
 
801
804
  export interface RefreshTeamMessagesHeadResult {
@@ -815,6 +818,7 @@ const EMPTY_TEAM_MESSAGES_CACHE_ENTRY: TeamMessagesCacheEntry = {
815
818
  loadingOlder: false,
816
819
  headHydrated: false,
817
820
  olderHydrated: false,
821
+ clearedAt: null,
818
822
  };
819
823
 
820
824
  function createEmptyTeamMessagesCacheEntry(): TeamMessagesCacheEntry {
@@ -829,6 +833,7 @@ function createEmptyTeamMessagesCacheEntry(): TeamMessagesCacheEntry {
829
833
  loadingOlder: false,
830
834
  headHydrated: false,
831
835
  olderHydrated: false,
836
+ clearedAt: null,
832
837
  };
833
838
  }
834
839
 
@@ -1500,6 +1505,7 @@ const mergedMessagesSelectorCache = new Map<
1500
1505
  {
1501
1506
  canonicalRef: InboxMessage[];
1502
1507
  optimisticRef: InboxMessage[];
1508
+ clearedAt: number | null;
1503
1509
  result: InboxMessage[];
1504
1510
  }
1505
1511
  >();
@@ -1733,15 +1739,23 @@ export function selectTeamMessages(
1733
1739
  const cached = mergedMessagesSelectorCache.get(teamName);
1734
1740
  if (
1735
1741
  cached?.canonicalRef === entry.canonicalMessages &&
1736
- cached.optimisticRef === entry.optimisticMessages
1742
+ cached.optimisticRef === entry.optimisticMessages &&
1743
+ cached.clearedAt === entry.clearedAt
1737
1744
  ) {
1738
1745
  return cached.result;
1739
1746
  }
1740
1747
 
1741
- const result = mergeTeamMessages(entry.canonicalMessages, entry.optimisticMessages);
1748
+ let result = mergeTeamMessages(entry.canonicalMessages, entry.optimisticMessages);
1749
+ // `!= null` treats both null and a missing/undefined field as "no cutoff", so
1750
+ // legacy entries constructed before clearedAt existed are not accidentally emptied.
1751
+ if (entry.clearedAt != null) {
1752
+ const cutoff = entry.clearedAt;
1753
+ result = result.filter((msg) => Date.parse(msg.timestamp) > cutoff);
1754
+ }
1742
1755
  mergedMessagesSelectorCache.set(teamName, {
1743
1756
  canonicalRef: entry.canonicalMessages,
1744
1757
  optimisticRef: entry.optimisticMessages,
1758
+ clearedAt: entry.clearedAt,
1745
1759
  result,
1746
1760
  });
1747
1761
  return result;
@@ -2070,6 +2084,7 @@ export interface TeamSlice {
2070
2084
  refreshTeamData: (teamName: string, opts?: RefreshTeamDataOptions) => Promise<void>;
2071
2085
  refreshTeamMessagesHead: (teamName: string) => Promise<RefreshTeamMessagesHeadResult>;
2072
2086
  loadOlderTeamMessages: (teamName: string) => Promise<void>;
2087
+ clearTeamMessages: (teamName: string) => void;
2073
2088
  refreshMemberActivityMeta: (teamName: string) => Promise<void>;
2074
2089
  syncTeamPendingReplyRefresh: (
2075
2090
  teamName: string,
@@ -2079,6 +2094,15 @@ export interface TeamSlice {
2079
2094
  ) => void;
2080
2095
  sendTeamMessage: (teamName: string, request: SendMessageRequest) => Promise<SendMessageResult>;
2081
2096
  addOptimisticTeamMessage: (teamName: string, message: InboxMessage) => void;
2097
+ /**
2098
+ * Accumulate a streaming direct-CLI assistant reply token-by-token into an optimistic
2099
+ * in-progress message keyed by `messageId`. The canonical reply (appended server-side
2100
+ * with the same messageId) prunes this twin on the next inbox refresh — no duplicate.
2101
+ */
2102
+ appendStreamingTeamReply: (
2103
+ teamName: string,
2104
+ chunk: { messageId: string; delta: string; from: string; to?: string }
2105
+ ) => void;
2082
2106
  crossTeamTargets: {
2083
2107
  teamName: string;
2084
2108
  displayName: string;
@@ -3322,12 +3346,20 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
3322
3346
  const prevByName = get().teamByName;
3323
3347
  const existingEntry = prevByName[teamName];
3324
3348
  const configColor = data.config.color;
3349
+ // getData collapses config.name to the slug for a draft/partially-provisioned
3350
+ // team (no team.json on disk). Prefer the summary's existing user-facing
3351
+ // displayName in that case so the name never regresses to the slug.
3352
+ const configName = data.config.name ?? '';
3353
+ const resolvedDisplayName =
3354
+ configName && configName !== teamName
3355
+ ? configName
3356
+ : existingEntry?.displayName || configName || teamName;
3325
3357
  if (configColor && (!existingEntry || existingEntry?.color !== configColor)) {
3326
3358
  const patched: TeamSummary = existingEntry
3327
- ? { ...existingEntry, color: configColor, displayName: data.config.name || teamName }
3359
+ ? { ...existingEntry, color: configColor, displayName: resolvedDisplayName }
3328
3360
  : {
3329
3361
  teamName,
3330
- displayName: data.config.name || teamName,
3362
+ displayName: resolvedDisplayName,
3331
3363
  description: data.config.description ?? '',
3332
3364
  color: configColor,
3333
3365
  memberCount: data.members.length,
@@ -3372,7 +3404,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
3372
3404
  await api.review.invalidateTaskChangeSummaries(teamName, invalidationState.taskIds);
3373
3405
  }
3374
3406
  // Sync tab label with the team's display name from config
3375
- const displayName = data.config.name || teamName;
3407
+ const displayName = resolvedDisplayName;
3376
3408
  const allTabs = get().getAllPaneTabs();
3377
3409
  const relatedTabs = allTabs.filter(
3378
3410
  (tab) => (tab.type === 'team' || tab.type === 'graph') && tab.teamName === teamName
@@ -3668,7 +3700,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
3668
3700
 
3669
3701
  try {
3670
3702
  const page = await unwrapIpc('team:getMessagesPage', () =>
3671
- api.teams.getMessagesPage(teamName, { limit: 50 })
3703
+ api.teams.getMessagesPage(teamName, { limit: TEAM_MESSAGES_PAGE_LIMIT })
3672
3704
  );
3673
3705
  if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
3674
3706
  return {
@@ -3805,7 +3837,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
3805
3837
  const page = await unwrapIpc('team:getMessagesPage', () =>
3806
3838
  api.teams.getMessagesPage(teamName, {
3807
3839
  cursor: entry.nextCursor,
3808
- limit: 50,
3840
+ limit: TEAM_MESSAGES_PAGE_LIMIT,
3809
3841
  })
3810
3842
  );
3811
3843
  if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
@@ -3884,6 +3916,27 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
3884
3916
  return request;
3885
3917
  },
3886
3918
 
3919
+ clearTeamMessages: (teamName: string) => {
3920
+ const clearedAt = Date.now();
3921
+ mergedMessagesSelectorCache.delete(teamName);
3922
+ set((state) => {
3923
+ const existing = getTeamMessagesCacheEntry(state, teamName);
3924
+ // Keep canonical messages (so refetch comparison is stable) but mark a cutoff:
3925
+ // any message with timestamp ≤ clearedAt is hidden from the view.
3926
+ return {
3927
+ teamMessagesByName: {
3928
+ ...state.teamMessagesByName,
3929
+ [teamName]: {
3930
+ ...existing,
3931
+ optimisticMessages: [],
3932
+ clearedAt,
3933
+ hasMore: false,
3934
+ },
3935
+ },
3936
+ };
3937
+ });
3938
+ },
3939
+
3887
3940
  refreshMemberActivityMeta: async (teamName: string) => {
3888
3941
  const entry = getTeamMessagesCacheEntry(get(), teamName);
3889
3942
  if (!entry.headHydrated) {
@@ -4112,6 +4165,43 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
4112
4165
  },
4113
4166
  }));
4114
4167
  },
4168
+ appendStreamingTeamReply: (teamName, chunk) => {
4169
+ const messageId = chunk.messageId.trim();
4170
+ if (!messageId) return;
4171
+ set((state) => {
4172
+ const entry = getTeamMessagesCacheEntry(state, teamName);
4173
+ const existingIndex = entry.optimisticMessages.findIndex(
4174
+ (m) => typeof m.messageId === 'string' && m.messageId.trim() === messageId
4175
+ );
4176
+ let nextOptimistic: InboxMessage[];
4177
+ if (existingIndex >= 0) {
4178
+ // Append the delta to the in-progress reply (token-by-token accumulation).
4179
+ const existing = entry.optimisticMessages[existingIndex];
4180
+ nextOptimistic = [...entry.optimisticMessages];
4181
+ nextOptimistic[existingIndex] = { ...existing, text: existing.text + chunk.delta };
4182
+ } else {
4183
+ nextOptimistic = [
4184
+ ...entry.optimisticMessages,
4185
+ {
4186
+ from: chunk.from,
4187
+ to: chunk.to,
4188
+ text: chunk.delta,
4189
+ timestamp: new Date().toISOString(),
4190
+ read: true,
4191
+ messageId,
4192
+ source: 'runtime_delivery',
4193
+ },
4194
+ ];
4195
+ nextOptimistic.sort(compareInboxMessagesByTimestamp);
4196
+ }
4197
+ return {
4198
+ teamMessagesByName: {
4199
+ ...state.teamMessagesByName,
4200
+ [teamName]: { ...entry, optimisticMessages: nextOptimistic },
4201
+ },
4202
+ };
4203
+ });
4204
+ },
4115
4205
 
4116
4206
  fetchCrossTeamTargets: async () => {
4117
4207
  set({ crossTeamTargetsLoading: true });
@@ -4461,8 +4551,29 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
4461
4551
  );
4462
4552
  }
4463
4553
 
4464
- // Just create the team config — no provisioning, no launching.
4465
- await unwrapIpc('team:create', () => api.teams.createTeam(request));
4554
+ const floor = nowIso();
4555
+ set((state) => {
4556
+ const previousRuntimeRunId = state.currentRuntimeRunIdByTeam[request.teamName];
4557
+ return {
4558
+ currentRuntimeRunIdByTeam:
4559
+ omitTeamKey(state.currentRuntimeRunIdByTeam, request.teamName) ??
4560
+ state.currentRuntimeRunIdByTeam,
4561
+ ignoredRuntimeRunIds: previousRuntimeRunId
4562
+ ? { ...state.ignoredRuntimeRunIds, [previousRuntimeRunId]: request.teamName }
4563
+ : state.ignoredRuntimeRunIds,
4564
+ provisioningStartedAtFloorByTeam: {
4565
+ ...state.provisioningStartedAtFloorByTeam,
4566
+ [request.teamName]: floor,
4567
+ },
4568
+ activeToolsByTeam:
4569
+ omitTeamKey(state.activeToolsByTeam, request.teamName) ?? state.activeToolsByTeam,
4570
+ finishedVisibleByTeam:
4571
+ omitTeamKey(state.finishedVisibleByTeam, request.teamName) ??
4572
+ state.finishedVisibleByTeam,
4573
+ toolHistoryByTeam:
4574
+ omitTeamKey(state.toolHistoryByTeam, request.teamName) ?? state.toolHistoryByTeam,
4575
+ };
4576
+ });
4466
4577
 
4467
4578
  // Persist per-team launch params (model, effort, limit context)
4468
4579
  const baseModel = extractBaseModel(request.model, request.providerId);
@@ -4475,6 +4586,12 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
4475
4586
  limitContext: request.limitContext ?? false,
4476
4587
  };
4477
4588
  saveLaunchParams(request.teamName, params);
4589
+ set((state) => ({
4590
+ launchParamsByTeam: {
4591
+ ...state.launchParamsByTeam,
4592
+ [request.teamName]: params,
4593
+ },
4594
+ }));
4478
4595
 
4479
4596
  // Initialize per-team tool approval settings based on skipPermissions flag
4480
4597
  const initialSettings: ToolApprovalSettings =
@@ -4483,6 +4600,18 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
4483
4600
  : { ...DEFAULT_TOOL_APPROVAL_SETTINGS, autoAllowAll: true };
4484
4601
  saveToolApprovalSettingsForTeam(request.teamName, initialSettings);
4485
4602
 
4603
+ const response = await unwrapIpc('team:create', () => api.teams.createTeam(request));
4604
+ const runId =
4605
+ typeof response === 'object' && response && 'runId' in response
4606
+ ? String(response.runId)
4607
+ : request.teamName;
4608
+ set((state) => ({
4609
+ currentRuntimeRunIdByTeam: {
4610
+ ...state.currentRuntimeRunIdByTeam,
4611
+ [request.teamName]: runId,
4612
+ },
4613
+ }));
4614
+
4486
4615
  // Refresh team list to pick up the new team
4487
4616
  void get().fetchTeams();
4488
4617
  window.setTimeout(() => {
@@ -23,6 +23,14 @@ export interface MentionSuggestion {
23
23
  searchText?: string;
24
24
  /** Optional slash command string including leading slash (command and skill suggestions only) */
25
25
  command?: `/${string}`;
26
+ /** Optional canonical command reference for registry-backed slash commands */
27
+ commandRef?: string;
28
+ /** Workflow prompt id for slash suggestions backed by a local workflow file */
29
+ workflowPromptId?: string;
30
+ /** Workflow prompt folder for slash suggestions backed by a local workflow file */
31
+ workflowPromptFolder?: string;
32
+ /** Workflow prompt metadata for slash suggestions backed by a local workflow file */
33
+ workflowPrompt?: import('@shared/types/systemManager').WorkflowPromptSummary;
26
34
  /** Canonical task id (task suggestions only) */
27
35
  taskId?: string;
28
36
  /** Owning team name (task suggestions only) */
@@ -87,7 +87,9 @@ export interface Tab {
87
87
  | 'extensions'
88
88
  | 'schedules'
89
89
  | 'tasks'
90
- | 'graph';
90
+ | 'graph'
91
+ | 'chat'
92
+ | 'society';
91
93
 
92
94
  /** Session ID (required when type === 'session') */
93
95
  sessionId?: string;
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { generateBindProject, isValidBindProject } from '../bindProjectSlug';
4
+
5
+ describe('isValidBindProject', () => {
6
+ it('accepts lowercase ascii slugs', () => {
7
+ expect(isValidBindProject('team-1')).toBe(true);
8
+ expect(isValidBindProject('frontend_bot')).toBe(true);
9
+ expect(isValidBindProject('a')).toBe(true);
10
+ });
11
+
12
+ it('rejects empty / uppercase / leading hyphen / spaces', () => {
13
+ expect(isValidBindProject('')).toBe(false);
14
+ expect(isValidBindProject('Team')).toBe(false);
15
+ expect(isValidBindProject('-x')).toBe(false);
16
+ expect(isValidBindProject('has space')).toBe(false);
17
+ });
18
+ });
19
+
20
+ describe('generateBindProject', () => {
21
+ it('returns empty for blank input', () => {
22
+ expect(generateBindProject(' ', new Set())).toBe('');
23
+ });
24
+
25
+ it('slugifies ascii names', () => {
26
+ expect(generateBindProject('Front End', new Set())).toMatch(/^front-end-[0-9a-z]+$/);
27
+ });
28
+
29
+ it('falls back to a team- base for non-ascii names', () => {
30
+ expect(generateBindProject('产品助手', new Set())).toMatch(/^team-[0-9a-z]+$/);
31
+ });
32
+
33
+ it('is deterministic for the same input + existing set (no per-keystroke reshuffle)', () => {
34
+ const existing = new Set<string>();
35
+ const a = generateBindProject('产品助手', existing);
36
+ const b = generateBindProject('产品助手', existing);
37
+ // The identifier must not reshuffle on every render/keystroke — this is the
38
+ // root cause of the flickering "already exists" red box.
39
+ expect(a).toBe(b);
40
+ });
41
+
42
+ it('never collides with existing bind projects (no false "已存在")', () => {
43
+ // Simulate a prior worker that already took the deterministic id.
44
+ const taken = generateBindProject('产品助手', new Set());
45
+ const existing = new Set([taken]);
46
+ const next = generateBindProject('产品助手', existing);
47
+ expect(existing.has(next)).toBe(false);
48
+ expect(isValidBindProject(next)).toBe(true);
49
+ });
50
+
51
+ it('keeps producing non-colliding ids across many creations', () => {
52
+ const existing = new Set<string>();
53
+ for (let i = 0; i < 20; i += 1) {
54
+ const id = generateBindProject('产品助手', existing);
55
+ expect(existing.has(id)).toBe(false);
56
+ expect(isValidBindProject(id)).toBe(true);
57
+ existing.add(id);
58
+ }
59
+ expect(existing.size).toBe(20);
60
+ });
61
+
62
+ it('produces a valid identifier for varied inputs', () => {
63
+ for (const name of ['前端工程师', 'My Cool Bot', '运维-巡检', '']) {
64
+ const id = generateBindProject(name, new Set());
65
+ // Empty name is allowed to yield '' (the dialog treats blank as "not set").
66
+ if (id) expect(isValidBindProject(id)).toBe(true);
67
+ }
68
+ });
69
+ });
@@ -0,0 +1,148 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { transformChunksToConversation } from '@renderer/utils/groupTransformer';
4
+ import type { EnhancedChunk, ParsedMessage } from '@renderer/types/data';
5
+ import { EMPTY_METRICS } from '@main/types/chunks';
6
+
7
+ // transformChunksToConversation 是 chunk→会话展示项的核心纯函数(chunk-building
8
+ // 关键路径),此前零覆盖。这里用最小 EnhancedChunk fixture 验证 chunk 类型映射、
9
+ // 计数、顺序、isOngoing 与 compact 阶段编号契约。
10
+
11
+ const T0 = new Date('2026-06-13T10:00:00.000Z');
12
+ const T1 = new Date('2026-06-13T10:00:01.000Z');
13
+
14
+ function msg(partial: Partial<ParsedMessage> = {}): ParsedMessage {
15
+ return {
16
+ uuid: 'm-1',
17
+ parentUuid: null,
18
+ type: 'user',
19
+ timestamp: T0,
20
+ content: 'hello',
21
+ isSidechain: false,
22
+ isMeta: false,
23
+ toolCalls: [],
24
+ toolResults: [],
25
+ ...partial,
26
+ } as ParsedMessage;
27
+ }
28
+
29
+ function base(id: string) {
30
+ return { id, startTime: T0, endTime: T1, durationMs: 0, metrics: EMPTY_METRICS };
31
+ }
32
+
33
+ const userChunk = (id: string, content = 'hi') =>
34
+ ({
35
+ ...base(id),
36
+ chunkType: 'user',
37
+ userMessage: msg({ content }),
38
+ rawMessages: [],
39
+ }) as EnhancedChunk;
40
+
41
+ const aiChunk = (id: string, text = 'ok') =>
42
+ ({
43
+ ...base(id),
44
+ chunkType: 'ai',
45
+ responses: [msg({ type: 'assistant', content: text })],
46
+ processes: [],
47
+ sidechainMessages: [],
48
+ toolExecutions: [],
49
+ semanticSteps: [],
50
+ rawMessages: [],
51
+ }) as EnhancedChunk;
52
+
53
+ const systemChunk = (id: string, output = 'done') =>
54
+ ({
55
+ ...base(id),
56
+ chunkType: 'system',
57
+ message: msg({ content: output }),
58
+ commandOutput: output,
59
+ rawMessages: [],
60
+ }) as EnhancedChunk;
61
+
62
+ const compactChunk = (id: string) =>
63
+ ({
64
+ ...base(id),
65
+ chunkType: 'compact',
66
+ message: msg({ content: 'compacted' }),
67
+ rawMessages: [],
68
+ }) as EnhancedChunk;
69
+
70
+ describe('transformChunksToConversation', () => {
71
+ it('returns an empty conversation for no chunks', () => {
72
+ const conv = transformChunksToConversation([], []);
73
+ expect(conv).toEqual({
74
+ sessionId: '',
75
+ items: [],
76
+ totalUserGroups: 0,
77
+ totalSystemGroups: 0,
78
+ totalAIGroups: 0,
79
+ totalCompactGroups: 0,
80
+ });
81
+ });
82
+
83
+ it('maps each chunk type to the correct item type and counts each once', () => {
84
+ const conv = transformChunksToConversation(
85
+ [userChunk('u-1'), aiChunk('u-1'), systemChunk('u-1'), compactChunk('u-1')],
86
+ []
87
+ );
88
+ expect(conv.items.map((i) => i.type)).toEqual(['user', 'ai', 'system', 'compact']);
89
+ expect(conv.totalUserGroups).toBe(1);
90
+ expect(conv.totalAIGroups).toBe(1);
91
+ expect(conv.totalSystemGroups).toBe(1);
92
+ expect(conv.totalCompactGroups).toBe(1);
93
+ });
94
+
95
+ it('counts are per-type and independent across repeats', () => {
96
+ const conv = transformChunksToConversation([userChunk('u'), userChunk('u'), aiChunk('u')], []);
97
+ expect(conv.totalUserGroups).toBe(2);
98
+ expect(conv.totalAIGroups).toBe(1);
99
+ expect(conv.totalSystemGroups).toBe(0);
100
+ expect(conv.totalCompactGroups).toBe(0);
101
+ });
102
+
103
+ it('sessionId comes from the first chunk id', () => {
104
+ const conv = transformChunksToConversation(
105
+ [aiChunk('session-42'), userChunk('session-42')],
106
+ []
107
+ );
108
+ expect(conv.sessionId).toBe('session-42');
109
+ });
110
+
111
+ it('preserves chunk order in the flattened items', () => {
112
+ const conv = transformChunksToConversation(
113
+ [aiChunk('s'), userChunk('s'), systemChunk('s'), aiChunk('s')],
114
+ []
115
+ );
116
+ expect(conv.items.map((i) => i.type)).toEqual(['ai', 'user', 'system', 'ai']);
117
+ });
118
+
119
+ it('marks only the last AI group as ongoing when isOngoing is true', () => {
120
+ const conv = transformChunksToConversation(
121
+ [userChunk('s'), aiChunk('s'), aiChunk('s')],
122
+ [],
123
+ true
124
+ );
125
+ const aiItems = conv.items.filter((i) => i.type === 'ai');
126
+ expect(aiItems).toHaveLength(2);
127
+ const first = aiItems[0].group as { isOngoing?: boolean };
128
+ const last = aiItems[1].group as { isOngoing?: boolean; status?: string };
129
+ expect(last.isOngoing).toBe(true);
130
+ expect(last.status).toBe('in_progress');
131
+ expect(first.isOngoing).not.toBe(true);
132
+ });
133
+
134
+ it('does not mark any group ongoing when isOngoing is false', () => {
135
+ const conv = transformChunksToConversation([userChunk('s'), aiChunk('s')], [], false);
136
+ const aiItem = conv.items.find((i) => i.type === 'ai')!;
137
+ expect((aiItem.group as { isOngoing?: boolean }).isOngoing).not.toBe(true);
138
+ });
139
+
140
+ it('assigns a compact phase number (>=2) to compact groups', () => {
141
+ const conv = transformChunksToConversation([userChunk('s'), compactChunk('s')], []);
142
+ const compactItem = conv.items.find((i) => i.type === 'compact') as
143
+ | { type: 'compact'; group: { startingPhaseNumber?: number } }
144
+ | undefined;
145
+ expect(compactItem).toBeDefined();
146
+ expect(compactItem!.group.startingPhaseNumber).toBeGreaterThanOrEqual(2);
147
+ });
148
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * restoreInitialRoute — maps the URL path to the matching tab action.
3
+ *
4
+ * This runs before React's first render (main.tsx) so the store holds the
5
+ * correct tab on the first paint. These tests pin the path → action mapping
6
+ * and the idempotent/empty-path behavior.
7
+ */
8
+ import { describe, expect, it, vi } from 'vitest';
9
+
10
+ import { restoreInitialRoute } from '../initialRoute';
11
+
12
+ import type { AppState } from '@renderer/store/types';
13
+
14
+ function mockState(): AppState {
15
+ return {
16
+ openTeamTab: vi.fn(),
17
+ openTeamsTab: vi.fn(),
18
+ openSystemManager: vi.fn(),
19
+ openSettingsTab: vi.fn(),
20
+ openExtensionsTab: vi.fn(),
21
+ openSchedulesTab: vi.fn(),
22
+ openTasksTab: vi.fn(),
23
+ openDashboard: vi.fn(),
24
+ openSocietyTab: vi.fn(),
25
+ navigateToSession: vi.fn(),
26
+ openTab: vi.fn(),
27
+ } as unknown as AppState;
28
+ }
29
+
30
+ describe('restoreInitialRoute', () => {
31
+ it('opens the teams tab for /teams', () => {
32
+ const state = mockState();
33
+ restoreInitialRoute(state, '/teams');
34
+ expect(state.openTeamsTab).toHaveBeenCalledTimes(1);
35
+ });
36
+
37
+ it('opens a team tab for /team/:slug', () => {
38
+ const state = mockState();
39
+ restoreInitialRoute(state, '/team/hermit');
40
+ expect(state.openTeamTab).toHaveBeenCalledWith('hermit');
41
+ });
42
+
43
+ it('decodes encoded path segments', () => {
44
+ const state = mockState();
45
+ restoreInitialRoute(state, '/team/%E6%88%91%E7%9A%84%E5%9B%A2%E9%98%9F');
46
+ expect(state.openTeamTab).toHaveBeenCalledWith('我的团队');
47
+ });
48
+
49
+ it('does nothing for the root path', () => {
50
+ const state = mockState();
51
+ restoreInitialRoute(state, '/');
52
+ expect(state.openTeamsTab).not.toHaveBeenCalled();
53
+ expect(state.openTab).not.toHaveBeenCalled();
54
+ });
55
+
56
+ it('does nothing for an empty path', () => {
57
+ const state = mockState();
58
+ restoreInitialRoute(state, '');
59
+ expect(state.openTab).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it('navigates to a session for /session/:project/:session', () => {
63
+ const state = mockState();
64
+ restoreInitialRoute(state, '/session/proj-1/sess-9');
65
+ expect(state.navigateToSession).toHaveBeenCalledWith('proj-1', 'sess-9');
66
+ });
67
+
68
+ it('opens a graph tab for /graph/:team', () => {
69
+ const state = mockState();
70
+ restoreInitialRoute(state, '/graph/hermit');
71
+ expect(state.openTab).toHaveBeenCalledWith({
72
+ type: 'graph',
73
+ label: 'hermit',
74
+ teamName: 'hermit',
75
+ });
76
+ });
77
+
78
+ it('opens a report tab for /report/:project/:session', () => {
79
+ const state = mockState();
80
+ restoreInitialRoute(state, '/report/proj-1/sess-9');
81
+ expect(state.openTab).toHaveBeenCalledWith({
82
+ type: 'report',
83
+ label: 'Session Report',
84
+ projectId: 'proj-1',
85
+ sessionId: 'sess-9',
86
+ });
87
+ });
88
+
89
+ it('ignores unknown routes', () => {
90
+ const state = mockState();
91
+ restoreInitialRoute(state, '/something-unknown');
92
+ expect(state.openTab).not.toHaveBeenCalled();
93
+ expect(state.openTeamsTab).not.toHaveBeenCalled();
94
+ });
95
+
96
+ it('trims and ignores leading/trailing slashes', () => {
97
+ const state = mockState();
98
+ restoreInitialRoute(state, '///teams///');
99
+ expect(state.openTeamsTab).toHaveBeenCalledTimes(1);
100
+ });
101
+ });