@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
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getGlobalHermitWorkflowDir,
|
|
7
|
+
listBuiltinWorkflowMetadata,
|
|
8
|
+
} from '@main/services/system-manager/BuiltinWorkflowSeeder';
|
|
9
|
+
import { validateOpenPathUserSelected } from '@main/utils/pathValidation';
|
|
10
|
+
import { createLogger } from '@shared/utils/logger';
|
|
11
|
+
import { KNOWN_SLASH_COMMANDS, isSupportedSlashCommandName } from '@shared/utils/slashCommands';
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
CapabilityCommand,
|
|
15
|
+
CapabilityCommandPromptRequest,
|
|
16
|
+
CapabilityCommandPromptResult,
|
|
17
|
+
CapabilityCommandSurface,
|
|
18
|
+
CapabilityPackExportRequest,
|
|
19
|
+
CapabilityPackImportRequest,
|
|
20
|
+
CapabilityPackListResult,
|
|
21
|
+
CapabilityPackManifest,
|
|
22
|
+
CapabilityPackMutationResult,
|
|
23
|
+
CapabilitySafety,
|
|
24
|
+
CapabilityScope,
|
|
25
|
+
CapabilitySkill,
|
|
26
|
+
CapabilityWorkflow,
|
|
27
|
+
LoadedCapabilityPack,
|
|
28
|
+
RegisteredSlashCommand,
|
|
29
|
+
} from '@shared/types/extensions';
|
|
30
|
+
|
|
31
|
+
const logger = createLogger('Extensions:CapabilityPacks');
|
|
32
|
+
const MANIFEST_FILENAME = 'pack.json';
|
|
33
|
+
const MAX_PACK_FILE_COUNT = 500;
|
|
34
|
+
const MAX_PACK_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
35
|
+
const MAX_COMMAND_PROMPT_BYTES = 512 * 1024;
|
|
36
|
+
const CAPABILITY_SCOPES = new Set<CapabilityScope>([
|
|
37
|
+
'admin-loop',
|
|
38
|
+
'team-loop',
|
|
39
|
+
'kanban-card',
|
|
40
|
+
'task-detail',
|
|
41
|
+
]);
|
|
42
|
+
const CAPABILITY_SURFACES = new Set<CapabilityCommandSurface>(['slash', 'quick-run']);
|
|
43
|
+
const CAPABILITY_SAFETY_VALUES = new Set<CapabilitySafety>([
|
|
44
|
+
'read-only',
|
|
45
|
+
'reporting',
|
|
46
|
+
'proposal-only',
|
|
47
|
+
'write',
|
|
48
|
+
'audit',
|
|
49
|
+
]);
|
|
50
|
+
const RESERVED_SLASH_COMMANDS = new Set([
|
|
51
|
+
...KNOWN_SLASH_COMMANDS.map((command) => command.name.toLowerCase()),
|
|
52
|
+
'help',
|
|
53
|
+
'settings',
|
|
54
|
+
'permissions',
|
|
55
|
+
'login',
|
|
56
|
+
'logout',
|
|
57
|
+
'mcp',
|
|
58
|
+
'agents',
|
|
59
|
+
'hooks',
|
|
60
|
+
'memory',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const BUILTIN_HERMIT_OPS_PACK_ID = 'hermit-team-ops';
|
|
64
|
+
const BUILTIN_HERMIT_OPS_PACK_NAMESPACE = 'hermit';
|
|
65
|
+
|
|
66
|
+
function getHermitHome(): string {
|
|
67
|
+
return process.env.HERMIT_HOME ?? path.join(os.homedir(), '.hermit');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toCapabilitySafety(safety: string): CapabilitySafety {
|
|
71
|
+
if (CAPABILITY_SAFETY_VALUES.has(safety as CapabilitySafety)) return safety as CapabilitySafety;
|
|
72
|
+
return 'audit';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createBuiltinHermitOpsPack(): LoadedCapabilityPack {
|
|
76
|
+
const workflows = listBuiltinWorkflowMetadata();
|
|
77
|
+
return {
|
|
78
|
+
manifest: {
|
|
79
|
+
schemaVersion: 1,
|
|
80
|
+
id: BUILTIN_HERMIT_OPS_PACK_ID,
|
|
81
|
+
name: 'Hermit Team Ops',
|
|
82
|
+
namespace: BUILTIN_HERMIT_OPS_PACK_NAMESPACE,
|
|
83
|
+
version: '1.0.0',
|
|
84
|
+
author: 'Hermit',
|
|
85
|
+
description:
|
|
86
|
+
'Hermit 官方预装的团队运维检测包,安装到 ~/.claude/commands/hermit 后可被所有团队复用。',
|
|
87
|
+
capabilities: {
|
|
88
|
+
commands: workflows.map((workflow) => ({
|
|
89
|
+
id: workflow.id,
|
|
90
|
+
alias: workflow.id,
|
|
91
|
+
title: workflow.label,
|
|
92
|
+
description: workflow.description,
|
|
93
|
+
scope: ['admin-loop', 'team-loop'],
|
|
94
|
+
surfaces: ['slash', 'quick-run'],
|
|
95
|
+
safety: toCapabilitySafety(workflow.safety),
|
|
96
|
+
prompt: workflow.filename,
|
|
97
|
+
workflow: workflow.filename,
|
|
98
|
+
order: workflow.order,
|
|
99
|
+
execution: { type: 'loop-session', reuse: true },
|
|
100
|
+
})),
|
|
101
|
+
workflows: workflows.map((workflow) => ({
|
|
102
|
+
id: workflow.id,
|
|
103
|
+
name: workflow.label,
|
|
104
|
+
description: workflow.description,
|
|
105
|
+
path: `workflows/${workflow.filename}`,
|
|
106
|
+
})),
|
|
107
|
+
skills: [],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
packDir: getGlobalHermitWorkflowDir(),
|
|
111
|
+
source: 'builtin',
|
|
112
|
+
enabled: true,
|
|
113
|
+
warnings: [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalizeName(value: unknown): string | null {
|
|
118
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function asStringArray(value: unknown): string[] {
|
|
122
|
+
return Array.isArray(value)
|
|
123
|
+
? value.filter((entry): entry is string => typeof entry === 'string')
|
|
124
|
+
: [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function sanitizePackId(packId: string): string {
|
|
128
|
+
const sanitized = packId.trim().replace(/[^a-zA-Z0-9._-]/g, '-');
|
|
129
|
+
if (!sanitized) throw new Error('Capability pack id is required');
|
|
130
|
+
return sanitized;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isValidSlashToken(value: string): boolean {
|
|
134
|
+
return isSupportedSlashCommandName(value) && !value.includes(':');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function requireValidSlashToken(value: string, fieldName: string): void {
|
|
138
|
+
if (!isValidSlashToken(value)) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`${fieldName} must start with a letter and contain only letters, numbers, hyphens, and underscores`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function filterStringUnion<T extends string>(
|
|
146
|
+
value: unknown,
|
|
147
|
+
allowed: ReadonlySet<T>,
|
|
148
|
+
fieldName: string,
|
|
149
|
+
required: boolean
|
|
150
|
+
): T[] {
|
|
151
|
+
const entries = asStringArray(value);
|
|
152
|
+
const invalid = entries.find((entry) => !allowed.has(entry as T));
|
|
153
|
+
if (invalid) {
|
|
154
|
+
throw new Error(`${fieldName} contains unsupported value: ${invalid}`);
|
|
155
|
+
}
|
|
156
|
+
if (required && entries.length === 0) {
|
|
157
|
+
throw new Error(`${fieldName} requires at least one value`);
|
|
158
|
+
}
|
|
159
|
+
return entries as T[];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export class CapabilityPackLoaderService {
|
|
163
|
+
private readonly rootDir: string;
|
|
164
|
+
|
|
165
|
+
constructor(rootDir = path.join(getHermitHome(), 'capability-packs')) {
|
|
166
|
+
this.rootDir = rootDir;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async list(): Promise<CapabilityPackListResult> {
|
|
170
|
+
const warnings: string[] = [];
|
|
171
|
+
await fs.mkdir(this.rootDir, { recursive: true });
|
|
172
|
+
|
|
173
|
+
let entries: Array<{ name: string; isDirectory: () => boolean }> = [];
|
|
174
|
+
try {
|
|
175
|
+
entries = await fs.readdir(this.rootDir, { withFileTypes: true });
|
|
176
|
+
} catch (error) {
|
|
177
|
+
logger.warn(`Failed to read capability pack root ${this.rootDir}`, error);
|
|
178
|
+
return {
|
|
179
|
+
packs: [],
|
|
180
|
+
warnings: ['Unable to read capability pack directory.'],
|
|
181
|
+
rootDir: this.rootDir,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const loadedPacks = await Promise.all(
|
|
186
|
+
entries
|
|
187
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
188
|
+
.map(async (entry) => {
|
|
189
|
+
const packDir = path.join(this.rootDir, entry.name);
|
|
190
|
+
try {
|
|
191
|
+
return await this.loadPackDir(packDir);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
194
|
+
warnings.push(`${entry.name}: ${message}`);
|
|
195
|
+
logger.warn(`Failed to load capability pack ${packDir}`, error);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const packs: LoadedCapabilityPack[] = [createBuiltinHermitOpsPack()];
|
|
202
|
+
const packIds = new Set<string>(packs.map((pack) => pack.manifest.id));
|
|
203
|
+
for (const pack of loadedPacks) {
|
|
204
|
+
if (!pack) continue;
|
|
205
|
+
if (packIds.has(pack.manifest.id)) {
|
|
206
|
+
warnings.push(
|
|
207
|
+
`${path.basename(pack.packDir)}: Duplicate capability pack id ${pack.manifest.id}`
|
|
208
|
+
);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
packIds.add(pack.manifest.id);
|
|
212
|
+
packs.push(pack);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
packs: packs.sort(
|
|
217
|
+
(a, b) =>
|
|
218
|
+
Number(a.source !== 'builtin') - Number(b.source !== 'builtin') ||
|
|
219
|
+
a.manifest.name.localeCompare(b.manifest.name)
|
|
220
|
+
),
|
|
221
|
+
warnings,
|
|
222
|
+
rootDir: this.rootDir,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async importPack(request: CapabilityPackImportRequest): Promise<CapabilityPackMutationResult> {
|
|
227
|
+
const sourceDir = await this.validateSourceDir(request.sourceDir);
|
|
228
|
+
const sourceManifest = await this.readManifest(path.join(sourceDir, MANIFEST_FILENAME));
|
|
229
|
+
const targetDir = path.join(this.rootDir, sanitizePackId(sourceManifest.id));
|
|
230
|
+
|
|
231
|
+
if (!request.overwrite && (await this.pathExists(targetDir))) {
|
|
232
|
+
throw new Error(`Capability pack ${sourceManifest.id} already exists`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
await fs.mkdir(this.rootDir, { recursive: true });
|
|
236
|
+
if (request.overwrite) {
|
|
237
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
238
|
+
}
|
|
239
|
+
await this.copyDirectory(sourceDir, targetDir);
|
|
240
|
+
|
|
241
|
+
const pack = await this.loadPackDir(targetDir);
|
|
242
|
+
return { pack, warnings: pack.warnings };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async exportPack(request: CapabilityPackExportRequest): Promise<CapabilityPackMutationResult> {
|
|
246
|
+
const packId = sanitizePackId(request.packId);
|
|
247
|
+
const sourceDir = path.join(this.rootDir, packId);
|
|
248
|
+
if (!(await this.pathExists(sourceDir))) {
|
|
249
|
+
throw new Error(`Capability pack ${packId} is not installed`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const validatedDestination = validateOpenPathUserSelected(request.destinationDir);
|
|
253
|
+
if (!validatedDestination.valid || !validatedDestination.normalizedPath) {
|
|
254
|
+
throw new Error(validatedDestination.error ?? 'Invalid export destination');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const destinationRoot = validatedDestination.normalizedPath;
|
|
258
|
+
const destination = path.join(destinationRoot, packId);
|
|
259
|
+
if (!request.overwrite && (await this.pathExists(destination))) {
|
|
260
|
+
throw new Error(`Export destination already contains ${packId}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (request.overwrite) {
|
|
264
|
+
await fs.rm(destination, { recursive: true, force: true });
|
|
265
|
+
}
|
|
266
|
+
await this.copyDirectory(sourceDir, destination);
|
|
267
|
+
const pack = await this.loadPackDir(sourceDir);
|
|
268
|
+
return { pack, warnings: [`Exported to ${destination}`] };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async getCommandPrompt(
|
|
272
|
+
request: CapabilityCommandPromptRequest
|
|
273
|
+
): Promise<CapabilityCommandPromptResult> {
|
|
274
|
+
const canonicalId = normalizeName(request.canonicalId);
|
|
275
|
+
if (!canonicalId) throw new Error('Capability command canonicalId is required');
|
|
276
|
+
|
|
277
|
+
const { packs } = await this.list();
|
|
278
|
+
const registered = this.buildRegisteredCommands(packs, request.scope);
|
|
279
|
+
const command = registered.find((entry) => entry.canonicalId === canonicalId);
|
|
280
|
+
if (!command) {
|
|
281
|
+
throw new Error(`Capability command ${canonicalId} not found`);
|
|
282
|
+
}
|
|
283
|
+
if (command.source === 'builtin') {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Built-in workflow command ${canonicalId} runs via Claude Code slash command`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const pack = packs.find((entry) => entry.manifest.id === command.packId);
|
|
290
|
+
if (!pack) {
|
|
291
|
+
throw new Error(`Capability pack ${command.packId ?? ''} not found`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const prompt = await this.readPromptFile(pack.packDir, command.command.prompt);
|
|
295
|
+
return { command, prompt };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async loadPackDir(packDir: string): Promise<LoadedCapabilityPack> {
|
|
299
|
+
const manifestPath = path.join(packDir, MANIFEST_FILENAME);
|
|
300
|
+
const manifest = await this.readManifest(manifestPath);
|
|
301
|
+
const warnings = await this.validateReferencedFiles(packDir, manifest);
|
|
302
|
+
return {
|
|
303
|
+
manifest,
|
|
304
|
+
packDir,
|
|
305
|
+
source: 'user',
|
|
306
|
+
enabled: true,
|
|
307
|
+
warnings,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async validateSourceDir(sourceDir: string): Promise<string> {
|
|
312
|
+
const validatedSource = validateOpenPathUserSelected(sourceDir);
|
|
313
|
+
if (!validatedSource.valid || !validatedSource.normalizedPath) {
|
|
314
|
+
throw new Error(validatedSource.error ?? 'Invalid import source');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const normalizedSourceDir = validatedSource.normalizedPath;
|
|
318
|
+
const stat = await fs.stat(normalizedSourceDir);
|
|
319
|
+
if (!stat.isDirectory()) {
|
|
320
|
+
throw new Error('Import source must be a directory');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await this.readManifest(path.join(normalizedSourceDir, MANIFEST_FILENAME));
|
|
324
|
+
return normalizedSourceDir;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async readManifest(manifestPath: string): Promise<CapabilityPackManifest> {
|
|
328
|
+
let raw: unknown;
|
|
329
|
+
try {
|
|
330
|
+
raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
331
|
+
} catch (error) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
error instanceof SyntaxError
|
|
334
|
+
? `Invalid ${MANIFEST_FILENAME}: ${error.message}`
|
|
335
|
+
: `Missing ${MANIFEST_FILENAME}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return this.normalizeManifest(raw);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private normalizeManifest(raw: unknown): CapabilityPackManifest {
|
|
343
|
+
if (!raw || typeof raw !== 'object') {
|
|
344
|
+
throw new Error('Capability pack manifest must be an object');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const record = raw as Record<string, unknown>;
|
|
348
|
+
if (record.schemaVersion !== 1) {
|
|
349
|
+
throw new Error('Unsupported capability pack schemaVersion');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const id = normalizeName(record.id);
|
|
353
|
+
const name = normalizeName(record.name);
|
|
354
|
+
const namespace = normalizeName(record.namespace);
|
|
355
|
+
const version = normalizeName(record.version);
|
|
356
|
+
if (!id || !name || !namespace || !version) {
|
|
357
|
+
throw new Error('Capability pack manifest requires id, name, namespace, and version');
|
|
358
|
+
}
|
|
359
|
+
requireValidSlashToken(namespace, 'Capability pack namespace');
|
|
360
|
+
|
|
361
|
+
const capabilities =
|
|
362
|
+
record.capabilities && typeof record.capabilities === 'object'
|
|
363
|
+
? (record.capabilities as Record<string, unknown>)
|
|
364
|
+
: {};
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
schemaVersion: 1,
|
|
368
|
+
id,
|
|
369
|
+
name,
|
|
370
|
+
namespace,
|
|
371
|
+
version,
|
|
372
|
+
author: normalizeName(record.author) ?? undefined,
|
|
373
|
+
description: normalizeName(record.description) ?? undefined,
|
|
374
|
+
capabilities: {
|
|
375
|
+
commands: this.normalizeCommands(capabilities.commands),
|
|
376
|
+
skills: this.normalizeSkills(capabilities.skills),
|
|
377
|
+
workflows: this.normalizeWorkflows(capabilities.workflows),
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private normalizeCommands(value: unknown): CapabilityCommand[] {
|
|
383
|
+
if (!Array.isArray(value)) return [];
|
|
384
|
+
return value.map((entry, index) => {
|
|
385
|
+
if (!entry || typeof entry !== 'object') {
|
|
386
|
+
throw new Error(`Command at index ${index} must be an object`);
|
|
387
|
+
}
|
|
388
|
+
const record = entry as Record<string, unknown>;
|
|
389
|
+
const id = normalizeName(record.id);
|
|
390
|
+
const alias = normalizeName(record.alias);
|
|
391
|
+
const title = normalizeName(record.title);
|
|
392
|
+
const prompt = normalizeName(record.prompt);
|
|
393
|
+
const safety = normalizeName(record.safety);
|
|
394
|
+
if (!id || !alias || !title || !prompt || !safety) {
|
|
395
|
+
throw new Error(`Command at index ${index} requires id, alias, title, prompt, and safety`);
|
|
396
|
+
}
|
|
397
|
+
requireValidSlashToken(alias, `Command at index ${index} alias`);
|
|
398
|
+
const scope = filterStringUnion(
|
|
399
|
+
record.scope,
|
|
400
|
+
CAPABILITY_SCOPES,
|
|
401
|
+
`Command at index ${index} scope`,
|
|
402
|
+
true
|
|
403
|
+
);
|
|
404
|
+
const surfaces = filterStringUnion(
|
|
405
|
+
record.surfaces,
|
|
406
|
+
CAPABILITY_SURFACES,
|
|
407
|
+
`Command at index ${index} surfaces`,
|
|
408
|
+
true
|
|
409
|
+
);
|
|
410
|
+
if (!CAPABILITY_SAFETY_VALUES.has(safety as CapabilitySafety)) {
|
|
411
|
+
throw new Error(`Command at index ${index} has unsupported safety: ${safety}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const executionRecord =
|
|
415
|
+
record.execution && typeof record.execution === 'object'
|
|
416
|
+
? (record.execution as Record<string, unknown>)
|
|
417
|
+
: null;
|
|
418
|
+
const executionType = normalizeName(executionRecord?.type);
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
id,
|
|
422
|
+
alias,
|
|
423
|
+
title,
|
|
424
|
+
description: normalizeName(record.description) ?? undefined,
|
|
425
|
+
scope,
|
|
426
|
+
surfaces,
|
|
427
|
+
safety: safety as CapabilityCommand['safety'],
|
|
428
|
+
prompt,
|
|
429
|
+
usesSkills: asStringArray(record.usesSkills),
|
|
430
|
+
workflow: record.workflow === null ? null : (normalizeName(record.workflow) ?? undefined),
|
|
431
|
+
order: typeof record.order === 'number' ? record.order : undefined,
|
|
432
|
+
execution:
|
|
433
|
+
executionType === 'send-message' || executionType === 'loop-session'
|
|
434
|
+
? { type: executionType, reuse: executionRecord?.reuse === true }
|
|
435
|
+
: undefined,
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private buildRegisteredCommands(
|
|
441
|
+
packs: readonly LoadedCapabilityPack[],
|
|
442
|
+
scope?: CapabilityScope
|
|
443
|
+
): RegisteredSlashCommand[] {
|
|
444
|
+
const registered: RegisteredSlashCommand[] = [];
|
|
445
|
+
for (const pack of packs) {
|
|
446
|
+
if (!pack.enabled) continue;
|
|
447
|
+
const packId = pack.manifest.id;
|
|
448
|
+
const namespace = pack.manifest.namespace.trim().toLowerCase();
|
|
449
|
+
for (const rawCommand of pack.manifest.capabilities.commands ?? []) {
|
|
450
|
+
if (scope && !rawCommand.scope.includes(scope)) continue;
|
|
451
|
+
if (!rawCommand.surfaces.includes('slash')) continue;
|
|
452
|
+
const alias = rawCommand.alias.trim().toLowerCase();
|
|
453
|
+
registered.push({
|
|
454
|
+
canonicalId: `${packId}.${rawCommand.id}`,
|
|
455
|
+
alias,
|
|
456
|
+
namespace,
|
|
457
|
+
slash: `/${alias}`,
|
|
458
|
+
namespacedSlash: `/${namespace}:${alias}`,
|
|
459
|
+
source: pack.source === 'builtin' ? 'builtin' : 'pack',
|
|
460
|
+
packId,
|
|
461
|
+
command: {
|
|
462
|
+
...rawCommand,
|
|
463
|
+
alias,
|
|
464
|
+
execution: rawCommand.execution ?? {
|
|
465
|
+
type: scope === 'admin-loop' ? 'loop-session' : 'send-message',
|
|
466
|
+
reuse: true,
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const byAlias = new Map<string, RegisteredSlashCommand[]>();
|
|
474
|
+
for (const command of registered) {
|
|
475
|
+
byAlias.set(command.alias, [...(byAlias.get(command.alias) ?? []), command]);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return registered.map((command) => {
|
|
479
|
+
const aliasPeers = byAlias.get(command.alias) ?? [];
|
|
480
|
+
const conflictsWith = [
|
|
481
|
+
...aliasPeers
|
|
482
|
+
.filter((peer) => peer.canonicalId !== command.canonicalId)
|
|
483
|
+
.map((peer) => peer.canonicalId),
|
|
484
|
+
...(RESERVED_SLASH_COMMANDS.has(command.alias) ? [`official.${command.alias}`] : []),
|
|
485
|
+
];
|
|
486
|
+
return conflictsWith.length ? { ...command, conflictsWith } : command;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private normalizeSkills(value: unknown): CapabilitySkill[] {
|
|
491
|
+
if (!Array.isArray(value)) return [];
|
|
492
|
+
return value.map((entry, index) => {
|
|
493
|
+
if (!entry || typeof entry !== 'object') {
|
|
494
|
+
throw new Error(`Skill at index ${index} must be an object`);
|
|
495
|
+
}
|
|
496
|
+
const record = entry as Record<string, unknown>;
|
|
497
|
+
const id = normalizeName(record.id);
|
|
498
|
+
const name = normalizeName(record.name);
|
|
499
|
+
const skillPath = normalizeName(record.path);
|
|
500
|
+
if (!id || !name || !skillPath) {
|
|
501
|
+
throw new Error(`Skill at index ${index} requires id, name, and path`);
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
id,
|
|
505
|
+
name,
|
|
506
|
+
description: normalizeName(record.description) ?? undefined,
|
|
507
|
+
path: skillPath,
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private normalizeWorkflows(value: unknown): CapabilityWorkflow[] {
|
|
513
|
+
if (!Array.isArray(value)) return [];
|
|
514
|
+
return value.map((entry, index) => {
|
|
515
|
+
if (!entry || typeof entry !== 'object') {
|
|
516
|
+
throw new Error(`Workflow at index ${index} must be an object`);
|
|
517
|
+
}
|
|
518
|
+
const record = entry as Record<string, unknown>;
|
|
519
|
+
const id = normalizeName(record.id);
|
|
520
|
+
const name = normalizeName(record.name);
|
|
521
|
+
const workflowPath = normalizeName(record.path);
|
|
522
|
+
if (!id || !name || !workflowPath) {
|
|
523
|
+
throw new Error(`Workflow at index ${index} requires id, name, and path`);
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
id,
|
|
527
|
+
name,
|
|
528
|
+
description: normalizeName(record.description) ?? undefined,
|
|
529
|
+
path: workflowPath,
|
|
530
|
+
};
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private async readPromptFile(packDir: string, relativePath: string): Promise<string> {
|
|
535
|
+
if (path.isAbsolute(relativePath) || relativePath.split(/[\\/]/).includes('..')) {
|
|
536
|
+
throw new Error(`Unsafe capability command prompt path: ${relativePath}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const absolutePath = path.join(packDir, relativePath);
|
|
540
|
+
const stat = await fs.stat(absolutePath);
|
|
541
|
+
if (!stat.isFile()) {
|
|
542
|
+
throw new Error(`Capability command prompt must be a file: ${relativePath}`);
|
|
543
|
+
}
|
|
544
|
+
if (stat.size > MAX_COMMAND_PROMPT_BYTES) {
|
|
545
|
+
throw new Error('Capability command prompt is too large');
|
|
546
|
+
}
|
|
547
|
+
return fs.readFile(absolutePath, 'utf8');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private async validateReferencedFiles(
|
|
551
|
+
packDir: string,
|
|
552
|
+
manifest: CapabilityPackManifest
|
|
553
|
+
): Promise<string[]> {
|
|
554
|
+
const warnings: string[] = [];
|
|
555
|
+
const references = [
|
|
556
|
+
...(manifest.capabilities.commands ?? []).map((command) => command.prompt),
|
|
557
|
+
...(manifest.capabilities.skills ?? []).map((skill) => skill.path),
|
|
558
|
+
...(manifest.capabilities.workflows ?? []).map((workflow) => workflow.path),
|
|
559
|
+
];
|
|
560
|
+
|
|
561
|
+
for (const relativePath of references) {
|
|
562
|
+
if (path.isAbsolute(relativePath) || relativePath.split(/[\\/]/).includes('..')) {
|
|
563
|
+
warnings.push(`Ignored unsafe path reference: ${relativePath}`);
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (!(await this.pathExists(path.join(packDir, relativePath)))) {
|
|
567
|
+
warnings.push(`Missing referenced file or folder: ${relativePath}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return warnings;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private async copyDirectory(sourceDir: string, targetDir: string): Promise<void> {
|
|
575
|
+
const files = await this.walkDirectory(sourceDir);
|
|
576
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
577
|
+
for (const file of files) {
|
|
578
|
+
const targetPath = path.join(targetDir, file.relativePath);
|
|
579
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
580
|
+
await fs.copyFile(file.absolutePath, targetPath);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private async walkDirectory(rootDir: string): Promise<
|
|
585
|
+
Array<{
|
|
586
|
+
absolutePath: string;
|
|
587
|
+
relativePath: string;
|
|
588
|
+
}>
|
|
589
|
+
> {
|
|
590
|
+
const allFiles: Array<{ absolutePath: string; relativePath: string }> = [];
|
|
591
|
+
let totalBytes = 0;
|
|
592
|
+
|
|
593
|
+
const visit = async (currentDir: string): Promise<void> => {
|
|
594
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
595
|
+
for (const entry of entries) {
|
|
596
|
+
if (entry.name.startsWith('.')) continue;
|
|
597
|
+
if (entry.isSymbolicLink()) {
|
|
598
|
+
throw new Error('Capability pack cannot contain symbolic links');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
602
|
+
if (entry.isDirectory()) {
|
|
603
|
+
await visit(fullPath);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const stat = await fs.stat(fullPath);
|
|
608
|
+
totalBytes += stat.size;
|
|
609
|
+
if (allFiles.length + 1 > MAX_PACK_FILE_COUNT) {
|
|
610
|
+
throw new Error(`Capability pack has too many files (max ${MAX_PACK_FILE_COUNT})`);
|
|
611
|
+
}
|
|
612
|
+
if (totalBytes > MAX_PACK_TOTAL_BYTES) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
`Capability pack is too large (max ${Math.floor(MAX_PACK_TOTAL_BYTES / (1024 * 1024))} MB)`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
allFiles.push({
|
|
619
|
+
absolutePath: fullPath,
|
|
620
|
+
relativePath: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
await visit(rootDir);
|
|
626
|
+
return allFiles.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private async pathExists(targetPath: string): Promise<boolean> {
|
|
630
|
+
try {
|
|
631
|
+
await fs.stat(targetPath);
|
|
632
|
+
return true;
|
|
633
|
+
} catch {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
@@ -38,13 +38,13 @@ interface FetchOptions {
|
|
|
38
38
|
headers?: Record<string, string>;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
interface FetchResponse {
|
|
41
|
+
export interface FetchResponse {
|
|
42
42
|
statusCode: number;
|
|
43
43
|
headers: Record<string, string | string[] | undefined>;
|
|
44
44
|
body: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function httpsGetFollowRedirects(
|
|
47
|
+
export function httpsGetFollowRedirects(
|
|
48
48
|
url: string,
|
|
49
49
|
options: FetchOptions = {},
|
|
50
50
|
redirectsLeft = MAX_REDIRECTS,
|