@yancyyu/openhermit 1.6.41 → 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-Br0X83Jf.js → ProjectEditorOverlay-C98qSs7-.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-DHMTbZPZ.js → TeamGraphOverlay-CsBbZwcL.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-DzIiX7yH.js → _basePickBy-ZOyLWjMK.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-6hZuzTLU.js → _baseUniq-DBb726rt.js} +1 -1
- package/dist-renderer/assets/{arc-CXgO6fx_.js → arc-CdiTaR_R.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DKWgtDHr.js → architectureDiagram-VXUJARFQ-Cz3sc5TH.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DOMUcC40.js → blockDiagram-VD42YOAC-DE4c-KJ3.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-B_k2L7qX.js → c4Diagram-YG6GDRKO-CmTMDTrV.js} +1 -1
- package/dist-renderer/assets/channel-KTpqi9eT.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-BeD_ccFy.js → chunk-4BX2VUAB-rhHy3tFl.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-ClZfkA5w.js → chunk-55IACEB6-fLZBzuo_.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-5XluxXsn.js → chunk-B4BG7PRW-DOzxQhim.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-BzIjjNVm.js → chunk-DI55MBZ5-COQCcXC5.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-HgH3MK_H.js → chunk-FMBD7UC4-IKU9U_Y4.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-WeC5T3Ba.js → chunk-QN33PNHL-D6WV154X.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-Cu1ApHfW.js → chunk-QZHKN3VN-D90_2DQp.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BOhlynJM.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-DGZSihDQ.js → cose-bilkent-S5V4N54A-6WiK6U2P.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-CnxwCbku.js → dagre-6UL2VRFP-DF4MMuTn.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-DsIhoxdI.js → diagram-PSM6KHXK-CcF1eZ7E.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-Cmh9KUF5.js → diagram-QEK2KX5R-DYlOVPQB.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-CKxV456A.js → diagram-S2PKOQOG-BHXWsZOP.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-EnvYjOjc.js → erDiagram-Q2GNP2WA-GjmuBx8d.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-BmNeWY_A.js → flowDiagram-NV44I4VS-BuS7YVHk.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D30fyK-u.js → ganttDiagram-JELNMOA3-3Teu5tAa.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-CrUNiYg1.js → gitGraphDiagram-V2S2FVAM-BiLdCYu5.js} +1 -1
- package/dist-renderer/assets/{graph-CY1gTfTb.js → graph-CDP_R8ct.js} +1 -1
- package/dist-renderer/assets/{index-CaEbzwAU.js → index-BSZdT-g-.js} +1 -1
- package/dist-renderer/assets/{index-D5K-SjBG.js → index-BhWvMqsz.js} +1 -1
- package/dist-renderer/assets/{index-9_hO4N1e.js → index-C2_AupSj.js} +1 -1
- package/dist-renderer/assets/{index-59r209c1.js → index-C5ujiwAR.js} +580 -588
- package/dist-renderer/assets/index-CIS2CTK9.css +1 -0
- package/dist-renderer/assets/{index-DMR9B1UP.js → index-CVNjLwkq.js} +1 -1
- package/dist-renderer/assets/{index-BC2hXmg_.js → index-CwG3se0q.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-By_XUlcD.js → infoDiagram-HS3SLOUP-DLHUFo72.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BM1LJE9m.js → journeyDiagram-XKPGCS4Q-BE07RpJD.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DHIW3aTA.js → kanban-definition-3W4ZIXB7-DDHZy4NB.js} +1 -1
- package/dist-renderer/assets/{layout-DAKiL_Mo.js → layout-5nA5wUxO.js} +1 -1
- package/dist-renderer/assets/{linear-DwOaRYea.js → linear-BtF1i2qN.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-b7bJ2cha.js → mindmap-definition-VGOIOE7T-Z1Ui9Sqy.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-DxyL9Zr2.js → pieDiagram-ADFJNKIX-LCjxckWv.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CR33pHlF.js → quadrantDiagram-AYHSOK5B-BOwKjSco.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-BAiSRSlh.js → requirementDiagram-UZGBJVZJ-pChP8Znd.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-C8JmDjoa.js → sankeyDiagram-TZEHDZUN-DifZ2qpo.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-c1d0Wi1m.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-nT8BiH2O.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-DpoRepUA.js → timeline-definition-IT6M3QCI-CPgokIo8.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-C41UJeIH.js → treemap-GDKQZRPO-DAVqSR9L.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-KMjGARKN.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 +1731 -539
- 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 +744 -0
- 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 +132 -52
- 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 +144 -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 +189 -57
- 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 +43 -3
- 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-D0XS_akr.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D13Ffs0U.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D13Ffs0U.js +0 -1
- package/dist-renderer/assets/clone-B1ZrxI1D.js +0 -1
- package/dist-renderer/assets/index-iyjkpSus.css +0 -32
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-Dmibmlso.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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { parseLoopSendIntent, validateLoopSendIntent } from './loopSendIntent';
|
|
4
|
+
|
|
5
|
+
function messageIntent(text = 'do the thing') {
|
|
6
|
+
return parseLoopSendIntent({ text, recipient: 'lead', leadRecipient: 'lead' });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('validateLoopSendIntent', () => {
|
|
10
|
+
it('rejects empty text with a generic command prompt (no Loop branding)', () => {
|
|
11
|
+
const intent = messageIntent(' ');
|
|
12
|
+
const result = validateLoopSendIntent(intent);
|
|
13
|
+
expect(result.ok).toBe(false);
|
|
14
|
+
expect(result.reason).toBeTruthy();
|
|
15
|
+
// The console accepts more than just loop commands — !runtime, !session,
|
|
16
|
+
// /workflows, @team dispatch — so the empty-state prompt must be generic
|
|
17
|
+
// command wording, never branded "Loop 指令".
|
|
18
|
+
expect(result.reason).toMatch(/指令/);
|
|
19
|
+
expect(result.reason).not.toMatch(/Loop/i);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('accepts a non-empty message when the team is alive', () => {
|
|
23
|
+
expect(validateLoopSendIntent(messageIntent('hello'), { isTeamAlive: true }).ok).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('blocks sends while provisioning', () => {
|
|
27
|
+
const result = validateLoopSendIntent(messageIntent('hello'), { isProvisioning: true });
|
|
28
|
+
expect(result.ok).toBe(false);
|
|
29
|
+
expect(result.reason).toMatch(/启动/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('blocks runtime/session intents when the team is offline', () => {
|
|
33
|
+
const runtime = parseLoopSendIntent({
|
|
34
|
+
text: '!runtime do something',
|
|
35
|
+
recipient: 'lead',
|
|
36
|
+
leadRecipient: 'lead',
|
|
37
|
+
});
|
|
38
|
+
expect(runtime.kind).toBe('runtime');
|
|
39
|
+
expect(validateLoopSendIntent(runtime, { isTeamAlive: false }).ok).toBe(false);
|
|
40
|
+
|
|
41
|
+
const session = parseLoopSendIntent({
|
|
42
|
+
text: '/loop-scan',
|
|
43
|
+
recipient: 'lead',
|
|
44
|
+
leadRecipient: 'lead',
|
|
45
|
+
slashCommandMode: 'session',
|
|
46
|
+
});
|
|
47
|
+
expect(session.kind).toBe('session');
|
|
48
|
+
expect(validateLoopSendIntent(session, { isTeamAlive: false }).ok).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('allows a runtime intent when the team is alive', () => {
|
|
52
|
+
const runtime = parseLoopSendIntent({
|
|
53
|
+
text: '!runtime do something',
|
|
54
|
+
recipient: 'lead',
|
|
55
|
+
leadRecipient: 'lead',
|
|
56
|
+
});
|
|
57
|
+
expect(runtime.kind).toBe('runtime');
|
|
58
|
+
expect(validateLoopSendIntent(runtime, { isTeamAlive: true }).ok).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('parseLoopSendIntent /workers', () => {
|
|
63
|
+
it('resolves /workers to workers-list in session (admin) mode', () => {
|
|
64
|
+
const intent = parseLoopSendIntent({
|
|
65
|
+
text: '/workers',
|
|
66
|
+
recipient: 'lead',
|
|
67
|
+
leadRecipient: 'lead',
|
|
68
|
+
slashCommandMode: 'session',
|
|
69
|
+
});
|
|
70
|
+
expect(intent.kind).toBe('workers-list');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('resolves /workers to workers-list even in message (team) mode', () => {
|
|
74
|
+
// /workers is offered in the team console too; it must actually run the
|
|
75
|
+
// client-side worker listing there, not silently fall through to a plain
|
|
76
|
+
// lead message that does nothing.
|
|
77
|
+
const intent = parseLoopSendIntent({
|
|
78
|
+
text: '/workers',
|
|
79
|
+
recipient: 'lead',
|
|
80
|
+
leadRecipient: 'lead',
|
|
81
|
+
slashCommandMode: 'message',
|
|
82
|
+
});
|
|
83
|
+
expect(intent.kind).toBe('workers-list');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { AttachmentPayload, SlashCommandMeta, TaskRef } from '@shared/types';
|
|
2
|
+
import type { WorkflowPromptSummary } from '@shared/types/systemManager';
|
|
3
|
+
|
|
4
|
+
import { parseTeamMentionDirective } from '@renderer/utils/teamMentionDirective';
|
|
5
|
+
|
|
6
|
+
export type LoopSendIntentKind =
|
|
7
|
+
| 'message'
|
|
8
|
+
| 'runtime'
|
|
9
|
+
| 'session'
|
|
10
|
+
| 'cross-team-task'
|
|
11
|
+
| 'workers-list';
|
|
12
|
+
|
|
13
|
+
export interface LoopSendIntentBase {
|
|
14
|
+
kind: LoopSendIntentKind;
|
|
15
|
+
text: string;
|
|
16
|
+
summary?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LoopMessageIntent extends LoopSendIntentBase {
|
|
20
|
+
kind: 'message';
|
|
21
|
+
recipient: string;
|
|
22
|
+
attachments?: AttachmentPayload[];
|
|
23
|
+
taskRefs?: TaskRef[];
|
|
24
|
+
slashCommand?: SlashCommandMeta;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LoopRuntimeIntent extends LoopSendIntentBase {
|
|
28
|
+
kind: 'runtime';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LoopSessionIntent extends LoopSendIntentBase {
|
|
32
|
+
kind: 'session';
|
|
33
|
+
sessionName?: string;
|
|
34
|
+
reuse?: boolean;
|
|
35
|
+
workflowPrompt?: WorkflowPromptSummary;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface LoopCrossTeamTaskIntent extends LoopSendIntentBase {
|
|
39
|
+
kind: 'cross-team-task';
|
|
40
|
+
toTeam: string;
|
|
41
|
+
subject: string;
|
|
42
|
+
taskRefs?: TaskRef[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LoopWorkersListIntent extends LoopSendIntentBase {
|
|
46
|
+
kind: 'workers-list';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type LoopSendIntent =
|
|
50
|
+
| LoopMessageIntent
|
|
51
|
+
| LoopRuntimeIntent
|
|
52
|
+
| LoopSessionIntent
|
|
53
|
+
| LoopCrossTeamTaskIntent
|
|
54
|
+
| LoopWorkersListIntent;
|
|
55
|
+
|
|
56
|
+
export interface LoopSendIntentParseOptions {
|
|
57
|
+
text: string;
|
|
58
|
+
recipient: string;
|
|
59
|
+
leadRecipient: string;
|
|
60
|
+
teamSlugs?: readonly string[];
|
|
61
|
+
attachments?: AttachmentPayload[];
|
|
62
|
+
taskRefs?: TaskRef[];
|
|
63
|
+
slashCommandMode?: 'message' | 'session';
|
|
64
|
+
workflowPrompts?: readonly WorkflowPromptSummary[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface LoopSendIntentValidationContext {
|
|
68
|
+
isTeamAlive?: boolean;
|
|
69
|
+
isProvisioning?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface LoopSendIntentValidationResult {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
reason?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeTeamSlug(value: string): string {
|
|
78
|
+
return value.trim().replace(/^@/, '');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseDirective(text: string): { directive: string; rest: string } | null {
|
|
82
|
+
const match = text.trim().match(/^!(runtime|session|message)\b\s*([\s\S]*)$/i);
|
|
83
|
+
if (!match) return null;
|
|
84
|
+
return {
|
|
85
|
+
directive: match[1].toLowerCase(),
|
|
86
|
+
rest: match[2]?.trim() ?? '',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseSessionDirective(
|
|
91
|
+
rest: string
|
|
92
|
+
): Pick<LoopSessionIntent, 'text' | 'sessionName' | 'reuse'> {
|
|
93
|
+
const reuse = /(?:^|\s)--reuse(?:\s|$)/.test(rest);
|
|
94
|
+
const withoutReuse = rest.replace(/(?:^|\s)--reuse(?=\s|$)/g, ' ').trim();
|
|
95
|
+
const nameMatch = withoutReuse.match(/(?:^|\s)--name\s+"([^"]+)"|(?:^|\s)--name\s+([^\s]+)/);
|
|
96
|
+
const sessionName = (nameMatch?.[1] ?? nameMatch?.[2])?.trim();
|
|
97
|
+
const text = nameMatch ? withoutReuse.replace(nameMatch[0], ' ').trim() : withoutReuse;
|
|
98
|
+
return { text, sessionName, reuse };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function parseLoopSendIntent(options: LoopSendIntentParseOptions): LoopSendIntent {
|
|
102
|
+
const text = options.text.trim();
|
|
103
|
+
const directive = parseDirective(text);
|
|
104
|
+
const attachments =
|
|
105
|
+
options.attachments && options.attachments.length > 0 ? options.attachments : undefined;
|
|
106
|
+
const taskRefs = options.taskRefs && options.taskRefs.length > 0 ? options.taskRefs : undefined;
|
|
107
|
+
|
|
108
|
+
if (directive?.directive === 'runtime') {
|
|
109
|
+
return {
|
|
110
|
+
kind: 'runtime',
|
|
111
|
+
text: directive.rest,
|
|
112
|
+
summary: directive.rest,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (directive?.directive === 'session') {
|
|
117
|
+
const session = parseSessionDirective(directive.rest);
|
|
118
|
+
return {
|
|
119
|
+
kind: 'session',
|
|
120
|
+
text: session.text,
|
|
121
|
+
summary: session.text,
|
|
122
|
+
sessionName: session.sessionName,
|
|
123
|
+
reuse: session.reuse,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const crossTeamDirective = parseTeamMentionDirective(text);
|
|
128
|
+
if (crossTeamDirective) {
|
|
129
|
+
const toTeam = normalizeTeamSlug(crossTeamDirective.mentioned);
|
|
130
|
+
const knownTeam = options.teamSlugs?.some((team) => normalizeTeamSlug(team) === toTeam);
|
|
131
|
+
if (knownTeam) {
|
|
132
|
+
return {
|
|
133
|
+
kind: 'cross-team-task',
|
|
134
|
+
toTeam,
|
|
135
|
+
subject: crossTeamDirective.subject,
|
|
136
|
+
text,
|
|
137
|
+
summary: crossTeamDirective.subject,
|
|
138
|
+
taskRefs,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const messageText = directive?.directive === 'message' ? directive.rest : text;
|
|
144
|
+
// /workers lists discoverable workers via a client-side API call, so it works
|
|
145
|
+
// in any console mode — including the team (message-mode) console, where it's
|
|
146
|
+
// offered in the command list. Without this it would fall through to a plain
|
|
147
|
+
// lead message that does nothing.
|
|
148
|
+
if (/^\/workers(?:\s|$)/i.test(messageText)) {
|
|
149
|
+
return {
|
|
150
|
+
kind: 'workers-list',
|
|
151
|
+
text: messageText,
|
|
152
|
+
summary: '获取当前数字员工列表',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
options.slashCommandMode === 'session' &&
|
|
158
|
+
/^\/[a-z][a-z0-9:-]{0,63}(?:\s|$)/i.test(messageText)
|
|
159
|
+
) {
|
|
160
|
+
const sessionName = messageText.split(/\s+/, 1)[0]?.slice(1) || undefined;
|
|
161
|
+
return {
|
|
162
|
+
kind: 'session',
|
|
163
|
+
text: messageText,
|
|
164
|
+
summary: messageText,
|
|
165
|
+
sessionName,
|
|
166
|
+
reuse: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
kind: 'message',
|
|
172
|
+
recipient: options.recipient || options.leadRecipient,
|
|
173
|
+
text: messageText,
|
|
174
|
+
summary: messageText,
|
|
175
|
+
attachments,
|
|
176
|
+
taskRefs,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function validateLoopSendIntent(
|
|
181
|
+
intent: LoopSendIntent,
|
|
182
|
+
context: LoopSendIntentValidationContext = {}
|
|
183
|
+
): LoopSendIntentValidationResult {
|
|
184
|
+
if (!intent.text.trim()) {
|
|
185
|
+
return { ok: false, reason: '请输入要下发的指令。' };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (context.isProvisioning) {
|
|
189
|
+
return { ok: false, reason: 'Loop runtime 正在启动中,稍后再下发指令。' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if ((intent.kind === 'runtime' || intent.kind === 'session') && context.isTeamAlive === false) {
|
|
193
|
+
return { ok: false, reason: 'Loop runtime 离线,无法直接注入运行时。' };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (intent.kind === 'message' && intent.attachments && context.isTeamAlive === false) {
|
|
197
|
+
return { ok: false, reason: 'Loop runtime 离线时不能发送附件。' };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (intent.kind === 'cross-team-task' && !intent.toTeam) {
|
|
201
|
+
return { ok: false, reason: '请选择目标团队。' };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { ok: true };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function getLoopSendIntentLabel(intent: LoopSendIntent): string {
|
|
208
|
+
switch (intent.kind) {
|
|
209
|
+
case 'runtime':
|
|
210
|
+
return '注入运行时';
|
|
211
|
+
case 'session':
|
|
212
|
+
return intent.reuse ? '复用 Loop 会话' : '新建 Loop 会话';
|
|
213
|
+
case 'cross-team-task':
|
|
214
|
+
return `跨团队派单:${intent.toTeam}`;
|
|
215
|
+
case 'workers-list':
|
|
216
|
+
return '查看数字员工';
|
|
217
|
+
case 'message':
|
|
218
|
+
default:
|
|
219
|
+
return `发送给 ${intent.recipient}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useStore } from '@renderer/store';
|
|
4
|
+
import { resolveProjectIdByPath } from '@renderer/utils/projectLookup';
|
|
5
|
+
import { extractRecentToolActivity, type LeadToolActivity } from '@renderer/utils/leadToolActivity';
|
|
6
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
7
|
+
|
|
8
|
+
const POLL_INTERVAL_MS = 10_000;
|
|
9
|
+
|
|
10
|
+
export interface UseLeadSessionToolActivityOptions {
|
|
11
|
+
teamName: string;
|
|
12
|
+
/** Lead/loop session id to surface tool activity for. */
|
|
13
|
+
sessionId?: string | null;
|
|
14
|
+
/** Workspace path used to resolve the project id that owns the session file. */
|
|
15
|
+
projectPath?: string | null;
|
|
16
|
+
/** When false, the session is treated as offline and polling pauses. */
|
|
17
|
+
isAlive?: boolean;
|
|
18
|
+
/** Max number of recent tool calls to return (newest first). */
|
|
19
|
+
limit?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Surface the lead agent's recent tool calls (Bash/Read/Edit/…) for the Loop
|
|
24
|
+
* console. Tool activity lives in the parsed session detail, not the team
|
|
25
|
+
* message feed, so this fetches the lead session (idempotently, into a
|
|
26
|
+
* loop-console-scoped tab slot) and polls while the team is alive.
|
|
27
|
+
*
|
|
28
|
+
* Works for both the team Loop console (TeamDetailView) and the admin Loop
|
|
29
|
+
* console (SystemManagerView) since it only needs teamName + a session id +
|
|
30
|
+
* the workspace path.
|
|
31
|
+
*/
|
|
32
|
+
export function useLeadSessionToolActivity({
|
|
33
|
+
teamName,
|
|
34
|
+
sessionId,
|
|
35
|
+
projectPath,
|
|
36
|
+
isAlive,
|
|
37
|
+
limit = 8,
|
|
38
|
+
}: UseLeadSessionToolActivityOptions): LeadToolActivity[] {
|
|
39
|
+
const tabId = `loop-tools:${teamName}`;
|
|
40
|
+
|
|
41
|
+
const { projects, repositoryGroups, fetchSessionDetail, sessionDetail } = useStore(
|
|
42
|
+
useShallow((s) => ({
|
|
43
|
+
projects: s.projects,
|
|
44
|
+
repositoryGroups: s.repositoryGroups,
|
|
45
|
+
fetchSessionDetail: s.fetchSessionDetail,
|
|
46
|
+
sessionDetail: tabId ? (s.tabSessionData[tabId]?.sessionDetail ?? null) : null,
|
|
47
|
+
}))
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const projectId = useMemo(
|
|
51
|
+
() => resolveProjectIdByPath(projectPath ?? null, projects, repositoryGroups),
|
|
52
|
+
[projectPath, projects, repositoryGroups]
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!projectId || !sessionId) return;
|
|
57
|
+
void fetchSessionDetail(projectId, sessionId, tabId, { silent: true });
|
|
58
|
+
if (!isAlive) return undefined;
|
|
59
|
+
const handle = window.setInterval(() => {
|
|
60
|
+
void fetchSessionDetail(projectId, sessionId, tabId, { silent: true });
|
|
61
|
+
}, POLL_INTERVAL_MS);
|
|
62
|
+
return () => window.clearInterval(handle);
|
|
63
|
+
}, [fetchSessionDetail, isAlive, projectId, sessionId, tabId]);
|
|
64
|
+
|
|
65
|
+
// Only use the detail when it actually matches the requested session, so a
|
|
66
|
+
// stale detail from a previous session never leaks into the timeline.
|
|
67
|
+
const matchedDetail =
|
|
68
|
+
sessionId && sessionDetail?.session?.id === sessionId ? sessionDetail : null;
|
|
69
|
+
|
|
70
|
+
return useMemo(
|
|
71
|
+
() => extractRecentToolActivity(matchedDetail?.messages ?? [], limit),
|
|
72
|
+
[matchedDetail, limit]
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTaskSuggestions } from '@renderer/hooks/useTaskSuggestions';
|
|
4
|
+
import { useTeamSuggestions } from '@renderer/hooks/useTeamSuggestions';
|
|
5
|
+
import { useProjectWorkflowCommands } from '@renderer/hooks/useProjectWorkflowCommands';
|
|
6
|
+
import { useStore } from '@renderer/store';
|
|
7
|
+
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
|
|
8
|
+
import { getLoopShortcutMentionSuggestions } from '@renderer/utils/loopShortcutSuggestions';
|
|
9
|
+
import { getSuggestedSlashCommandsForProvider } from '@renderer/utils/providerSlashCommands';
|
|
10
|
+
import {
|
|
11
|
+
buildCapabilityPackCommandSuggestions,
|
|
12
|
+
collectSlashSuggestionAliases,
|
|
13
|
+
} from '@renderer/utils/slashCommandRegistry';
|
|
14
|
+
import { buildSlashCommandSuggestions } from '@renderer/utils/skillCommandSuggestions';
|
|
15
|
+
import { CANONICAL_LEAD_MEMBER_NAME, isLeadMember } from '@shared/utils/leadDetection';
|
|
16
|
+
import {
|
|
17
|
+
inferTeamProviderIdFromModel,
|
|
18
|
+
normalizeOptionalTeamProviderId,
|
|
19
|
+
} from '@shared/utils/teamProvider';
|
|
20
|
+
|
|
21
|
+
import type { MentionSuggestion } from '@renderer/types/mention';
|
|
22
|
+
import type { ResolvedTeamMember } from '@shared/types';
|
|
23
|
+
|
|
24
|
+
interface UseLoopCommandSuggestionsOptions {
|
|
25
|
+
teamName: string;
|
|
26
|
+
members: ResolvedTeamMember[];
|
|
27
|
+
commandSuggestions?: MentionSuggestion[];
|
|
28
|
+
/** Team project path — used to load that project's .claude/commands workflow commands. */
|
|
29
|
+
projectPath?: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface UseLoopCommandSuggestionsResult {
|
|
33
|
+
mentionSuggestions: MentionSuggestion[];
|
|
34
|
+
teamSuggestions: MentionSuggestion[];
|
|
35
|
+
taskSuggestions: MentionSuggestion[];
|
|
36
|
+
commandSuggestions: MentionSuggestion[];
|
|
37
|
+
teamSlugs: string[];
|
|
38
|
+
leadRecipient: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatRole(role?: string): string | undefined {
|
|
42
|
+
const value = role?.trim();
|
|
43
|
+
if (!value) return undefined;
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isLoopFocusedSuggestion(suggestion: MentionSuggestion): boolean {
|
|
48
|
+
const haystack = [
|
|
49
|
+
suggestion.name,
|
|
50
|
+
suggestion.command,
|
|
51
|
+
suggestion.description,
|
|
52
|
+
suggestion.searchText,
|
|
53
|
+
]
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join(' ')
|
|
56
|
+
.toLowerCase();
|
|
57
|
+
return /loop|doctor|summary|hygiene|workflow|worktree|memory|scan|daily|workers|诊断|摘要|循环|运维|清理|记忆|数字员工/.test(
|
|
58
|
+
haystack
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function useLoopCommandSuggestions({
|
|
63
|
+
teamName,
|
|
64
|
+
members,
|
|
65
|
+
commandSuggestions: scopedCommandSuggestions,
|
|
66
|
+
projectPath,
|
|
67
|
+
}: UseLoopCommandSuggestionsOptions): UseLoopCommandSuggestionsResult {
|
|
68
|
+
const colorMap = useMemo(() => buildMemberColorMap(members), [members]);
|
|
69
|
+
const mentionSuggestions = useMemo<MentionSuggestion[]>(
|
|
70
|
+
() =>
|
|
71
|
+
members.map((member) => ({
|
|
72
|
+
id: member.name,
|
|
73
|
+
name: member.name,
|
|
74
|
+
subtitle: formatRole(member.role) ?? formatRole(member.agentType),
|
|
75
|
+
color: colorMap.get(member.name),
|
|
76
|
+
})),
|
|
77
|
+
[colorMap, members]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const leadMember = useMemo(
|
|
81
|
+
() => members.find((member) => isLeadMember(member)) ?? members[0],
|
|
82
|
+
[members]
|
|
83
|
+
);
|
|
84
|
+
const leadRecipient = leadMember?.name ?? CANONICAL_LEAD_MEMBER_NAME;
|
|
85
|
+
const leadProviderId = useMemo(
|
|
86
|
+
() =>
|
|
87
|
+
normalizeOptionalTeamProviderId(leadMember?.providerId) ??
|
|
88
|
+
inferTeamProviderIdFromModel(leadMember?.model),
|
|
89
|
+
[leadMember?.model, leadMember?.providerId]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const { suggestions: teamSuggestions } = useTeamSuggestions(teamName);
|
|
93
|
+
const { suggestions: taskSuggestions } = useTaskSuggestions(teamName);
|
|
94
|
+
// Load the team project's own .claude/commands so project-specific commands
|
|
95
|
+
// appear in the team console. Skipped when the caller supplies its own full
|
|
96
|
+
// suggestion set (e.g. the admin console builds its own).
|
|
97
|
+
const projectWorkflowSuggestions = useProjectWorkflowCommands(
|
|
98
|
+
scopedCommandSuggestions ? null : projectPath
|
|
99
|
+
);
|
|
100
|
+
const capabilityPacks = useStore((state) => state.capabilityPacks);
|
|
101
|
+
const fetchCapabilityPacks = useStore((state) => state.fetchCapabilityPacks);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
void fetchCapabilityPacks();
|
|
105
|
+
}, [fetchCapabilityPacks]);
|
|
106
|
+
|
|
107
|
+
const commandSuggestions = useMemo<MentionSuggestion[]>(() => {
|
|
108
|
+
// 指令台命令排序(用户要求):本项目指令 → hermit 指令 → claude 指令。
|
|
109
|
+
// - 本项目:团队项目 .claude/commands(projectWorkflowSuggestions)
|
|
110
|
+
// - hermit:loop 快捷指令 + hermit:* 命令 + 能力包(packs)
|
|
111
|
+
// - claude:claude 内建 slash 命令(baseSuggestions)
|
|
112
|
+
// 调用方传入完整集合(admin 控制台)时 projectWorkflowSuggestions 与 packs 均为空,
|
|
113
|
+
// 退化为 [scoped, claude],与改动前行为一致。
|
|
114
|
+
const baseSuggestions = buildSlashCommandSuggestions(
|
|
115
|
+
getSuggestedSlashCommandsForProvider(leadProviderId),
|
|
116
|
+
[],
|
|
117
|
+
[],
|
|
118
|
+
leadProviderId
|
|
119
|
+
).filter(isLoopFocusedSuggestion);
|
|
120
|
+
|
|
121
|
+
const hermitBase = scopedCommandSuggestions ?? getLoopShortcutMentionSuggestions();
|
|
122
|
+
// 能力包归入 hermit 组;别名冲突检测仍覆盖全部已有命令(本项目+hermit+claude),
|
|
123
|
+
// 与改动前集合等价,仅调整展示顺序。
|
|
124
|
+
const packSuggestions = scopedCommandSuggestions
|
|
125
|
+
? []
|
|
126
|
+
: buildCapabilityPackCommandSuggestions(capabilityPacks, 'team-loop', {
|
|
127
|
+
forceNamespacedAliases: collectSlashSuggestionAliases([
|
|
128
|
+
...projectWorkflowSuggestions,
|
|
129
|
+
...hermitBase,
|
|
130
|
+
...baseSuggestions,
|
|
131
|
+
]),
|
|
132
|
+
});
|
|
133
|
+
// 排序:本项目 → hermit(快捷指令+能力包) → claude。
|
|
134
|
+
const localSuggestions = [
|
|
135
|
+
...projectWorkflowSuggestions,
|
|
136
|
+
...hermitBase,
|
|
137
|
+
...packSuggestions,
|
|
138
|
+
...baseSuggestions,
|
|
139
|
+
];
|
|
140
|
+
const seen = new Set<string>();
|
|
141
|
+
return localSuggestions.filter((suggestion) => {
|
|
142
|
+
const key = suggestion.command ?? suggestion.id;
|
|
143
|
+
if (seen.has(key)) return false;
|
|
144
|
+
seen.add(key);
|
|
145
|
+
return true;
|
|
146
|
+
});
|
|
147
|
+
}, [capabilityPacks, leadProviderId, scopedCommandSuggestions]);
|
|
148
|
+
|
|
149
|
+
const teamSlugs = useMemo(
|
|
150
|
+
() =>
|
|
151
|
+
teamSuggestions.map((suggestion) =>
|
|
152
|
+
suggestion.id.startsWith('team:') ? suggestion.id.slice('team:'.length) : suggestion.name
|
|
153
|
+
),
|
|
154
|
+
[teamSuggestions]
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
mentionSuggestions,
|
|
159
|
+
teamSuggestions,
|
|
160
|
+
taskSuggestions,
|
|
161
|
+
commandSuggestions,
|
|
162
|
+
teamSlugs,
|
|
163
|
+
leadRecipient,
|
|
164
|
+
};
|
|
165
|
+
}
|