@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.
- package/README.md +98 -89
- package/bin/hermit.mjs +96 -0
- package/dist-renderer/assets/{ProjectEditorOverlay-DlFQ6mai.js → ProjectEditorOverlay-C98qSs7-.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-D2TPMPGR.js → TeamGraphOverlay-CsBbZwcL.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-Cmd0RHLQ.js → _basePickBy-ZOyLWjMK.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-BI_iy8ea.js → _baseUniq-DBb726rt.js} +1 -1
- package/dist-renderer/assets/{arc-NzW2mjTP.js → arc-CdiTaR_R.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Bzq85AYv.js → architectureDiagram-VXUJARFQ-Cz3sc5TH.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-D1PvYS-b.js → blockDiagram-VD42YOAC-DE4c-KJ3.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-D49RKzPC.js → c4Diagram-YG6GDRKO-CmTMDTrV.js} +1 -1
- package/dist-renderer/assets/channel-KTpqi9eT.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-fmI_MQmQ.js → chunk-4BX2VUAB-rhHy3tFl.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-Xsv9RCXZ.js → chunk-55IACEB6-fLZBzuo_.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-BE1KO8Um.js → chunk-B4BG7PRW-DOzxQhim.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-tqJ7Mv7f.js → chunk-DI55MBZ5-COQCcXC5.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DMD45MVJ.js → chunk-FMBD7UC4-IKU9U_Y4.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DOhGrz-q.js → chunk-QN33PNHL-D6WV154X.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-D8yDgJdD.js → chunk-QZHKN3VN-D90_2DQp.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BcsEDu7A.js → chunk-TZMSLE5B-BQEil57G.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-lpzulY5X.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-lpzulY5X.js +1 -0
- package/dist-renderer/assets/clone-CriGymY9.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DlSqGHMX.js → cose-bilkent-S5V4N54A-6WiK6U2P.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-BTT9tSAx.js → dagre-6UL2VRFP-DF4MMuTn.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-Du-U-mK2.js → diagram-PSM6KHXK-CcF1eZ7E.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-jFdHeKas.js → diagram-QEK2KX5R-DYlOVPQB.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DKLNK2bu.js → diagram-S2PKOQOG-BHXWsZOP.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CZxHgIIo.js → erDiagram-Q2GNP2WA-GjmuBx8d.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-v4XStCD0.js → flowDiagram-NV44I4VS-BuS7YVHk.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DJjD_BEL.js → ganttDiagram-JELNMOA3-3Teu5tAa.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-BNy-jr03.js → gitGraphDiagram-V2S2FVAM-BiLdCYu5.js} +1 -1
- package/dist-renderer/assets/{graph-DDTrn6je.js → graph-CDP_R8ct.js} +1 -1
- package/dist-renderer/assets/{index-BBp78BAu.js → index-BSZdT-g-.js} +1 -1
- package/dist-renderer/assets/{index-eotrJaYy.js → index-BhWvMqsz.js} +1 -1
- package/dist-renderer/assets/{index-D8_B-cfs.js → index-C2_AupSj.js} +1 -1
- package/dist-renderer/assets/{index-BQrwHZ-k.js → index-C5ujiwAR.js} +580 -588
- package/dist-renderer/assets/index-CIS2CTK9.css +1 -0
- package/dist-renderer/assets/{index-CRKQSG9S.js → index-CVNjLwkq.js} +1 -1
- package/dist-renderer/assets/{index-DR6Wz52b.js → index-CwG3se0q.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DqnOsuza.js → infoDiagram-HS3SLOUP-DLHUFo72.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DTobaO1d.js → journeyDiagram-XKPGCS4Q-BE07RpJD.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-HbwVOvWc.js → kanban-definition-3W4ZIXB7-DDHZy4NB.js} +1 -1
- package/dist-renderer/assets/{layout--VYmTcw2.js → layout-5nA5wUxO.js} +1 -1
- package/dist-renderer/assets/{linear-BsJh89Mr.js → linear-BtF1i2qN.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-BZqUZePd.js → mindmap-definition-VGOIOE7T-Z1Ui9Sqy.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-B1q_nH6P.js → pieDiagram-ADFJNKIX-LCjxckWv.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-UD8QhSEu.js → quadrantDiagram-AYHSOK5B-BOwKjSco.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-BA_i7Nw8.js → requirementDiagram-UZGBJVZJ-pChP8Znd.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CMTnX-2d.js → sankeyDiagram-TZEHDZUN-DifZ2qpo.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BQXDB615.js → sequenceDiagram-WL72ISMW-CJg-WYyY.js} +1 -1
- package/dist-renderer/assets/{splashScene-D0YB9uxm.js → splashScene-94xWCzLA.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BAsPXy6X.js → stateDiagram-FKZM4ZOC-DWHOoFdv.js} +1 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-CGYZOoMb.js +1 -0
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BdasmVkC.js → timeline-definition-IT6M3QCI-CPgokIo8.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-BkKQqIui.js → treemap-GDKQZRPO-DAVqSR9L.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-EAlPHOdx.js → xychartDiagram-PRI3JC2R-CCOcGbrD.js} +1 -1
- package/dist-renderer/chat-community-qr.jpg +0 -0
- package/dist-renderer/fonts/Agave-Bold.ttf +0 -0
- package/dist-renderer/fonts/Agave-Regular.ttf +0 -0
- package/dist-renderer/icon.png +0 -0
- package/dist-renderer/icon.rar +0 -0
- package/dist-renderer/index.html +3 -3
- package/package.json +21 -26
- package/src/features/worker-society/core/application/WorkerSocietyService.test.ts +802 -0
- package/src/features/worker-society/core/application/WorkerSocietyService.ts +428 -0
- package/src/features/worker-society/core/application/fakes.ts +101 -0
- package/src/features/worker-society/core/application/ports.ts +70 -0
- package/src/features/worker-society/core/domain/models/society.ts +141 -0
- package/src/features/worker-society/core/domain/policies/societyPolicies.test.ts +739 -0
- package/src/features/worker-society/core/domain/policies/societyPolicies.ts +496 -0
- package/src/features/worker-society/main/adapters/input/societyMcp.test.ts +317 -0
- package/src/features/worker-society/main/adapters/input/societyMcp.ts +257 -0
- package/src/features/worker-society/main/adapters/input/societyRoutes.test.ts +695 -0
- package/src/features/worker-society/main/adapters/input/societyRoutes.ts +194 -0
- package/src/features/worker-society/main/composition/societyComposition.test.ts +74 -0
- package/src/features/worker-society/main/composition/societyComposition.ts +70 -0
- package/src/features/worker-society/main/composition/workerSocietyPlugin.test.ts +69 -0
- package/src/features/worker-society/main/composition/workerSocietyPlugin.ts +67 -0
- package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.test.ts +132 -0
- package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.ts +84 -0
- package/src/features/worker-society/main/infrastructure/fsStores.test.ts +216 -0
- package/src/features/worker-society/main/infrastructure/fsStores.ts +113 -0
- package/src/features/worker-society/main/infrastructure/mergingProfileStore.test.ts +195 -0
- package/src/features/worker-society/main/infrastructure/mergingProfileStore.ts +96 -0
- package/src/features/worker-society/renderer/SocietyGraph.tsx +166 -0
- package/src/features/worker-society/renderer/SocietyNodeLabels.tsx +139 -0
- package/src/features/worker-society/renderer/SocietyNodeOverlay.tsx +339 -0
- package/src/features/worker-society/renderer/SocietyView.tsx +437 -0
- package/src/features/worker-society/renderer/index.ts +11 -0
- package/src/features/worker-society/renderer/societyApi.test.ts +259 -0
- package/src/features/worker-society/renderer/societyApi.ts +144 -0
- package/src/features/worker-society/renderer/societyGraphAdapter.test.ts +321 -0
- package/src/features/worker-society/renderer/societyGraphAdapter.ts +240 -0
- package/src/features/worker-society/renderer/societyOverlayActions.test.ts +57 -0
- package/src/features/worker-society/renderer/societyOverlayActions.ts +49 -0
- package/src/features/worker-society/renderer/societyStore.test.ts +218 -0
- package/src/features/worker-society/renderer/societyStore.ts +146 -0
- package/src/features/worker-society/renderer/societyViewUtils.test.ts +81 -0
- package/src/features/worker-society/renderer/societyViewUtils.ts +68 -0
- package/src/main/ipc/extensions.ts +27 -0
- package/src/main/server.ts +1709 -534
- package/src/main/services/ccConnect/CcConnectBridge.ts +26 -11
- package/src/main/services/ccConnect/CcConnectClient.ts +9 -2
- package/src/main/services/ccConnect/workDirReconcile.test.ts +57 -0
- package/src/main/services/ccConnect/workDirReconcile.ts +36 -0
- package/src/main/services/direct-cli/DirectCliSessionManager.test.ts +397 -0
- package/src/main/services/direct-cli/DirectCliSessionManager.ts +508 -0
- package/src/main/services/direct-cli/DirectCliSessionStore.test.ts +79 -0
- package/src/main/services/direct-cli/DirectCliSessionStore.ts +97 -0
- package/src/main/services/direct-cli/__tests__/directCliMessageId.test.ts +40 -0
- package/src/main/services/direct-cli/directCliMessageId.ts +21 -0
- package/src/main/services/direct-cli/index.ts +17 -0
- package/src/main/services/extensions/capability-packs/CapabilityPackLoaderService.ts +637 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +2 -2
- package/src/main/services/loop-assets/LoopAssetsScannerService.ts +657 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +33 -5
- package/src/main/services/session-intelligence/LocalSessionScanner.ts +156 -71
- package/src/main/services/session-intelligence/SessionUsageParser.ts +103 -8
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +11 -0
- package/src/main/services/session-intelligence/__tests__/teamSessionListMapper.test.ts +104 -0
- package/src/main/services/session-intelligence/teamSessionListMapper.ts +78 -0
- package/src/main/services/system-manager/AdminLoopInitializer.ts +95 -0
- package/src/main/services/system-manager/BuiltinWorkflowSeeder.ts +679 -74
- package/src/main/services/system-manager/SystemManagerConfigService.ts +19 -1
- package/src/main/services/system-manager/WorkflowPromptService.ts +58 -5
- package/src/main/services/system-manager/__tests__/AdminLoopInitializer.test.ts +129 -0
- package/src/main/services/system-manager/__tests__/SystemManagerConfigService.test.ts +60 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +2 -0
- package/src/main/services/teams-mvp/OpsRunbookContext.ts +60 -0
- package/src/main/services/teams-mvp/TaskDispatchService.test.ts +305 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +250 -131
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +12 -2
- package/src/main/services/teams-mvp/TeamWorkspaceService.test.ts +207 -0
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +104 -51
- package/src/main/services/teams-mvp/index.ts +6 -0
- package/src/main/utils/externalPlatformSessionRouting.ts +92 -0
- package/src/main/utils/toolApprovalRules.ts +151 -0
- package/src/renderer/App.tsx +24 -89
- package/src/renderer/api/httpClient.ts +115 -37
- package/src/renderer/api/providers.ts +5 -16
- package/src/renderer/components/chat/CommunityChatView.tsx +81 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +130 -84
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +39 -5
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +2 -1
- package/src/renderer/components/extensions/capability-packs/CapabilityPacksPanel.tsx +170 -0
- package/src/renderer/components/layout/PaneContent.tsx +10 -2
- package/src/renderer/components/layout/SortableTab.tsx +4 -0
- package/src/renderer/components/layout/TabBarActions.tsx +13 -16
- package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +4 -135
- package/src/renderer/components/schedules/SchedulesView.tsx +22 -14
- package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +7 -6
- package/src/renderer/components/settings/SettingsTabs.tsx +24 -21
- package/src/renderer/components/settings/SettingsView.tsx +22 -13
- package/src/renderer/components/settings/components/SettingRow.tsx +13 -5
- package/src/renderer/components/settings/components/SettingsSectionCard.tsx +53 -0
- package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +10 -6
- package/src/renderer/components/settings/components/SettingsSelect.tsx +12 -9
- package/src/renderer/components/settings/components/SettingsToggle.tsx +6 -5
- package/src/renderer/components/settings/components/index.ts +1 -0
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +78 -59
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +32 -44
- package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
- package/src/renderer/components/settings/sections/GeneralSection.tsx +216 -186
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +25 -17
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +63 -22
- package/src/renderer/components/sidebar/SidebarSessions.tsx +120 -80
- package/src/renderer/components/sidebar/SidebarTaskItem.tsx +1 -1
- package/src/renderer/components/splash/splashScene.ts +6 -2
- package/src/renderer/components/system-manager/SystemManagerView.tsx +169 -255
- package/src/renderer/components/tasks/TasksView.tsx +63 -37
- package/src/renderer/components/team/CcSessionsSection.tsx +124 -89
- package/src/renderer/components/team/HarnessBrandLogos.tsx +318 -0
- package/src/renderer/components/team/HarnessSelect.tsx +25 -26
- package/src/renderer/components/team/TeamDetailView.tsx +137 -153
- package/src/renderer/components/team/TeamEmptyState.tsx +9 -37
- package/src/renderer/components/team/TeamListView.tsx +143 -30
- package/src/renderer/components/team/__tests__/CcSessionsSection.hasLocalFile.test.tsx +128 -0
- package/src/renderer/components/team/activity/ActivityItem.tsx +21 -9
- package/src/renderer/components/team/activity/ActivityTimeline.tsx +2 -2
- package/src/renderer/components/team/dialogs/AdvancedCliSection.tsx +1 -1
- package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +13 -10
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +156 -83
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +9 -157
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +19 -15
- package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +48 -10
- package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +11 -12
- package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +39 -37
- package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +434 -64
- package/src/renderer/components/team/dialogs/SendMessageDialog.tsx +12 -10
- package/src/renderer/components/team/dialogs/TaskDetailDialog.tsx +2 -2
- package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.bindProject.test.tsx +399 -0
- package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.chineseRepro.test.tsx +253 -0
- package/src/renderer/components/team/dialogs/platformAllowUtils.ts +91 -0
- package/src/renderer/components/team/dialogs/teammateRuntimeCompatibility.tsx +1 -1
- package/src/renderer/components/team/kanban/KanbanTaskCard.test.tsx +41 -0
- package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +41 -86
- package/src/renderer/components/team/loop-console/LoopCommandComposer.tsx +310 -0
- package/src/renderer/components/team/loop-console/LoopConsolePanel.tsx +372 -0
- package/src/renderer/components/team/loop-console/loopSendIntent.test.ts +85 -0
- package/src/renderer/components/team/loop-console/loopSendIntent.ts +221 -0
- package/src/renderer/components/team/loop-console/useLeadSessionToolActivity.ts +74 -0
- package/src/renderer/components/team/loop-console/useLoopCommandSuggestions.ts +165 -0
- package/src/renderer/components/team/loop-console/useLoopConsoleController.ts +266 -0
- package/src/renderer/components/team/members/LeadModelRow.test.tsx +1 -1
- package/src/renderer/components/team/members/LeadModelRow.tsx +5 -3
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +11 -0
- package/src/renderer/components/team/members/MemberDetailStats.tsx +13 -3
- package/src/renderer/components/team/members/MemberDraftRow.test.tsx +1 -1
- package/src/renderer/components/team/members/MemberDraftRow.tsx +1 -1
- package/src/renderer/components/team/members/MemberMessagesTab.tsx +2 -2
- package/src/renderer/components/team/members/MemberStatsTab.tsx +1 -1
- package/src/renderer/components/team/messages/MessageComposer.tsx +150 -44
- package/src/renderer/components/team/messages/MessagesFilterPopover.tsx +2 -2
- package/src/renderer/components/team/messages/MessagesPanel.tsx +34 -28
- package/src/renderer/components/team/schedule/CcCronScheduleDialog.tsx +6 -6
- package/src/renderer/components/team/taskLogs/ExactTaskLogCard.tsx +2 -2
- package/src/renderer/components/team/taskLogs/TaskLogStreamSection.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +2 -3
- package/src/renderer/components/ui/MentionableTextarea.tsx +5 -1
- package/src/renderer/constants/teamColors.ts +5 -5
- package/src/renderer/hooks/useExtensionsTabState.ts +1 -1
- package/src/renderer/hooks/useMentionDetection.ts +5 -1
- package/src/renderer/hooks/useProjectWorkflowCommands.ts +57 -0
- package/src/renderer/hooks/useTeamSuggestions.ts +2 -0
- package/src/renderer/index.css +19 -2
- package/src/renderer/main.tsx +7 -1
- package/src/renderer/store/index.ts +18 -1
- package/src/renderer/store/slices/extensionsSlice.ts +83 -0
- package/src/renderer/store/slices/tabSlice.ts +61 -0
- package/src/renderer/store/slices/teamSlice.ts +138 -9
- package/src/renderer/types/mention.ts +8 -0
- package/src/renderer/types/tabs.ts +3 -1
- package/src/renderer/utils/__tests__/bindProjectSlug.test.ts +69 -0
- package/src/renderer/utils/__tests__/groupTransformer.test.ts +148 -0
- package/src/renderer/utils/__tests__/initialRoute.test.ts +101 -0
- package/src/renderer/utils/__tests__/leadToolActivity.test.ts +124 -0
- package/src/renderer/utils/__tests__/mergeTeamMessages.test.ts +81 -0
- package/src/renderer/utils/__tests__/teamMessageFiltering.test.ts +213 -0
- package/src/renderer/utils/__tests__/teamMessageKey.test.ts +75 -0
- package/src/renderer/utils/__tests__/workflowCommandExecution.test.ts +173 -0
- package/src/renderer/utils/__tests__/workflowCommandSuggestions.test.ts +59 -0
- package/src/renderer/utils/bindProjectSlug.ts +57 -0
- package/src/renderer/utils/capabilityCommandExecution.ts +113 -0
- package/src/renderer/utils/initialRoute.ts +89 -0
- package/src/renderer/utils/leadToolActivity.ts +117 -0
- package/src/renderer/utils/loopShortcutSuggestions.ts +106 -0
- package/src/renderer/utils/mentionSuggestions.ts +1 -1
- package/src/renderer/utils/slashCommandRegistry.ts +231 -0
- package/src/renderer/utils/teamMentionDirective.ts +31 -0
- package/src/renderer/utils/workflowCommandExecution.ts +96 -0
- package/src/renderer/utils/workflowCommandSuggestions.ts +49 -0
- package/src/shared/types/api.ts +79 -4
- package/src/shared/types/ccConnect.ts +1 -0
- package/src/shared/types/extensions/api.ts +19 -0
- package/src/shared/types/extensions/capabilityPack.ts +118 -0
- package/src/shared/types/extensions/index.ts +29 -1
- package/src/shared/types/index.ts +6 -0
- package/src/shared/types/loopAssets.ts +54 -0
- package/src/shared/types/providers.ts +0 -16
- package/src/shared/types/systemManager.ts +26 -1
- package/src/shared/types/team.ts +41 -5
- package/src/shared/types/terminal.ts +2 -36
- package/src/shared/types/worker.test.ts +28 -0
- package/src/shared/types/worker.ts +3 -0
- package/src/shared/utils/__tests__/effortLevels.test.ts +88 -0
- package/src/shared/utils/__tests__/providerBackend.test.ts +88 -0
- package/src/shared/utils/__tests__/providerLaunchArgs.test.ts +220 -0
- package/src/shared/utils/claudeStreamJson.test.ts +187 -0
- package/src/shared/utils/claudeStreamJson.ts +153 -0
- package/src/shared/utils/providerLaunchArgs.ts +217 -0
- package/src/shared/utils/slashCommands.ts +10 -0
- package/src/types/node-pty.d.ts +8 -0
- package/dist-renderer/assets/channel-Ch7JrfUu.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-z9I4AnFy.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-z9I4AnFy.js +0 -1
- package/dist-renderer/assets/clone-Dfi1Jx6l.js +0 -1
- package/dist-renderer/assets/index-iyjkpSus.css +0 -32
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DTUIBfce.js +0 -1
- package/src/main/services/system-manager/SystemManagerPtyService.ts +0 -233
- package/src/renderer/components/common/TerminalPane.tsx +0 -213
- 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
|
-
|
|
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:
|
|
3359
|
+
? { ...existingEntry, color: configColor, displayName: resolvedDisplayName }
|
|
3328
3360
|
: {
|
|
3329
3361
|
teamName,
|
|
3330
|
-
displayName:
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
-
|
|
4465
|
-
|
|
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) */
|
|
@@ -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
|
+
});
|