@yancyyu/openhermit 1.6.36 → 1.6.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-B_QAoeaA.js → ProjectEditorOverlay-lJZi-9Hp.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-PB9luAZU.js → TeamGraphOverlay-ZEDfZyHb.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-Dfzcp_Ry.js → _basePickBy-CIhniz70.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-B5u2Yiq2.js → _baseUniq-cKAW4Q8I.js} +1 -1
  5. package/dist-renderer/assets/{arc-DElOI7qz.js → arc-YmNsoDXW.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Cf6f4tCu.js → architectureDiagram-VXUJARFQ-DHEls2sX.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-FJUdo9Ry.js → blockDiagram-VD42YOAC-Bpwf1Sbg.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-BvJQS9lb.js → c4Diagram-YG6GDRKO-B0IaQ4w5.js} +1 -1
  9. package/dist-renderer/assets/channel-yIlSKy0e.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-n-SGLbin.js → chunk-4BX2VUAB-DLk-hcFc.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-Dwle9tlA.js → chunk-55IACEB6-1XRmX_Zm.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-Dic8YxQz.js → chunk-B4BG7PRW-1waH1DAD.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-3n5jC1jk.js → chunk-DI55MBZ5-BqpZBtrN.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-BqizUB3O.js → chunk-FMBD7UC4-Bly7vVym.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-JRDmD8o9.js → chunk-QN33PNHL-Ci2QWBAs.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-BxFpQw92.js → chunk-QZHKN3VN-YCqFW7d-.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-ByqPwtW9.js → chunk-TZMSLE5B-B0xGXInl.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-24fHez0s.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-24fHez0s.js +1 -0
  20. package/dist-renderer/assets/clone-BTNuUva-.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CVztr86T.js → cose-bilkent-S5V4N54A-DxcFNQKT.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-CIui920O.js → dagre-6UL2VRFP-DPo_RfZY.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-CyL8-bgb.js → diagram-PSM6KHXK-U3hQsFe4.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-CM_67YoY.js → diagram-QEK2KX5R-OrwrAy0V.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-DtrtPSGg.js → diagram-S2PKOQOG-CXATPWVw.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-bOICzF9d.js → erDiagram-Q2GNP2WA-B0e8AfMF.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CJV1g9Hr.js → flowDiagram-NV44I4VS-CXfzA4jJ.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-CXbhDo09.js → ganttDiagram-JELNMOA3-CMr08qVl.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-vbXopTpS.js → gitGraphDiagram-V2S2FVAM-vYFHpPmy.js} +1 -1
  30. package/dist-renderer/assets/{graph-CY2T-j4q.js → graph-DOe5j8dH.js} +1 -1
  31. package/dist-renderer/assets/{index-Dv7q-OB0.js → index-B2Dy7M2G.js} +1 -1
  32. package/dist-renderer/assets/index-Bi6nrZ4z.css +1 -0
  33. package/dist-renderer/assets/{index-CpyChjme.js → index-BySQS7AB.js} +1 -1
  34. package/dist-renderer/assets/{index-S-i9egm8.js → index-C_okzZXP.js} +1 -1
  35. package/dist-renderer/assets/{index-Mrh4pTHw.js → index-CzWxVCRL.js} +1 -1
  36. package/dist-renderer/assets/{index-Dn-BpzSm.js → index-V7dAKPqd.js} +571 -607
  37. package/dist-renderer/assets/{index-C9ONRXVI.js → index-VJ-MM9xa.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-BNg14AdU.js → infoDiagram-HS3SLOUP-D_WubR0B.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-2_PkPCiu.js → journeyDiagram-XKPGCS4Q-w9ca-1TI.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DjTx7qoU.js → kanban-definition-3W4ZIXB7-Jg9p6_pN.js} +1 -1
  41. package/dist-renderer/assets/{layout-DZlHGGN0.js → layout-B-z3y17c.js} +1 -1
  42. package/dist-renderer/assets/{linear-DnlOm48z.js → linear-D-RTX5UW.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-B-nrgt7V.js → mindmap-definition-VGOIOE7T-CDQmHOYP.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BToJFvWR.js → pieDiagram-ADFJNKIX-D_odsQL7.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C0qoxXvH.js → quadrantDiagram-AYHSOK5B-BRsmYWSA.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-DCKGwsGN.js → requirementDiagram-UZGBJVZJ-ChNE_BOV.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CS6JCcu7.js → sankeyDiagram-TZEHDZUN-C8FtpwKc.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-C9pAWoSR.js → sequenceDiagram-WL72ISMW-DmLCzNcc.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BTTX_v1m.js → stateDiagram-FKZM4ZOC-WJBm4bhu.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-BHk97lQJ.js → stateDiagram-v2-4FDKWEC3-_m6iPPUR.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CSWCEzCQ.js → timeline-definition-IT6M3QCI-BXs_hOJs.js} +1 -1
  52. package/dist-renderer/assets/{treemap-GDKQZRPO-CmiIc68g.js → treemap-GDKQZRPO-o04MA0G9.js} +1 -1
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-DhwSTphI.js → xychartDiagram-PRI3JC2R-Czj69XRd.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +2 -2
  56. package/src/main/ipc/extensions.ts +29 -50
  57. package/src/main/server.ts +17 -26
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +2 -51
  59. package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
  60. package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
  61. package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -7
  62. package/src/renderer/api/httpClient.ts +108 -22
  63. package/src/renderer/components/extensions/ExtensionStoreView.tsx +6 -96
  64. package/src/renderer/components/extensions/plugins/PluginCard.tsx +8 -0
  65. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +13 -8
  66. package/src/renderer/components/team/TeamDetailView.tsx +15 -0
  67. package/src/renderer/components/team/tools/AddMcpInline.tsx +47 -0
  68. package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
  69. package/src/renderer/components/team/tools/McpChip.tsx +42 -0
  70. package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
  71. package/src/renderer/components/team/tools/ToolsSection.tsx +208 -0
  72. package/src/shared/types/extensions/api.ts +9 -0
  73. package/src/shared/types/extensions/index.ts +4 -0
  74. package/src/shared/types/extensions/mcp.ts +41 -0
  75. package/src/shared/utils/extensionNormalizers.ts +22 -0
  76. package/dist-renderer/assets/channel-DnbgZg0A.js +0 -1
  77. package/dist-renderer/assets/classDiagram-2ON5EDUG-BAD4p014.js +0 -1
  78. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BAD4p014.js +0 -1
  79. package/dist-renderer/assets/clone-CRX5ZTPd.js +0 -1
  80. package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
  81. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
  82. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
  83. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
  84. package/src/main/services/extensions/install/McpInstallService.ts +0 -407
  85. package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
  86. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
  87. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
  88. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
  89. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
  90. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
  91. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
