@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
|
@@ -130,8 +130,8 @@ export const SendMessageDialog = ({
|
|
|
130
130
|
const canAttach = supportsAttachments && canAddMore;
|
|
131
131
|
const attachmentRestrictionReason = !supportsAttachments
|
|
132
132
|
? !isLeadRecipient
|
|
133
|
-
? '
|
|
134
|
-
: '
|
|
133
|
+
? '文件只能发送给 Loop Lead'
|
|
134
|
+
: 'Loop runtime 在线时才能添加文件'
|
|
135
135
|
: undefined;
|
|
136
136
|
|
|
137
137
|
const [pendingAutoClose, setPendingAutoClose] = useState(false);
|
|
@@ -273,7 +273,7 @@ export const SendMessageDialog = ({
|
|
|
273
273
|
);
|
|
274
274
|
|
|
275
275
|
const showFileRestrictionError = useCallback(() => {
|
|
276
|
-
setFileRestrictionError(attachmentRestrictionReason ?? '
|
|
276
|
+
setFileRestrictionError(attachmentRestrictionReason ?? '文件只能发送给 Loop Lead');
|
|
277
277
|
window.clearTimeout(fileRestrictionTimerRef.current);
|
|
278
278
|
fileRestrictionTimerRef.current = window.setTimeout(() => {
|
|
279
279
|
setFileRestrictionError(null);
|
|
@@ -354,8 +354,10 @@ export const SendMessageDialog = ({
|
|
|
354
354
|
/>
|
|
355
355
|
|
|
356
356
|
<DialogHeader>
|
|
357
|
-
<DialogTitle
|
|
358
|
-
<DialogDescription
|
|
357
|
+
<DialogTitle>下发 Loop 指令</DialogTitle>
|
|
358
|
+
<DialogDescription>
|
|
359
|
+
向 runtime 成员下发 Loop 指令或协作请求,用于推进当前工作。
|
|
360
|
+
</DialogDescription>
|
|
359
361
|
</DialogHeader>
|
|
360
362
|
|
|
361
363
|
<div className="grid gap-4 py-2">
|
|
@@ -372,7 +374,7 @@ export const SendMessageDialog = ({
|
|
|
372
374
|
|
|
373
375
|
<div className="grid gap-2">
|
|
374
376
|
<div className="flex items-center gap-2">
|
|
375
|
-
<Label htmlFor="smd-message"
|
|
377
|
+
<Label htmlFor="smd-message">Loop 指令</Label>
|
|
376
378
|
{isLeadRecipient ? (
|
|
377
379
|
<>
|
|
378
380
|
<input
|
|
@@ -400,7 +402,7 @@ export const SendMessageDialog = ({
|
|
|
400
402
|
</TooltipTrigger>
|
|
401
403
|
<TooltipContent side="top">
|
|
402
404
|
{!isTeamAlive
|
|
403
|
-
? '
|
|
405
|
+
? 'Loop runtime 在线时才能添加文件'
|
|
404
406
|
: !canAddMore
|
|
405
407
|
? '已达到附件上限'
|
|
406
408
|
: '添加文件(支持粘贴或拖拽)'}
|
|
@@ -416,7 +418,7 @@ export const SendMessageDialog = ({
|
|
|
416
418
|
error={attachmentError ?? fileRestrictionError}
|
|
417
419
|
onDismissError={clearAttachmentError}
|
|
418
420
|
disabled={attachmentsBlocked}
|
|
419
|
-
disabledHint="
|
|
421
|
+
disabledHint="仅在 Loop runtime 在线且接收人为 Loop Lead 时支持附件。请移除附件或切换接收人。"
|
|
420
422
|
/>
|
|
421
423
|
|
|
422
424
|
<div className={quote ? 'flex flex-col' : 'contents'}>
|
|
@@ -469,7 +471,7 @@ export const SendMessageDialog = ({
|
|
|
469
471
|
<MentionableTextarea
|
|
470
472
|
id="smd-message"
|
|
471
473
|
className={quote ? 'rounded-t-none' : undefined}
|
|
472
|
-
placeholder="
|
|
474
|
+
placeholder="输入 Loop 指令...(回车发送)"
|
|
473
475
|
value={textDraft.value}
|
|
474
476
|
onValueChange={textDraft.setValue}
|
|
475
477
|
suggestions={mentionSuggestions}
|
|
@@ -492,7 +494,7 @@ export const SendMessageDialog = ({
|
|
|
492
494
|
onClick={handleSubmit}
|
|
493
495
|
>
|
|
494
496
|
<Send size={12} />
|
|
495
|
-
{sending ? '
|
|
497
|
+
{sending ? '下发中...' : '下发'}
|
|
496
498
|
</button>
|
|
497
499
|
}
|
|
498
500
|
footerRight={
|
|
@@ -799,7 +799,7 @@ export const TaskDetailDialog = ({
|
|
|
799
799
|
);
|
|
800
800
|
})()
|
|
801
801
|
: null}
|
|
802
|
-
{onDeleteTask && currentTask ? (
|
|
802
|
+
{onDeleteTask && currentTask && currentTask.status !== 'in_progress' ? (
|
|
803
803
|
<Button
|
|
804
804
|
variant="ghost"
|
|
805
805
|
size="sm"
|
|
@@ -828,7 +828,7 @@ export const TaskDetailDialog = ({
|
|
|
828
828
|
<HelpCircle size={14} />
|
|
829
829
|
{currentTask.needsClarification === 'user'
|
|
830
830
|
? '等待你补充说明'
|
|
831
|
-
: '
|
|
831
|
+
: '等待 Loop Lead 补充说明'}
|
|
832
832
|
</span>
|
|
833
833
|
<Button
|
|
834
834
|
variant="ghost"
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreateTeamDialog — bindProject (项目标识) invariant tests.
|
|
3
|
+
*
|
|
4
|
+
* Regression guard for the false "该项目标识已存在" red-box flicker. The
|
|
5
|
+
* auto-generated ASCII identifier must be collision-free against
|
|
6
|
+
* `existingBindProjects` in EVERY rendered frame, so:
|
|
7
|
+
* - the "该项目标识已存在" error never appears for an auto value, and
|
|
8
|
+
* - the create button is never disabled by a phantom collision.
|
|
9
|
+
*
|
|
10
|
+
* The real `bindProjectSlug` generator is exercised (not mocked) so this
|
|
11
|
+
* validates the dialog ↔ generator integration end to end.
|
|
12
|
+
*/
|
|
13
|
+
import React, { act } from 'react';
|
|
14
|
+
import { createRoot } from 'react-dom/client';
|
|
15
|
+
|
|
16
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
17
|
+
|
|
18
|
+
import { generateBindProject } from '@renderer/utils/bindProjectSlug';
|
|
19
|
+
|
|
20
|
+
import { CreateTeamDialog } from '../CreateTeamDialog';
|
|
21
|
+
|
|
22
|
+
// ── Mutable draft state (hoisted so the mocked hook can read it) ────────────
|
|
23
|
+
const draftState = vi.hoisted(() => ({
|
|
24
|
+
teamName: '产品助手',
|
|
25
|
+
cwdMode: 'custom' as 'project' | 'custom',
|
|
26
|
+
customCwd: '/tmp/project',
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('@renderer/hooks/useCreateTeamDraft', () => ({
|
|
30
|
+
useCreateTeamDraft: () => ({
|
|
31
|
+
teamName: draftState.teamName,
|
|
32
|
+
setTeamName: () => undefined,
|
|
33
|
+
cwdMode: draftState.cwdMode,
|
|
34
|
+
setCwdMode: () => undefined,
|
|
35
|
+
selectedProjectPath: '',
|
|
36
|
+
setSelectedProjectPath: () => undefined,
|
|
37
|
+
customCwd: draftState.customCwd,
|
|
38
|
+
setCustomCwd: () => undefined,
|
|
39
|
+
teamColor: '',
|
|
40
|
+
setTeamColor: () => undefined,
|
|
41
|
+
isLoaded: true,
|
|
42
|
+
clearDraft: () => undefined,
|
|
43
|
+
}),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
vi.mock('@renderer/hooks/useTheme', () => ({
|
|
47
|
+
useTheme: () => ({ isLight: false }),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
vi.mock('@renderer/lib/utils', () => ({
|
|
51
|
+
cn: (...args: Array<unknown>): string =>
|
|
52
|
+
args.filter((a): a is string => typeof a === 'string' && a.length > 0).join(' '),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
vi.mock('@renderer/utils/pathNormalize', () => ({
|
|
56
|
+
normalizePath: (p: string) => p,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
vi.mock('@shared/utils/ephemeralProjectPath', () => ({
|
|
60
|
+
isEphemeralProjectPath: () => false,
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
vi.mock('@renderer/api', () => ({
|
|
64
|
+
api: { getProjects: async () => [] },
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
vi.mock('@renderer/api/providers', () => ({
|
|
68
|
+
providersApi: { list: async () => ({ providers: [] }) },
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
vi.mock('@renderer/components/ui/button', () => ({
|
|
72
|
+
Button: ({
|
|
73
|
+
children,
|
|
74
|
+
onClick,
|
|
75
|
+
disabled,
|
|
76
|
+
}: {
|
|
77
|
+
children: React.ReactNode;
|
|
78
|
+
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
|
79
|
+
disabled?: boolean;
|
|
80
|
+
}) =>
|
|
81
|
+
React.createElement(
|
|
82
|
+
'button',
|
|
83
|
+
{ type: 'button', onClick, disabled: Boolean(disabled), 'data-testid': 'btn' },
|
|
84
|
+
children
|
|
85
|
+
),
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
vi.mock('@renderer/components/ui/dialog', () => ({
|
|
89
|
+
Dialog: ({ children }: { children: React.ReactNode }) =>
|
|
90
|
+
React.createElement(React.Fragment, null, children),
|
|
91
|
+
DialogContent: ({ children }: { children: React.ReactNode }) =>
|
|
92
|
+
React.createElement('div', null, children),
|
|
93
|
+
DialogDescription: ({ children }: { children: React.ReactNode }) =>
|
|
94
|
+
React.createElement('div', null, children),
|
|
95
|
+
DialogFooter: ({ children }: { children: React.ReactNode }) =>
|
|
96
|
+
React.createElement('div', null, children),
|
|
97
|
+
DialogHeader: ({ children }: { children: React.ReactNode }) =>
|
|
98
|
+
React.createElement('div', null, children),
|
|
99
|
+
DialogTitle: ({ children }: { children: React.ReactNode }) =>
|
|
100
|
+
React.createElement('div', null, children),
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
vi.mock('@renderer/components/ui/input', () => ({
|
|
104
|
+
Input: (props: Record<string, unknown>) =>
|
|
105
|
+
React.createElement('input', {
|
|
106
|
+
id: props.id as string,
|
|
107
|
+
value: (props.value as string) ?? '',
|
|
108
|
+
onChange: props.onChange as React.ChangeEventHandler<HTMLInputElement>,
|
|
109
|
+
className: props.className as string,
|
|
110
|
+
placeholder: props.placeholder as string,
|
|
111
|
+
'data-testid': props.id === 'team-bind-project' ? 'bind-project' : undefined,
|
|
112
|
+
}),
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
vi.mock('@renderer/components/ui/label', () => ({
|
|
116
|
+
Label: ({ children }: { children: React.ReactNode }) =>
|
|
117
|
+
React.createElement('label', null, children),
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
vi.mock('../../HarnessCards', () => ({
|
|
121
|
+
AGENT_TYPE_LABELS: {} as Record<string, string>,
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
vi.mock('../../HarnessSelect', () => ({
|
|
125
|
+
HarnessSelect: () => React.createElement('div'),
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
vi.mock('../ProjectPathSelector', () => ({
|
|
129
|
+
ProjectPathSelector: () => React.createElement('div'),
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
interface RenderHandle {
|
|
135
|
+
host: HTMLDivElement;
|
|
136
|
+
root: ReturnType<typeof createRoot>;
|
|
137
|
+
rerender: (props: Record<string, unknown>) => Promise<void>;
|
|
138
|
+
unmount: () => Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function renderDialog(props: Record<string, unknown>): Promise<RenderHandle> {
|
|
142
|
+
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
143
|
+
const host = document.createElement('div');
|
|
144
|
+
document.body.appendChild(host);
|
|
145
|
+
const root = createRoot(host);
|
|
146
|
+
|
|
147
|
+
const renderOnce = async (p: Record<string, unknown>) => {
|
|
148
|
+
await act(async () => {
|
|
149
|
+
root.render(React.createElement(CreateTeamDialog, p as never));
|
|
150
|
+
await Promise.resolve();
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
await renderOnce(props);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
host,
|
|
158
|
+
root,
|
|
159
|
+
rerender: (p) => renderOnce(p),
|
|
160
|
+
unmount: async () => {
|
|
161
|
+
await act(async () => {
|
|
162
|
+
root.unmount();
|
|
163
|
+
await Promise.resolve();
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const baseProps = (existingBindProjects: string[]): Record<string, unknown> => ({
|
|
170
|
+
open: true,
|
|
171
|
+
canCreate: true,
|
|
172
|
+
provisioningErrorsByTeam: {},
|
|
173
|
+
clearProvisioningError: () => undefined,
|
|
174
|
+
existingTeamNames: [],
|
|
175
|
+
existingBindProjects,
|
|
176
|
+
provisioningTeamNames: [],
|
|
177
|
+
activeTeams: [],
|
|
178
|
+
onClose: () => undefined,
|
|
179
|
+
onCreate: async () => undefined,
|
|
180
|
+
onOpenTeam: () => undefined,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const findCreateButton = (host: HTMLElement): HTMLButtonElement | null => {
|
|
184
|
+
const buttons = Array.from(host.querySelectorAll<HTMLButtonElement>('button'));
|
|
185
|
+
return buttons.find((b) => b.textContent?.includes('创建数字员工')) ?? null;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const findBindProjectInput = (host: HTMLElement): HTMLInputElement | null =>
|
|
189
|
+
host.querySelector<HTMLInputElement>('[data-testid="bind-project"]');
|
|
190
|
+
|
|
191
|
+
describe('CreateTeamDialog bindProject invariant', () => {
|
|
192
|
+
afterEach(() => {
|
|
193
|
+
document.body.innerHTML = '';
|
|
194
|
+
draftState.teamName = '产品助手';
|
|
195
|
+
draftState.cwdMode = 'custom';
|
|
196
|
+
draftState.customCwd = '/tmp/project';
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('auto-generated id is collision-free and never shows "该项目标识已存在"', async () => {
|
|
200
|
+
// Simulate a prior worker that already took the deterministic id for this name.
|
|
201
|
+
const takenId = generateBindProject(draftState.teamName, new Set());
|
|
202
|
+
const expectedNext = generateBindProject(draftState.teamName, new Set([takenId]));
|
|
203
|
+
|
|
204
|
+
const { host, unmount } = await renderDialog(baseProps([takenId]));
|
|
205
|
+
|
|
206
|
+
const input = findBindProjectInput(host);
|
|
207
|
+
expect(input, 'bind-project input rendered').not.toBeNull();
|
|
208
|
+
expect(input?.value).toBe(expectedNext);
|
|
209
|
+
expect(host.textContent).not.toContain('该项目标识已存在');
|
|
210
|
+
|
|
211
|
+
const createBtn = findCreateButton(host);
|
|
212
|
+
expect(createBtn, 'create button rendered').not.toBeNull();
|
|
213
|
+
expect(createBtn?.disabled).toBe(false);
|
|
214
|
+
|
|
215
|
+
await unmount();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('does NOT flicker "已存在" when the existing list loads after mount', async () => {
|
|
219
|
+
// Teams load asynchronously: dialog first opens with an empty list, then the
|
|
220
|
+
// list populates with an id that collides with the just-generated one. The
|
|
221
|
+
// derived bindProject must jump straight to a free slot in the same render
|
|
222
|
+
// — no transient "已存在" frame is ever produced.
|
|
223
|
+
const { host, rerender, unmount } = await renderDialog(baseProps([]));
|
|
224
|
+
|
|
225
|
+
const firstInput = findBindProjectInput(host);
|
|
226
|
+
const firstValue = firstInput?.value ?? '';
|
|
227
|
+
expect(firstValue).toBe(generateBindProject(draftState.teamName, new Set()));
|
|
228
|
+
expect(host.textContent).not.toContain('该项目标识已存在');
|
|
229
|
+
|
|
230
|
+
// Now the list populates with exactly the id that was just generated.
|
|
231
|
+
await rerender(baseProps([firstValue]));
|
|
232
|
+
|
|
233
|
+
const settledInput = findBindProjectInput(host);
|
|
234
|
+
const expectedAfterLoad = generateBindProject(draftState.teamName, new Set([firstValue]));
|
|
235
|
+
expect(settledInput?.value).toBe(expectedAfterLoad);
|
|
236
|
+
expect(host.textContent).not.toContain('该项目标识已存在');
|
|
237
|
+
expect(findCreateButton(host)?.disabled).toBe(false);
|
|
238
|
+
|
|
239
|
+
await unmount();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('uses the deterministic id when there is no collision', async () => {
|
|
243
|
+
const { host, unmount } = await renderDialog(baseProps([]));
|
|
244
|
+
|
|
245
|
+
const expected = generateBindProject(draftState.teamName, new Set());
|
|
246
|
+
expect(findBindProjectInput(host)?.value).toBe(expected);
|
|
247
|
+
expect(host.textContent).not.toContain('该项目标识已存在');
|
|
248
|
+
|
|
249
|
+
await unmount();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('blocks create when the display name already exists', async () => {
|
|
253
|
+
const { host, unmount } = await renderDialog({
|
|
254
|
+
...baseProps([]),
|
|
255
|
+
existingDisplayNames: [draftState.teamName],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Same name as an existing worker → red error on the name field, create disabled.
|
|
259
|
+
expect(host.textContent).toContain('该名称已存在,请换一个');
|
|
260
|
+
const nameInput = host.querySelector<HTMLInputElement>('#team-name');
|
|
261
|
+
expect(nameInput?.className).toContain('border-[var(--field-error-border)]');
|
|
262
|
+
expect(findCreateButton(host)?.disabled).toBe(true);
|
|
263
|
+
|
|
264
|
+
await unmount();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('allows create when the display name is unique', async () => {
|
|
268
|
+
const { host, unmount } = await renderDialog({
|
|
269
|
+
...baseProps([]),
|
|
270
|
+
existingDisplayNames: ['其他不重名的员工'],
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(host.textContent).not.toContain('该名称已存在,请换一个');
|
|
274
|
+
expect(findCreateButton(host)?.disabled).toBe(false);
|
|
275
|
+
|
|
276
|
+
await unmount();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('does NOT flash "该名称已存在" mid-create when the name lands in the list', async () => {
|
|
280
|
+
// Regression: clicking create updates the parent team list, so the
|
|
281
|
+
// just-created name momentarily appears in existingDisplayNames while the
|
|
282
|
+
// dialog still renders the name step. The duplicate-name red box must stay
|
|
283
|
+
// hidden throughout the in-flight create — no one-frame flicker right
|
|
284
|
+
// before the success screen.
|
|
285
|
+
let resolveCreate: (() => void) | undefined;
|
|
286
|
+
const onCreate = vi.fn(
|
|
287
|
+
() =>
|
|
288
|
+
new Promise<void>((resolve) => {
|
|
289
|
+
resolveCreate = resolve;
|
|
290
|
+
})
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const { host, rerender, unmount } = await renderDialog({
|
|
294
|
+
...baseProps([]),
|
|
295
|
+
existingDisplayNames: [],
|
|
296
|
+
onCreate,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Click create on a unique name → enters submitting state, onCreate pending.
|
|
300
|
+
const createBtn = findCreateButton(host);
|
|
301
|
+
expect(createBtn, 'create button rendered').not.toBeNull();
|
|
302
|
+
await act(async () => {
|
|
303
|
+
createBtn?.click();
|
|
304
|
+
await Promise.resolve();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Button now reads "创建中..." → proves isSubmitting === true mid-create.
|
|
308
|
+
const submittingBtn = Array.from(host.querySelectorAll('button')).find((b) =>
|
|
309
|
+
b.textContent?.includes('创建中')
|
|
310
|
+
);
|
|
311
|
+
expect(submittingBtn, 'create entered submitting state').toBeTruthy();
|
|
312
|
+
|
|
313
|
+
// Parent now reports the just-created name as existing (store caught up).
|
|
314
|
+
await rerender({
|
|
315
|
+
...baseProps([]),
|
|
316
|
+
existingDisplayNames: [draftState.teamName],
|
|
317
|
+
onCreate,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// No flicker: the duplicate-name red box must NOT appear mid-create.
|
|
321
|
+
expect(host.textContent).not.toContain('该名称已存在');
|
|
322
|
+
|
|
323
|
+
// Finish the create → success step. setStep('done') is batched with
|
|
324
|
+
// setIsSubmitting(false) inside createLocalTeam's success block, so the
|
|
325
|
+
// name step unmounts atomically: the just-created name (now in the list)
|
|
326
|
+
// never re-renders the name field, and "该名称已存在" never surfaces.
|
|
327
|
+
await act(async () => {
|
|
328
|
+
resolveCreate?.();
|
|
329
|
+
await Promise.resolve();
|
|
330
|
+
await Promise.resolve();
|
|
331
|
+
});
|
|
332
|
+
expect(host.textContent).toContain('数字员工已创建成功');
|
|
333
|
+
expect(host.textContent).not.toContain('该名称已存在');
|
|
334
|
+
|
|
335
|
+
await unmount();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('opens the JUST-CREATED team, not a regenerated id, after the list refreshes', async () => {
|
|
339
|
+
// Regression: after create succeeds the parent's fetchTeams() refresh adds
|
|
340
|
+
// the just-created slug to existingBindProjects. The live `bindProject`
|
|
341
|
+
// re-derives (its candidate is now taken) and the numeric-counter fallback
|
|
342
|
+
// landed on an UNRELATED existing team's slug (e.g. "team-2"), so the done
|
|
343
|
+
// step's "打开数字员工" button opened the WRONG team. The dialog must capture
|
|
344
|
+
// the slug actually used at creation and open THAT one.
|
|
345
|
+
const createdSlug = generateBindProject(draftState.teamName, new Set());
|
|
346
|
+
// What the live derivation would regenerate to once `createdSlug` is taken:
|
|
347
|
+
// the counter walk lands on "team-2" (an unrelated existing team's slug).
|
|
348
|
+
const regeneratedAfterRefresh = generateBindProject(
|
|
349
|
+
draftState.teamName,
|
|
350
|
+
new Set([createdSlug])
|
|
351
|
+
);
|
|
352
|
+
expect(regeneratedAfterRefresh).not.toBe(createdSlug);
|
|
353
|
+
|
|
354
|
+
const onCreate = vi.fn(async () => undefined);
|
|
355
|
+
const onOpenTeam = vi.fn();
|
|
356
|
+
|
|
357
|
+
const { host, rerender, unmount } = await renderDialog({
|
|
358
|
+
...baseProps([]),
|
|
359
|
+
onCreate,
|
|
360
|
+
onOpenTeam,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Create the team with the unique auto slug → success step.
|
|
364
|
+
const createBtn = findCreateButton(host);
|
|
365
|
+
expect(createBtn, 'create button rendered').not.toBeNull();
|
|
366
|
+
await act(async () => {
|
|
367
|
+
createBtn?.click();
|
|
368
|
+
await Promise.resolve();
|
|
369
|
+
await Promise.resolve();
|
|
370
|
+
await Promise.resolve();
|
|
371
|
+
});
|
|
372
|
+
expect(onCreate).toHaveBeenCalledTimes(1);
|
|
373
|
+
expect(onCreate).toHaveBeenCalledWith(expect.objectContaining({ teamName: createdSlug }));
|
|
374
|
+
expect(host.textContent).toContain('数字员工已创建成功');
|
|
375
|
+
|
|
376
|
+
// Parent list refreshes and now reports the just-created slug as existing.
|
|
377
|
+
await rerender({
|
|
378
|
+
...baseProps([createdSlug]),
|
|
379
|
+
onCreate,
|
|
380
|
+
onOpenTeam,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// The done step's "open" button must use the captured created slug, not the
|
|
384
|
+
// regenerated counter value that collides with an unrelated existing team.
|
|
385
|
+
const openBtn = Array.from(host.querySelectorAll('button')).find((b) =>
|
|
386
|
+
b.textContent?.includes('打开数字员工')
|
|
387
|
+
);
|
|
388
|
+
expect(openBtn, 'open button rendered in done step').toBeTruthy();
|
|
389
|
+
await act(async () => {
|
|
390
|
+
openBtn?.click();
|
|
391
|
+
await Promise.resolve();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(onOpenTeam).toHaveBeenCalledTimes(1);
|
|
395
|
+
expect(onOpenTeam.mock.calls[0][0]).toBe(createdSlug);
|
|
396
|
+
|
|
397
|
+
await unmount();
|
|
398
|
+
});
|
|
399
|
+
});
|