@@ -6,6 +6,8 @@
6
6
  * to run in a regular browser connected to an HTTP server.
7
7
  */
8
8
 
9
+ import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
10
+
9
11
  import type { DashboardRecentProjectsPayload } from '@features/recent-projects/contracts';
10
12
  import type {
11
13
  AddMemberRequest,
@@ -17,10 +19,13 @@ import type {
17
19
  BoardTaskExactLogSummariesResponse,
18
20
  BoardTaskLogStreamResponse,
19
21
  BoardTaskLogStreamSummary,
22
+ CcSession,
23
+ CcSessionDetail,
20
24
  ClaudeMdFileInfo,
21
25
  ClaudeRootFolderSelection,
22
26
  ClaudeRootInfo,
23
27
  CliInstallerAPI,
28
+ CollabTask,
24
29
  ConfigAPI,
25
30
  ContextInfo,
26
31
  ConversationGroup,
@@ -39,6 +44,7 @@ import type {
39
44
  MachineRuntimeProcess,
40
45
  MemberFullStats,
41
46
  MemberLogSummary,
47
+ MemberSpawnStatusesSnapshot,
42
48
  NotificationsAPI,
43
49
  NotificationTrigger,
44
50
  PaginatedSessionsResult,
@@ -65,6 +71,7 @@ import type {
65
71
  SshLastConnection,
66
72
  SubagentDetail,
67
73
  TaskComment,
74
+ TeamAgentRuntimeSnapshot,
68
75
  TeamChangeEvent,
69
76
  TeamClaudeLogsQuery,
70
77
  TeamClaudeLogsResponse,
@@ -95,26 +102,9 @@ import type {
95
102
  UpdateSchedulePatch,
96
103
  WaterfallData,
97
104
  WslClaudeRootCandidate,
98
- MemberSpawnStatusesSnapshot,
99
- TeamAgentRuntimeSnapshot,
100
- CcSession,
101
- CcSessionDetail,
102
- CollabTask,
103
105
  } from '@shared/types';
104
-
105
- import type {
106
- AgentChangeSet,
107
- ApplyReviewResult,
108
- ChangeStats,
109
- ConflictCheckResult,
110
- FileChangeWithContent,
111
- HunkDecision,
112
- RejectResult,
113
- TaskChangeSetV2,
114
- } from '@shared/types/review';
115
- import type { CliProviderStatus } from '@shared/types/cliInstaller';
116
106
  import type { AgentConfig } from '@shared/types/api';
117
- import type { CliArgsValidationResult } from '@shared/utils/cliArgsParser';
107
+ import type { CliProviderStatus } from '@shared/types/cliInstaller';
118
108
  import type { EditorAPI, ProjectAPI, WorkspaceListResponse } from '@shared/types/editor';
119
109
  import type {
120
110
  EnrichedPlugin,
@@ -122,6 +112,10 @@ import type {
122
112
  McpCatalogItem,
123
113
  McpCustomInstallRequest,
124
114
  McpInstallRequest,
115
+ McpLibraryEntry,
116
+ McpLibraryImportRequest,
117
+ McpLibraryImportResult,
118
+ McpLibraryUpsertRequest,
125
119
  McpSearchResult,
126
120
  McpServerDiagnostic,
127
121
  OperationResult,
@@ -136,8 +130,19 @@ import type {
136
130
  SkillUpsertRequest,
137
131
  SkillWatcherEvent,
138
132
  } from '@shared/types/extensions';
133
+ import type {
134
+ AgentChangeSet,
135
+ ApplyReviewResult,
136
+ ChangeStats,
137
+ ConflictCheckResult,
138
+ FileChangeWithContent,
139
+ HunkDecision,
140
+ RejectResult,
141
+ TaskChangeSetV2,
142
+ } from '@shared/types/review';
139
143
  import type { ApplyReviewRequest } from '@shared/types/review';
140
144
  import type { TerminalAPI } from '@shared/types/terminal';
145
+ import type { CliArgsValidationResult } from '@shared/utils/cliArgsParser';
141
146
 
142
147
  export class HttpAPIClient implements ElectronAPI {
143
148
  private baseUrl: string;
@@ -166,7 +171,7 @@ export class HttpAPIClient implements ElectronAPI {
166
171
  };
167
172
  }
168
173
 
169
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- event callbacks have varying signatures
174
+
170
175
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- event callbacks have varying signatures
171
176
  private addEventListener(channel: string, callback: (...args: any[]) => void): () => void {
172
177
  this.initEventSource();
@@ -1873,7 +1878,41 @@ export class HttpAPIClient implements ElectronAPI {
1873
1878
  }
1874
1879
  })
1875
1880
  );
1876
- const providers = providerResults.filter((p): p is NonNullable<typeof p> => p !== null);
1881
+ // The cc-connect sidecar backend does not implement per-provider status
1882
+ // endpoints (they return an empty array), so drop any malformed entries
1883
+ // and only keep real provider objects.
1884
+ const validProviders = providerResults.filter(
1885
+ (p): p is CliProviderStatus =>
1886
+ p != null &&
1887
+ typeof p === 'object' &&
1888
+ !Array.isArray(p) &&
1889
+ typeof (p).providerId === 'string'
1890
+ );
1891
+
1892
+ // When the backend reports no provider capability data, fall back to a
1893
+ // sane Anthropic provider so extension management (plugins/MCP/skills)
1894
+ // is not falsely gated off. The backend remains the source of truth and
1895
+ // will reject an install if the runtime genuinely cannot perform it.
1896
+ const providers =
1897
+ validProviders.length > 0
1898
+ ? validProviders
1899
+ : [
1900
+ {
1901
+ providerId: 'anthropic',
1902
+ displayName: 'Anthropic',
1903
+ supported: true,
1904
+ authenticated: true,
1905
+ authMethod: null,
1906
+ verificationState: 'verified',
1907
+ models: [],
1908
+ canLoginFromUi: true,
1909
+ capabilities: {
1910
+ teamLaunch: true,
1911
+ oneShot: true,
1912
+ extensions: createDefaultCliExtensionCapabilities(),
1913
+ },
1914
+ } satisfies CliProviderStatus,
1915
+ ];
1877
1916
 
1878
1917
  return {
1879
1918
  flavor: 'agent_teams_orchestrator',
@@ -1887,7 +1926,7 @@ export class HttpAPIClient implements ElectronAPI {
1887
1926
  launchError: null,
1888
1927
  latestVersion: null,
1889
1928
  updateAvailable: false,
1890
- authLoggedIn: result.authenticated,
1929
+ authLoggedIn: result.authenticated ?? true,
1891
1930
  authStatusChecking: true,
1892
1931
  authMethod: null,
1893
1932
  providers,
@@ -1914,7 +1953,21 @@ export class HttpAPIClient implements ElectronAPI {
1914
1953
  },
1915
1954
  getProviderStatus: async (providerId: string): Promise<CliProviderStatus | null> => {
1916
1955
  try {
1917
- return await this.get(`/api/cli/provider/${encodeURIComponent(providerId)}/status`);
1956
+ const result = await this.get<unknown>(
1957
+ `/api/cli/provider/${encodeURIComponent(providerId)}/status`
1958
+ );
1959
+ // The cc-connect sidecar returns an empty array for unimplemented
1960
+ // provider endpoints. Treat any malformed payload as "no data" (null)
1961
+ // so it does not overwrite the synthesized provider in getStatus().
1962
+ if (
1963
+ result == null ||
1964
+ typeof result !== 'object' ||
1965
+ Array.isArray(result) ||
1966
+ typeof (result as CliProviderStatus).providerId !== 'string'
1967
+ ) {
1968
+ return null;
1969
+ }
1970
+ return result as CliProviderStatus;
1918
1971
  } catch {
1919
1972
  return null;
1920
1973
  }
@@ -2074,6 +2127,39 @@ export class HttpAPIClient implements ElectronAPI {
2074
2127
  if (!result.success) return {};
2075
2128
  return result.data ?? {};
2076
2129
  },
2130
+ libraryList: async () => {
2131
+ const result = await this.get<{
2132
+ success: boolean;
2133
+ data?: McpLibraryEntry[];
2134
+ error?: string;
2135
+ }>('/api/extensions/mcp/library');
2136
+ if (!result.success) return [];
2137
+ return result.data ?? [];
2138
+ },
2139
+ libraryUpsert: async (request: McpLibraryUpsertRequest) => {
2140
+ const result = await this.post<{
2141
+ success: boolean;
2142
+ data?: McpLibraryEntry;
2143
+ error?: string;
2144
+ }>('/api/extensions/mcp/library', request);
2145
+ if (!result.success || !result.data) throw new Error(result.error ?? 'Save failed');
2146
+ return result.data;
2147
+ },
2148
+ libraryDelete: async (id: string) => {
2149
+ const result = await this.delete<{ success: boolean; error?: string }>(
2150
+ `/api/extensions/mcp/library/${encodeURIComponent(id)}`
2151
+ );
2152
+ if (!result.success) throw new Error(result.error ?? 'Delete failed');
2153
+ },
2154
+ libraryImport: async (request: McpLibraryImportRequest) => {
2155
+ const result = await this.post<{
2156
+ success: boolean;
2157
+ data?: McpLibraryImportResult;
2158
+ error?: string;
2159
+ }>('/api/extensions/mcp/library/import', request);
2160
+ if (!result.success) throw new Error(result.error ?? 'Import failed');
2161
+ return result.data ?? { imported: [], skipped: [] };
2162
+ },
2077
2163
  };
2078
2164
 
2079
2165
  skills = {
@@ -4,7 +4,7 @@
4
4
  * Global catalog data comes from Zustand store.
5
5
  */
6
6
 
7
- import { useCallback, useEffect, useMemo, useState } from 'react';
7
+ import { useCallback, useEffect, useMemo } from 'react';
8
8
 
9
9
  // Stubs for removed codex-account feature
10
10
  function useCodexAccountSnapshot(_opts: { enabled: boolean; includeRateLimits?: boolean }) {
@@ -41,22 +41,16 @@ import { getRuntimeDisplayName } from '@renderer/utils/runtimeDisplayName';
41
41
  import { getCliProviderExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
42
42
  import {
43
43
  AlertTriangle,
44
- BookOpen,
45
44
  Info,
46
45
  Loader2,
47
- Plus,
48
46
  Puzzle,
49
47
  RefreshCw,
50
- Server,
51
48
  Sliders,
52
49
  } from 'lucide-react';
53
50
  import { useShallow } from 'zustand/react/shallow';
54
51
 
55
- import { CustomMcpServerDialog } from './mcp/CustomMcpServerDialog';
56
52
  import { EnvVarPanel } from './env/EnvVarPanel';
57
- import { McpServersPanel } from './mcp/McpServersPanel';
58
53
  import { PluginsPanel } from './plugins/PluginsPanel';
59
- import { SkillsPanel } from './skills/SkillsPanel';
60
54
  import { StoreExtensionToast } from './common/ExtensionToast';
61
55
  import { ExtensionsSubTabTrigger } from './ExtensionsSubTabTrigger';
62
56
 
@@ -124,18 +118,6 @@ const EXTENSION_SUB_TABS = [
124
118
  icon: Puzzle,
125
119
  description: 'Claude Code 私有扩展,增强运行时的能力与集成。',
126
120
  },
127
- {
128
- value: 'mcp-servers' as const,
129
- label: 'MCP 服务器',
130
- icon: Server,
131
- description: '连接外部工具和应用,让运行时可以读取数据或执行本应用之外的操作。',
132
- },
133
- {
134
- value: 'skills' as const,
135
- label: '技能',
136
- icon: BookOpen,
137
- description: '面向常见任务的可复用指令,帮助运行时更稳定地处理重复工作。',
138
- },
139
121
  {
140
122
  value: 'env-vars' as const,
141
123
  label: '环境变量',
@@ -145,16 +127,10 @@ const EXTENSION_SUB_TABS = [
145
127
  ] as const;
146
128
 
147
129
  export const ExtensionStoreView = (): React.JSX.Element => {
148
- const tabId = useTabIdOptional();
149
130
  const {
150
131
  bootstrapCliStatus,
151
132
  fetchCliStatus,
152
133
  fetchPluginCatalog,
153
- fetchSkillsCatalog,
154
- mcpBrowse,
155
- mcpFetchInstalled,
156
- mcpBrowseLoading,
157
- skillsLoading,
158
134
  cliStatus,
159
135
  cliStatusLoading,
160
136
  cliProviderStatusLoading,
@@ -168,12 +144,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
168
144
  bootstrapCliStatus: s.bootstrapCliStatus,
169
145
  fetchCliStatus: s.fetchCliStatus,
170
146
  fetchPluginCatalog: s.fetchPluginCatalog,
171
- fetchSkillsCatalog: s.fetchSkillsCatalog,
172
147
  pluginCatalog: s.pluginCatalog,
173
- mcpBrowse: s.mcpBrowse,
174
- mcpFetchInstalled: s.mcpFetchInstalled,
175
- mcpBrowseLoading: s.mcpBrowseLoading,
176
- skillsLoading: s.skillsLoading,
177
148
  cliStatus: s.cliStatus,
178
149
  cliStatusLoading: s.cliStatusLoading,
179
150
  cliProviderStatusLoading: s.cliProviderStatusLoading,
@@ -229,6 +200,9 @@ export const ExtensionStoreView = (): React.JSX.Element => {
229
200
  const runtimeDisplayName = getRuntimeDisplayName(effectiveCliStatus, multimodelEnabled);
230
201
  const cliInstalled = effectiveCliStatus?.installed ?? true;
231
202
  const hasOngoingSessions = sessions.some((sess) => sess.isOngoing);
203
+
204
+ const tabState = useExtensionsTabState();
205
+ const tabId = useTabIdOptional();
232
206
  const extensionsTabProjectId = useStore((s) =>
233
207
  tabId
234
208
  ? (s.paneLayout.panes.flatMap((pane) => pane.tabs).find((tab) => tab.id === tabId)
@@ -236,8 +210,6 @@ export const ExtensionStoreView = (): React.JSX.Element => {
236
210
  : null
237
211
  );
238
212
 
239
- const tabState = useExtensionsTabState();
240
- const [customMcpDialogOpen, setCustomMcpDialogOpen] = useState(false);
241
213
  const resolvedProject = useMemo(
242
214
  () => resolveProjectPathById(extensionsTabProjectId, projects, repositoryGroups),
243
215
  [extensionsTabProjectId, projects, repositoryGroups]
@@ -254,42 +226,25 @@ export const ExtensionStoreView = (): React.JSX.Element => {
254
226
  });
255
227
  }, [bootstrapCliStatus, fetchCliStatus, multimodelEnabled]);
256
228
 
257
- // Fetch MCP installed state on mount
258
- useEffect(() => {
259
- void mcpFetchInstalled(projectPath ?? undefined);
260
- }, [mcpFetchInstalled, projectPath]);
261
-
262
229
  // Fetch Plugin catalog on mount / project change
263
230
  useEffect(() => {
264
231
  void fetchPluginCatalog(projectPath ?? undefined);
265
232
  }, [fetchPluginCatalog, projectPath]);
266
233
 
267
- // Fetch Skills catalog on mount / project change
268
- useEffect(() => {
269
- void fetchSkillsCatalog(projectPath ?? undefined);
270
- }, [fetchSkillsCatalog, projectPath]);
271
-
272
- // Refresh all data (MCP + skills + runtime status)
234
+ // Refresh all data
273
235
  const handleRefresh = useCallback(() => {
274
236
  void refreshCliStatusForCurrentMode({
275
237
  multimodelEnabled,
276
238
  bootstrapCliStatus,
277
239
  fetchCliStatus,
278
240
  });
279
- void mcpBrowse(); // re-fetch first page
280
- void mcpFetchInstalled(projectPath ?? undefined);
281
- void fetchSkillsCatalog(projectPath ?? undefined);
282
241
  }, [
283
242
  bootstrapCliStatus,
284
243
  fetchCliStatus,
285
- fetchSkillsCatalog,
286
244
  multimodelEnabled,
287
- mcpBrowse,
288
- mcpFetchInstalled,
289
- projectPath,
290
245
  ]);
291
246
 
292
- const isRefreshing = effectiveCliStatusLoading || mcpBrowseLoading || skillsLoading;
247
+ const isRefreshing = effectiveCliStatusLoading;
293
248
  const cliStatusBanner = useMemo(() => {
294
249
  const providers = effectiveCliStatus?.providers ?? [];
295
250
  const visibleProviders = filterExtensionStoreProviders(
@@ -535,17 +490,6 @@ export const ExtensionStoreView = (): React.JSX.Element => {
535
490
  />
536
491
  ))}
537
492
  </TabsList>
538
- {tabState.activeSubTab === 'mcp-servers' && (
539
- <Button
540
- variant="outline"
541
- size="sm"
542
- onClick={() => setCustomMcpDialogOpen(true)}
543
- className="mb-1 whitespace-nowrap"
544
- >
545
- <Plus className="mr-1 size-3.5" />
546
- 添加自定义
547
- </Button>
548
- )}
549
493
  </div>
550
494
 
551
495
  <TabsContent value="plugins" className="mt-0 pt-4">
@@ -567,44 +511,10 @@ export const ExtensionStoreView = (): React.JSX.Element => {
567
511
  />
568
512
  </TabsContent>
569
513
 
570
- <TabsContent value="mcp-servers" className="mt-0 pt-4">
571
- <McpServersPanel
572
- projectPath={projectPath}
573
- mcpSearchQuery={tabState.mcpSearchQuery}
574
- mcpSearch={tabState.mcpSearch}
575
- mcpSearchResults={tabState.mcpSearchResults}
576
- mcpSearchLoading={tabState.mcpSearchLoading}
577
- mcpSearchWarnings={tabState.mcpSearchWarnings}
578
- selectedMcpServerId={tabState.selectedMcpServerId}
579
- setSelectedMcpServerId={tabState.setSelectedMcpServerId}
580
- cliStatus={effectiveCliStatus}
581
- cliStatusLoading={effectiveCliStatusLoading}
582
- />
583
- </TabsContent>
584
-
585
- <TabsContent value="skills" className="mt-0 pt-4">
586
- <SkillsPanel
587
- projectPath={projectPath}
588
- projectLabel={projectLabel}
589
- skillsSearchQuery={tabState.skillsSearchQuery}
590
- setSkillsSearchQuery={tabState.setSkillsSearchQuery}
591
- skillsSort={tabState.skillsSort}
592
- setSkillsSort={tabState.setSkillsSort}
593
- selectedSkillId={tabState.selectedSkillId}
594
- setSelectedSkillId={tabState.setSelectedSkillId}
595
- />
596
- </TabsContent>
597
-
598
514
  <TabsContent value="env-vars" className="mt-0 pt-4">
599
515
  <EnvVarPanel projectPath={projectPath} />
600
516
  </TabsContent>
601
517
  </Tabs>
602
-
603
- {/* Custom MCP server dialog (lifted to store view level) */}
604
- <CustomMcpServerDialog
605
- open={customMcpDialogOpen}
606
- onClose={() => setCustomMcpDialogOpen(false)}
607
- />
608
518
  </div>
609
519
  </div>
610
520
  </div>
@@ -10,6 +10,7 @@ import {
10
10
  getPluginOperationKey,
11
11
  hasInstallationInScope,
12
12
  inferCapabilities,
13
+ isEssentialPlugin,
13
14
  normalizeCategory,
14
15
  } from '@shared/utils/extensionNormalizers';
15
16
  import { Tag } from 'lucide-react';
@@ -77,6 +78,13 @@ export const PluginCard = ({
77
78
  </div>
78
79
  </div>
79
80
  )}
81
+ {isEssentialPlugin(plugin) && (
82
+ <div className="pointer-events-none absolute -right-px -top-px size-16 overflow-hidden">
83
+ <div className="absolute right-[-24px] top-[4px] w-[80px] rotate-45 bg-amber-500/90 text-center text-[9px] font-semibold leading-[18px] text-white shadow-sm">
84
+ ⭐ 必装
85
+ </div>
86
+ </div>
87
+ )}
80
88
 
81
89
  {/* Header: name + status/meta */}
82
90
  <div className="flex items-start justify-between gap-2">
@@ -16,7 +16,11 @@ import {
16
16
  SelectValue,
17
17
  } from '@renderer/components/ui/select';
18
18
  import { useStore } from '@renderer/store';
19
- import { inferCapabilities, normalizeCategory } from '@shared/utils/extensionNormalizers';
19
+ import {
20
+ inferCapabilities,
21
+ isEssentialPlugin,
22
+ normalizeCategory,
23
+ } from '@shared/utils/extensionNormalizers';
20
24
  import { getCliProviderExtensionCapability } from '@shared/utils/providerExtensionCapabilities';
21
25
  import { ArrowUpDown, Filter, Puzzle, Search } from 'lucide-react';
22
26
  import { useShallow } from 'zustand/react/shallow';
@@ -100,9 +104,15 @@ function selectFilteredPlugins(
100
104
  result = result.filter((p) => p.isInstalled);
101
105
  }
102
106
 
103
- // Sort
107
+ // Sort. Essential plugins (oh-my-claudecode, codex) are pinned to the top of
108
+ // the grid — installed or not — so the recommendation lives inside the store
109
+ // list itself (rather than a separate banner). Within each group the chosen
110
+ // sort field still applies.
104
111
  const direction = sort.order === 'asc' ? 1 : -1;
112
+ const recommendRank = (p: EnrichedPlugin): number => (isEssentialPlugin(p) ? 0 : 1);
105
113
  result = [...result].sort((a, b) => {
114
+ const rankDelta = recommendRank(a) - recommendRank(b);
115
+ if (rankDelta !== 0) return rankDelta;
106
116
  switch (sort.field) {
107
117
  case 'popularity':
108
118
  return (a.installCount - b.installCount) * direction;
@@ -134,12 +144,7 @@ export const PluginsPanel = ({
134
144
  cliStatus: cliStatusOverride,
135
145
  cliStatusLoading,
136
146
  }: PluginsPanelProps): React.JSX.Element => {
137
- const {
138
- catalog,
139
- loading,
140
- error,
141
- cliStatus: storedCliStatus,
142
- } = useStore(
147
+ const { catalog, loading, error, cliStatus: storedCliStatus } = useStore(
143
148
  useShallow((s) => ({
144
149
  catalog: s.pluginCatalog,
145
150
  loading: s.pluginCatalogLoading,
@@ -73,6 +73,7 @@ import {
73
73
  MoreHorizontal,
74
74
  Shield,
75
75
  Users,
76
+ Wrench,
76
77
  } from 'lucide-react';
77
78
  import { useShallow } from 'zustand/react/shallow';
78
79
 
@@ -106,6 +107,7 @@ import {
106
107
  } from './sidebar/teamSidebarUiState';
107
108
  import { CollapsibleTeamSection } from './CollapsibleTeamSection';
108
109
  import { ProcessesSection } from './ProcessesSection';
110
+ import { ToolsSection } from './tools/ToolsSection';
109
111
  import { getLaunchJoinMilestonesFromMembers, getLaunchJoinState } from './provisioningSteps';
110
112
  import { TeamProvisioningBanner } from './TeamProvisioningBanner';
111
113
  import {
@@ -2279,6 +2281,19 @@ export const TeamDetailView = ({
2279
2281
  />
2280
2282
  </CollapsibleTeamSection>
2281
2283
 
2284
+ <CollapsibleTeamSection
2285
+ sectionId="tools"
2286
+ title="工具"
2287
+ icon={<Wrench size={14} />}
2288
+ defaultOpen={false}
2289
+ >
2290
+ <ToolsSection
2291
+ teamName={teamName}
2292
+ projectPath={data.config.projectPath ?? null}
2293
+ harnessType={data.harness}
2294
+ />
2295
+ </CollapsibleTeamSection>
2296
+
2282
2297
  <CollapsibleTeamSection
2283
2298
  sectionId="kanban"
2284
2299
  title="外部派单"
@@ -0,0 +1,47 @@
1
+ /**
2
+ * AddMcpInline — opens CustomMcpServerDialog for adding a custom MCP server.
3
+ * Refreshes parent list when dialog closes (install may have occurred).
4
+ */
5
+
6
+ import { useState } from 'react';
7
+
8
+ import { Button } from '@renderer/components/ui/button';
9
+ import { CustomMcpServerDialog } from '@renderer/components/extensions/mcp/CustomMcpServerDialog';
10
+
11
+ interface AddMcpInlineProps {
12
+ projectPath: string | null;
13
+ harnessType: string;
14
+ onAdded: () => void;
15
+ onCancel: () => void;
16
+ }
17
+
18
+ export const AddMcpInline = ({
19
+ onAdded,
20
+ onCancel,
21
+ }: AddMcpInlineProps): React.JSX.Element => {
22
+ const [dialogOpen, setDialogOpen] = useState(true);
23
+
24
+ const handleClose = () => {
25
+ setDialogOpen(false);
26
+ // Always refresh when dialog closes — install may have happened
27
+ onAdded();
28
+ };
29
+
30
+ return (
31
+ <>
32
+ <div className="flex items-center gap-2">
33
+ <Button variant="outline" size="sm" className="h-7 gap-1 text-xs" onClick={() => setDialogOpen(true)}>
34
+ 添加自定义 MCP
35
+ </Button>
36
+ <Button variant="ghost" size="sm" onClick={onCancel} className="h-7 text-xs">
37
+ 取消
38
+ </Button>
39
+ </div>
40
+
41
+ <CustomMcpServerDialog
42
+ open={dialogOpen}
43
+ onClose={handleClose}
44
+ />
45
+ </>
46
+ );
47
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AddSkillInline — inline compact form for adding a skill.
3
+ * Opens the existing SkillEditorDialog in create mode.
4
+ */
5
+
6
+ import { useState } from 'react';
7
+
8
+ import { Button } from '@renderer/components/ui/button';
9
+ import { SkillEditorDialog } from '@renderer/components/extensions/skills/SkillEditorDialog';
10
+
11
+ interface AddSkillInlineProps {
12
+ projectPath: string | null;
13
+ projectLabel?: string | null;
14
+ onAdded: () => void;
15
+ onCancel: () => void;
16
+ }
17
+
18
+ export const AddSkillInline = ({
19
+ projectPath,
20
+ projectLabel,
21
+ onAdded,
22
+ onCancel,
23
+ }: AddSkillInlineProps): React.JSX.Element => {
24
+ const [editorOpen, setEditorOpen] = useState(true);
25
+
26
+ const handleClose = () => {
27
+ setEditorOpen(false);
28
+ onCancel();
29
+ };
30
+
31
+ const handleSaved = () => {
32
+ setEditorOpen(false);
33
+ onAdded();
34
+ };
35
+
36
+ return (
37
+ <div className="flex items-center gap-2">
38
+ <Button
39
+ variant="outline"
40
+ size="sm"
41
+ className="h-7 gap-1 text-xs"
42
+ onClick={() => setEditorOpen(true)}
43
+ >
44
+ 创建新 Skill
45
+ </Button>
46
+ <Button variant="ghost" size="sm" onClick={onCancel} className="h-7 text-xs">
47
+ 取消
48
+ </Button>
49
+
50
+ <SkillEditorDialog
51
+ open={editorOpen}
52
+ onClose={handleClose}
53
+ onSaved={handleSaved}
54
+ mode="create"
55
+ projectPath={projectPath ?? null}
56
+ projectLabel={projectLabel ?? null}
57
+ detail={null}
58
+ />
59
+ </div>
60
+ );
61
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * McpChip — compact chip for an installed MCP server.
3
+ * Shows server name, status dot, and remove button on hover.
4
+ */
5
+
6
+ import { X } from 'lucide-react';
7
+
8
+ import type { InstalledMcpEntry, McpServerDiagnostic } from '@shared/types/extensions';
9
+
10
+ interface McpChipProps {
11
+ entry: InstalledMcpEntry;
12
+ diagnostic?: McpServerDiagnostic;
13
+ onRemove: (entry: InstalledMcpEntry) => void;
14
+ }
15
+
16
+ export const McpChip = ({ entry, diagnostic, onRemove }: McpChipProps): React.JSX.Element => {
17
+ // Default to green ("connected"). Only show a problem color when diagnostics
18
+ // explicitly report a failure or an auth requirement; absent/unknown status
19
+ // (e.g. diagnostics endpoint unavailable) is treated as healthy.
20
+ const statusColor =
21
+ diagnostic?.status === 'failed'
22
+ ? 'bg-red-500'
23
+ : diagnostic?.status === 'needs-authentication'
24
+ ? 'bg-amber-500'
25
+ : 'bg-emerald-500';
26
+
27
+ return (
28
+ <div className="group inline-flex items-center gap-1.5 rounded-full bg-[var(--color-bg-secondary)] px-2.5 py-1 text-xs transition-colors hover:bg-[var(--color-bg-secondary-hover)]">
29
+ <span className={`size-2 shrink-0 rounded-full ${statusColor}`} title={diagnostic?.status ?? 'unknown'} />
30
+ <span className="max-w-[120px] truncate text-[var(--color-text)]">{entry.name}</span>
31
+ <button
32
+ type="button"
33
+ className="shrink-0 rounded-full p-0.5 opacity-0 transition-opacity hover:bg-red-500/20 group-hover:opacity-100"
34
+ onClick={() => onRemove(entry)}
35
+ aria-label={`删除 ${entry.name}`}
36
+ title="点击删除"
37
+ >
38
+ <X size={10} className="text-[var(--color-text-muted)] hover:text-red-400" />
39
+ </button>
40
+ </div>
41
+ );
42
+ };