@yancyyu/openhermit 1.6.29 → 1.6.30
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/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DsQt4FHy.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-CrWocIjq.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-B6d8ysWi.js} +1 -1
- package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-DAIYCFP8.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
- package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DZou1667.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
- package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-DY3qbzqj.js} +1 -1
- package/dist-renderer/assets/{index-BowUl0Jb.js → index-BlOrAXp3.js} +542 -532
- package/dist-renderer/assets/{index-6m1ZAymG.js → index-Bs27J5gB.js} +1 -1
- package/dist-renderer/assets/{index-Dp3kJTEe.js → index-C8B_nKOF.js} +1 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
- package/dist-renderer/assets/{index-TOpt_T7A.js → index-DLKyDr4T.js} +1 -1
- package/dist-renderer/assets/{index-qNBNjW4K.js → index-Dhsk3_DD.js} +1 -1
- package/dist-renderer/assets/{index-vAykq1H1.js → index-GpUvV2xs.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
- package/dist-renderer/assets/{layout-DNANbrI4.js → layout-BZLlNmbr.js} +1 -1
- package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-qz6v45xy.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-CGKpOUF2.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +209 -6
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/api/httpClient.ts +61 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +35 -0
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
- package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/teamSlice.ts +8 -2
- package/src/shared/types/api.ts +29 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
- package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
- package/dist-renderer/assets/index-BhellmRb.css +0 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive shell environment resolver.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the user's interactive shell environment (PATH, etc.) by spawning
|
|
5
|
+
* a login/interactive shell and reading its exported variables. The result is
|
|
6
|
+
* cached for the lifetime of the process.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from TeamProvisioningService for reuse by ScheduledTaskExecutor
|
|
9
|
+
* and any other service that needs the user's shell environment.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getHomeDir } from '@main/utils/pathDecoder';
|
|
13
|
+
import { createLogger } from '@shared/utils/logger';
|
|
14
|
+
import { spawn } from 'child_process';
|
|
15
|
+
|
|
16
|
+
const logger = createLogger('Utils:shellEnv');
|
|
17
|
+
|
|
18
|
+
const SHELL_ENV_TIMEOUT_MS = 12_000;
|
|
19
|
+
const SHELL_ENV_BEST_EFFORT_TIMEOUT_MS = 5_000;
|
|
20
|
+
const SHELL_ENV_FAILURE_COOLDOWN_MS = 60_000;
|
|
21
|
+
|
|
22
|
+
let cachedInteractiveShellEnv: NodeJS.ProcessEnv | null = null;
|
|
23
|
+
let shellEnvResolvePromise: Promise<NodeJS.ProcessEnv> | null = null;
|
|
24
|
+
let shellEnvFailureCooldownUntil = 0;
|
|
25
|
+
let lastShellEnvFailureMessage: string | null = null;
|
|
26
|
+
|
|
27
|
+
export interface ShellEnvResolveProgress {
|
|
28
|
+
phase: string;
|
|
29
|
+
message: string;
|
|
30
|
+
source?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ShellEnvResolveOptions {
|
|
34
|
+
onProgress?: (progress: ShellEnvResolveProgress) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Stable diagnostic label for the caller that initiated the shell probe.
|
|
37
|
+
* Keep this to a short feature/service id, not a filesystem path.
|
|
38
|
+
*/
|
|
39
|
+
source?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ShellEnvBestEffortResolveOptions extends ShellEnvResolveOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Max time to wait on the critical path before returning fallbackEnv.
|
|
45
|
+
* By default, the full shell resolve continues in the background and caches
|
|
46
|
+
* on success. Set background=false for hot paths that only want cached env
|
|
47
|
+
* or an immediate fallback.
|
|
48
|
+
*/
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Whether a slow shell probe should continue in the background after the
|
|
52
|
+
* caller falls back. Disable this for startup/status hot paths where a
|
|
53
|
+
* delayed hard timeout would only create log noise and process pressure.
|
|
54
|
+
*/
|
|
55
|
+
background?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Returned when shell env is not ready quickly enough. This is intentionally
|
|
58
|
+
* not cached as a real shell env.
|
|
59
|
+
*/
|
|
60
|
+
fallbackEnv?: NodeJS.ProcessEnv;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function emitProgress(
|
|
64
|
+
options: ShellEnvResolveOptions | undefined,
|
|
65
|
+
phase: string,
|
|
66
|
+
message: string
|
|
67
|
+
): void {
|
|
68
|
+
const source = normalizeShellEnvSource(options?.source);
|
|
69
|
+
options?.onProgress?.(source ? { phase, message, source } : { phase, message });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeShellEnvSource(source: string | undefined): string | null {
|
|
73
|
+
const trimmed = source?.trim();
|
|
74
|
+
if (!trimmed) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return trimmed.replace(/[^A-Za-z0-9_.:-]/g, '_').slice(0, 80);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatShellEnvSource(options: ShellEnvResolveOptions | undefined): string {
|
|
81
|
+
const source = normalizeShellEnvSource(options?.source);
|
|
82
|
+
return source ? ` source=${source}` : '';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function rememberShellEnvFailure(message: string): void {
|
|
86
|
+
lastShellEnvFailureMessage = message;
|
|
87
|
+
shellEnvFailureCooldownUntil = Date.now() + SHELL_ENV_FAILURE_COOLDOWN_MS;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function clearShellEnvFailure(): void {
|
|
91
|
+
lastShellEnvFailureMessage = null;
|
|
92
|
+
shellEnvFailureCooldownUntil = 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseNullSeparatedEnv(content: string): NodeJS.ProcessEnv {
|
|
96
|
+
const parsed: NodeJS.ProcessEnv = {};
|
|
97
|
+
const lines = content.split('\0');
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
if (!line) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const separatorIndex = line.indexOf('=');
|
|
103
|
+
if (separatorIndex <= 0) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const key = line.slice(0, separatorIndex);
|
|
107
|
+
const value = line.slice(separatorIndex + 1);
|
|
108
|
+
parsed[key] = value;
|
|
109
|
+
}
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function readShellEnv(shellPath: string, args: string[]): Promise<NodeJS.ProcessEnv> {
|
|
114
|
+
const envDump = await new Promise<string>((resolve, reject) => {
|
|
115
|
+
const child = spawn(shellPath, args, {
|
|
116
|
+
env: process.env,
|
|
117
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
118
|
+
windowsHide: true,
|
|
119
|
+
});
|
|
120
|
+
const chunks: Buffer[] = [];
|
|
121
|
+
let settled = false;
|
|
122
|
+
let timeoutHandle: NodeJS.Timeout | null = setTimeout(() => {
|
|
123
|
+
timeoutHandle = null;
|
|
124
|
+
child.kill();
|
|
125
|
+
// SIGKILL fallback if SIGTERM is ignored (e.g., shell stuck on .zshrc)
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
try {
|
|
128
|
+
child.kill('SIGKILL');
|
|
129
|
+
} catch {
|
|
130
|
+
/* already dead */
|
|
131
|
+
}
|
|
132
|
+
}, 3000);
|
|
133
|
+
if (!settled) {
|
|
134
|
+
settled = true;
|
|
135
|
+
reject(new Error('shell env resolve timeout'));
|
|
136
|
+
}
|
|
137
|
+
}, SHELL_ENV_TIMEOUT_MS);
|
|
138
|
+
|
|
139
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
140
|
+
chunks.push(chunk);
|
|
141
|
+
});
|
|
142
|
+
child.once('error', (error) => {
|
|
143
|
+
if (timeoutHandle) {
|
|
144
|
+
clearTimeout(timeoutHandle);
|
|
145
|
+
timeoutHandle = null;
|
|
146
|
+
}
|
|
147
|
+
if (!settled) {
|
|
148
|
+
settled = true;
|
|
149
|
+
reject(error);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
child.once('close', (code: number | null, signal: NodeJS.Signals | null) => {
|
|
153
|
+
if (timeoutHandle) {
|
|
154
|
+
clearTimeout(timeoutHandle);
|
|
155
|
+
}
|
|
156
|
+
if (!settled) {
|
|
157
|
+
settled = true;
|
|
158
|
+
if (chunks.length === 0 && (code !== 0 || signal)) {
|
|
159
|
+
reject(
|
|
160
|
+
new Error(
|
|
161
|
+
signal
|
|
162
|
+
? `shell env command exited with signal ${signal}`
|
|
163
|
+
: `shell env command exited with code ${code}`
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
return parseNullSeparatedEnv(envDump);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the user's interactive shell environment.
|
|
177
|
+
*
|
|
178
|
+
* Tries login shell first (`-lic`), falls back to interactive (`-ic`).
|
|
179
|
+
* On Windows returns empty object. Result is cached after first success.
|
|
180
|
+
*/
|
|
181
|
+
export async function resolveInteractiveShellEnv(
|
|
182
|
+
options: ShellEnvResolveOptions = {}
|
|
183
|
+
): Promise<NodeJS.ProcessEnv> {
|
|
184
|
+
if (cachedInteractiveShellEnv) {
|
|
185
|
+
emitProgress(options, 'shell-env-cached', 'Using cached shell environment...');
|
|
186
|
+
return cachedInteractiveShellEnv;
|
|
187
|
+
}
|
|
188
|
+
if (shellEnvResolvePromise) {
|
|
189
|
+
emitProgress(options, 'shell-env-waiting', 'Waiting for shell environment...');
|
|
190
|
+
return shellEnvResolvePromise;
|
|
191
|
+
}
|
|
192
|
+
if (process.platform === 'win32') {
|
|
193
|
+
emitProgress(options, 'shell-env-skipped', 'Skipping shell environment on Windows...');
|
|
194
|
+
cachedInteractiveShellEnv = {};
|
|
195
|
+
return cachedInteractiveShellEnv;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
shellEnvResolvePromise = (async () => {
|
|
199
|
+
const shellPath = process.env.SHELL || '/bin/zsh';
|
|
200
|
+
try {
|
|
201
|
+
emitProgress(options, 'shell-env-login', 'Reading login shell environment...');
|
|
202
|
+
const loginEnv = await readShellEnv(shellPath, ['-lic', 'env -0']);
|
|
203
|
+
cachedInteractiveShellEnv = loginEnv;
|
|
204
|
+
clearShellEnvFailure();
|
|
205
|
+
return loginEnv;
|
|
206
|
+
} catch (loginError) {
|
|
207
|
+
const loginMessage = loginError instanceof Error ? loginError.message : String(loginError);
|
|
208
|
+
try {
|
|
209
|
+
emitProgress(options, 'shell-env-interactive', 'Trying interactive shell environment...');
|
|
210
|
+
const interactiveEnv = await readShellEnv(shellPath, ['-ic', 'env -0']);
|
|
211
|
+
cachedInteractiveShellEnv = interactiveEnv;
|
|
212
|
+
clearShellEnvFailure();
|
|
213
|
+
return interactiveEnv;
|
|
214
|
+
} catch (interactiveError) {
|
|
215
|
+
const interactiveMessage =
|
|
216
|
+
interactiveError instanceof Error ? interactiveError.message : String(interactiveError);
|
|
217
|
+
logger.warn(
|
|
218
|
+
`Failed to resolve shell env after login and interactive probes${formatShellEnvSource(
|
|
219
|
+
options
|
|
220
|
+
)}: login=${loginMessage}; interactive=${interactiveMessage}`
|
|
221
|
+
);
|
|
222
|
+
rememberShellEnvFailure(interactiveMessage);
|
|
223
|
+
emitProgress(options, 'shell-env-fallback', 'Using current process environment...');
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
shellEnvResolvePromise = null;
|
|
228
|
+
}
|
|
229
|
+
})();
|
|
230
|
+
|
|
231
|
+
return shellEnvResolvePromise;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve shell env without making the caller wait for slow prompt/plugin init.
|
|
236
|
+
*
|
|
237
|
+
* This is deliberately additive: fallbackEnv is returned only to the current
|
|
238
|
+
* caller, never cached. A successful background resolve still populates the
|
|
239
|
+
* normal interactive-shell cache used by buildMergedCliPath/buildEnrichedEnv.
|
|
240
|
+
*/
|
|
241
|
+
export async function resolveInteractiveShellEnvBestEffort(
|
|
242
|
+
options: ShellEnvBestEffortResolveOptions = {}
|
|
243
|
+
): Promise<NodeJS.ProcessEnv> {
|
|
244
|
+
if (cachedInteractiveShellEnv) {
|
|
245
|
+
emitProgress(options, 'shell-env-cached', 'Using cached shell environment...');
|
|
246
|
+
return cachedInteractiveShellEnv;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (process.platform === 'win32') {
|
|
250
|
+
return resolveInteractiveShellEnv(options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const fallbackEnv = options.fallbackEnv ?? {};
|
|
254
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? SHELL_ENV_BEST_EFFORT_TIMEOUT_MS);
|
|
255
|
+
const startedAt = Date.now();
|
|
256
|
+
if (options.background === false) {
|
|
257
|
+
emitProgress(options, 'shell-env-best-effort-fallback', 'Using fallback shell environment...');
|
|
258
|
+
return fallbackEnv;
|
|
259
|
+
}
|
|
260
|
+
if (!shellEnvResolvePromise && startedAt < shellEnvFailureCooldownUntil) {
|
|
261
|
+
const retryInMs = Math.max(0, shellEnvFailureCooldownUntil - startedAt);
|
|
262
|
+
emitProgress(
|
|
263
|
+
options,
|
|
264
|
+
'shell-env-failure-cooldown',
|
|
265
|
+
lastShellEnvFailureMessage
|
|
266
|
+
? `Using fallback shell environment after recent failure: ${lastShellEnvFailureMessage}`
|
|
267
|
+
: `Using fallback shell environment for ${retryInMs}ms after recent failure...`
|
|
268
|
+
);
|
|
269
|
+
return fallbackEnv;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const resolvePromise = resolveInteractiveShellEnv(options);
|
|
273
|
+
if (timeoutMs === 0) {
|
|
274
|
+
emitProgress(options, 'shell-env-best-effort-fallback', 'Using fallback shell environment...');
|
|
275
|
+
return fallbackEnv;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let timeoutHandle: NodeJS.Timeout | null = null;
|
|
279
|
+
const fallbackPromise = new Promise<NodeJS.ProcessEnv>((resolve) => {
|
|
280
|
+
timeoutHandle = setTimeout(() => {
|
|
281
|
+
timeoutHandle = null;
|
|
282
|
+
emitProgress(
|
|
283
|
+
options,
|
|
284
|
+
'shell-env-best-effort-timeout',
|
|
285
|
+
'Shell environment is still resolving; using fallback for now...'
|
|
286
|
+
);
|
|
287
|
+
resolve(fallbackEnv);
|
|
288
|
+
}, timeoutMs);
|
|
289
|
+
timeoutHandle.unref?.();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const resolvedEnv = await Promise.race([resolvePromise, fallbackPromise]);
|
|
294
|
+
if (!cachedInteractiveShellEnv && shellEnvFailureCooldownUntil > startedAt) {
|
|
295
|
+
return fallbackEnv;
|
|
296
|
+
}
|
|
297
|
+
return resolvedEnv;
|
|
298
|
+
} finally {
|
|
299
|
+
if (timeoutHandle) {
|
|
300
|
+
clearTimeout(timeoutHandle);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clear the cached shell environment. Useful for testing.
|
|
307
|
+
*/
|
|
308
|
+
export function clearShellEnvCache(): void {
|
|
309
|
+
cachedInteractiveShellEnv = null;
|
|
310
|
+
shellEnvResolvePromise = null;
|
|
311
|
+
clearShellEnvFailure();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Return the cached shell environment synchronously, or null if not yet resolved.
|
|
316
|
+
*
|
|
317
|
+
* Use this when you need the shell env but cannot afford to wait for resolution
|
|
318
|
+
* (e.g. synchronous PATH enrichment with async pre-warming at startup).
|
|
319
|
+
*/
|
|
320
|
+
export function getCachedShellEnv(): NodeJS.ProcessEnv | null {
|
|
321
|
+
return cachedInteractiveShellEnv;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* HOME from login/interactive shell when resolved, else Electron/Node home.
|
|
326
|
+
* Matches TeamProvisioningService so CLI reads the same ~/.claude as the terminal.
|
|
327
|
+
*/
|
|
328
|
+
export function getShellPreferredHome(): string {
|
|
329
|
+
const fromShell = getCachedShellEnv()?.HOME?.trim();
|
|
330
|
+
return fromShell || getHomeDir();
|
|
331
|
+
}
|
|
@@ -2220,6 +2220,67 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
2220
2220
|
},
|
|
2221
2221
|
};
|
|
2222
2222
|
|
|
2223
|
+
// ---------------------------------------------------------------------------
|
|
2224
|
+
// Credentials (project env, MCP credentials)
|
|
2225
|
+
// ---------------------------------------------------------------------------
|
|
2226
|
+
|
|
2227
|
+
credentials = {
|
|
2228
|
+
getStatus: async () =>
|
|
2229
|
+
this.get<{ encryption: string; storagePath: string } | null>(
|
|
2230
|
+
'/api/extensions/credentials/status'
|
|
2231
|
+
),
|
|
2232
|
+
|
|
2233
|
+
getProjectEnv: async (projectPath: string) =>
|
|
2234
|
+
this.get<Record<string, string>>(
|
|
2235
|
+
`/api/extensions/credentials/project-env?projectPath=${encodeURIComponent(projectPath)}`
|
|
2236
|
+
),
|
|
2237
|
+
|
|
2238
|
+
saveProjectEnv: async (projectPath: string, vars: Record<string, string>): Promise<void> => {
|
|
2239
|
+
await this.post('/api/extensions/credentials/project-env', { projectPath, vars });
|
|
2240
|
+
},
|
|
2241
|
+
|
|
2242
|
+
scanRequired: async (
|
|
2243
|
+
projectPath: string,
|
|
2244
|
+
mcpServers: {
|
|
2245
|
+
name: string;
|
|
2246
|
+
envVars?: { name: string; isRequired: boolean; description?: string };
|
|
2247
|
+
}[],
|
|
2248
|
+
skillReqs: {
|
|
2249
|
+
name: string;
|
|
2250
|
+
envVars: { name: string; isRequired?: boolean; description?: string }[];
|
|
2251
|
+
}[]
|
|
2252
|
+
) =>
|
|
2253
|
+
this.post<{
|
|
2254
|
+
required: {
|
|
2255
|
+
name: string;
|
|
2256
|
+
isRequired: boolean;
|
|
2257
|
+
description?: string;
|
|
2258
|
+
source: string;
|
|
2259
|
+
value?: string;
|
|
2260
|
+
}[];
|
|
2261
|
+
}>('/api/extensions/credentials/scan-required', { projectPath, mcpServers, skillReqs }),
|
|
2262
|
+
|
|
2263
|
+
resolveAgentEnv: async (projectPath: string) =>
|
|
2264
|
+
this.get<Record<string, string>>(
|
|
2265
|
+
`/api/extensions/credentials/resolve-agent-env?projectPath=${encodeURIComponent(projectPath)}`
|
|
2266
|
+
),
|
|
2267
|
+
|
|
2268
|
+
getSkillGlobalEnv: async (skillFolderName: string) =>
|
|
2269
|
+
this.get<Record<string, string>>(
|
|
2270
|
+
`/api/extensions/credentials/skill-env?folderName=${encodeURIComponent(skillFolderName)}`
|
|
2271
|
+
),
|
|
2272
|
+
|
|
2273
|
+
saveSkillGlobalEnv: async (
|
|
2274
|
+
skillFolderName: string,
|
|
2275
|
+
vars: Record<string, string>
|
|
2276
|
+
): Promise<void> => {
|
|
2277
|
+
await this.post('/api/extensions/credentials/skill-env', {
|
|
2278
|
+
folderName: skillFolderName,
|
|
2279
|
+
vars,
|
|
2280
|
+
});
|
|
2281
|
+
},
|
|
2282
|
+
};
|
|
2283
|
+
|
|
2223
2284
|
// ---------------------------------------------------------------------------
|
|
2224
2285
|
// Workspace (file system browsing)
|
|
2225
2286
|
// ---------------------------------------------------------------------------
|
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
import { resolveProjectPathById } from '@renderer/utils/projectLookup';
|
|
38
38
|
import { refreshCliStatusForCurrentMode } from '@renderer/utils/refreshCliStatus';
|
|
39
39
|
import { getRuntimeDisplayName } from '@renderer/utils/runtimeDisplayName';
|
|
40
|
-
import { getExtensionActionDisableReason } from '@shared/utils/extensionNormalizers';
|
|
41
40
|
import { getCliProviderExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
|
|
42
41
|
import {
|
|
43
42
|
AlertTriangle,
|
|
@@ -48,15 +47,20 @@ import {
|
|
|
48
47
|
Puzzle,
|
|
49
48
|
RefreshCw,
|
|
50
49
|
Server,
|
|
50
|
+
Sliders,
|
|
51
51
|
} from 'lucide-react';
|
|
52
52
|
import { useShallow } from 'zustand/react/shallow';
|
|
53
53
|
|
|
54
54
|
import { CustomMcpServerDialog } from './mcp/CustomMcpServerDialog';
|
|
55
|
+
import { EnvVarPanel } from './env/EnvVarPanel';
|
|
55
56
|
import { McpServersPanel } from './mcp/McpServersPanel';
|
|
57
|
+
import { PluginsPanel } from './plugins/PluginsPanel';
|
|
56
58
|
import { SkillsPanel } from './skills/SkillsPanel';
|
|
59
|
+
import { StoreExtensionToast } from './common/ExtensionToast';
|
|
57
60
|
import { ExtensionsSubTabTrigger } from './ExtensionsSubTabTrigger';
|
|
58
61
|
|
|
59
62
|
import type { CliProviderId, CliProviderStatus } from '@shared/types';
|
|
63
|
+
import type { ExtensionsSubTab } from '@renderer/hooks/useExtensionsTabState';
|
|
60
64
|
|
|
61
65
|
const ProviderCapabilityCardSkeleton = ({
|
|
62
66
|
providerId,
|
|
@@ -113,6 +117,12 @@ function isCodexSnapshotPending(
|
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
const EXTENSION_SUB_TABS = [
|
|
120
|
+
{
|
|
121
|
+
value: 'plugins' as const,
|
|
122
|
+
label: '插件',
|
|
123
|
+
icon: Puzzle,
|
|
124
|
+
description: 'Claude Code 私有扩展,增强运行时的能力与集成。',
|
|
125
|
+
},
|
|
116
126
|
{
|
|
117
127
|
value: 'mcp-servers' as const,
|
|
118
128
|
label: 'MCP 服务器',
|
|
@@ -125,6 +135,12 @@ const EXTENSION_SUB_TABS = [
|
|
|
125
135
|
icon: BookOpen,
|
|
126
136
|
description: '面向常见任务的可复用指令,帮助运行时更稳定地处理重复工作。',
|
|
127
137
|
},
|
|
138
|
+
{
|
|
139
|
+
value: 'env-vars' as const,
|
|
140
|
+
label: '环境变量',
|
|
141
|
+
icon: Sliders,
|
|
142
|
+
description: '管理运行时环境变量,启动 agent 时自动注入。',
|
|
143
|
+
},
|
|
128
144
|
] as const;
|
|
129
145
|
|
|
130
146
|
export const ExtensionStoreView = (): React.JSX.Element => {
|
|
@@ -132,6 +148,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
132
148
|
const {
|
|
133
149
|
bootstrapCliStatus,
|
|
134
150
|
fetchCliStatus,
|
|
151
|
+
fetchPluginCatalog,
|
|
135
152
|
fetchSkillsCatalog,
|
|
136
153
|
mcpBrowse,
|
|
137
154
|
mcpFetchInstalled,
|
|
@@ -149,7 +166,9 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
149
166
|
useShallow((s) => ({
|
|
150
167
|
bootstrapCliStatus: s.bootstrapCliStatus,
|
|
151
168
|
fetchCliStatus: s.fetchCliStatus,
|
|
169
|
+
fetchPluginCatalog: s.fetchPluginCatalog,
|
|
152
170
|
fetchSkillsCatalog: s.fetchSkillsCatalog,
|
|
171
|
+
pluginCatalog: s.pluginCatalog,
|
|
153
172
|
mcpBrowse: s.mcpBrowse,
|
|
154
173
|
mcpFetchInstalled: s.mcpFetchInstalled,
|
|
155
174
|
mcpBrowseLoading: s.mcpBrowseLoading,
|
|
@@ -239,6 +258,11 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
239
258
|
void mcpFetchInstalled(projectPath ?? undefined);
|
|
240
259
|
}, [mcpFetchInstalled, projectPath]);
|
|
241
260
|
|
|
261
|
+
// Fetch Plugin catalog on mount / project change
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
void fetchPluginCatalog(projectPath ?? undefined);
|
|
264
|
+
}, [fetchPluginCatalog, projectPath]);
|
|
265
|
+
|
|
242
266
|
// Fetch Skills catalog on mount / project change
|
|
243
267
|
useEffect(() => {
|
|
244
268
|
void fetchSkillsCatalog(projectPath ?? undefined);
|
|
@@ -265,16 +289,6 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
265
289
|
]);
|
|
266
290
|
|
|
267
291
|
const isRefreshing = effectiveCliStatusLoading || mcpBrowseLoading || skillsLoading;
|
|
268
|
-
const mcpMutationDisableReason = useMemo(
|
|
269
|
-
() =>
|
|
270
|
-
getExtensionActionDisableReason({
|
|
271
|
-
isInstalled: false,
|
|
272
|
-
cliStatus: effectiveCliStatus,
|
|
273
|
-
cliStatusLoading: effectiveCliStatusLoading,
|
|
274
|
-
section: 'mcp',
|
|
275
|
-
}),
|
|
276
|
-
[effectiveCliStatus, effectiveCliStatusLoading]
|
|
277
|
-
);
|
|
278
292
|
const cliStatusBanner = useMemo(() => {
|
|
279
293
|
const providers = effectiveCliStatus?.providers ?? [];
|
|
280
294
|
const visibleProviders = getVisibleMultimodelProviders(providers);
|
|
@@ -504,7 +518,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
504
518
|
)}
|
|
505
519
|
<Tabs
|
|
506
520
|
value={tabState.activeSubTab}
|
|
507
|
-
onValueChange={(v) => tabState.setActiveSubTab(v as
|
|
521
|
+
onValueChange={(v) => tabState.setActiveSubTab(v as ExtensionsSubTab)}
|
|
508
522
|
>
|
|
509
523
|
<div className="-mx-6 flex items-end justify-between border-b border-border px-6">
|
|
510
524
|
<TabsList className="gap-1 rounded-b-none">
|
|
@@ -519,28 +533,37 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
519
533
|
))}
|
|
520
534
|
</TabsList>
|
|
521
535
|
{tabState.activeSubTab === 'mcp-servers' && (
|
|
522
|
-
<
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
>
|
|
532
|
-
<Plus className="mr-1 size-3.5" />
|
|
533
|
-
添加自定义
|
|
534
|
-
</Button>
|
|
535
|
-
</span>
|
|
536
|
-
</TooltipTrigger>
|
|
537
|
-
{mcpMutationDisableReason && (
|
|
538
|
-
<TooltipContent>{mcpMutationDisableReason}</TooltipContent>
|
|
539
|
-
)}
|
|
540
|
-
</Tooltip>
|
|
536
|
+
<Button
|
|
537
|
+
variant="outline"
|
|
538
|
+
size="sm"
|
|
539
|
+
onClick={() => setCustomMcpDialogOpen(true)}
|
|
540
|
+
className="mb-1 whitespace-nowrap"
|
|
541
|
+
>
|
|
542
|
+
<Plus className="mr-1 size-3.5" />
|
|
543
|
+
添加自定义
|
|
544
|
+
</Button>
|
|
541
545
|
)}
|
|
542
546
|
</div>
|
|
543
547
|
|
|
548
|
+
<TabsContent value="plugins" className="mt-0 pt-4">
|
|
549
|
+
<PluginsPanel
|
|
550
|
+
projectPath={projectPath}
|
|
551
|
+
pluginFilters={tabState.pluginFilters}
|
|
552
|
+
pluginSort={tabState.pluginSort}
|
|
553
|
+
setPluginSort={tabState.setPluginSort}
|
|
554
|
+
selectedPluginId={tabState.selectedPluginId}
|
|
555
|
+
setSelectedPluginId={tabState.setSelectedPluginId}
|
|
556
|
+
updatePluginSearch={tabState.updatePluginSearch}
|
|
557
|
+
toggleCategory={tabState.toggleCategory}
|
|
558
|
+
toggleCapability={tabState.toggleCapability}
|
|
559
|
+
toggleInstalledOnly={tabState.toggleInstalledOnly}
|
|
560
|
+
clearFilters={tabState.clearFilters}
|
|
561
|
+
hasActiveFilters={tabState.hasActiveFilters}
|
|
562
|
+
cliStatus={effectiveCliStatus}
|
|
563
|
+
cliStatusLoading={effectiveCliStatusLoading}
|
|
564
|
+
/>
|
|
565
|
+
</TabsContent>
|
|
566
|
+
|
|
544
567
|
<TabsContent value="mcp-servers" className="mt-0 pt-4">
|
|
545
568
|
<McpServersPanel
|
|
546
569
|
projectPath={projectPath}
|
|
@@ -568,19 +591,21 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
568
591
|
setSelectedSkillId={tabState.setSelectedSkillId}
|
|
569
592
|
/>
|
|
570
593
|
</TabsContent>
|
|
594
|
+
|
|
595
|
+
<TabsContent value="env-vars" className="mt-0 pt-4">
|
|
596
|
+
<EnvVarPanel projectPath={projectPath} />
|
|
597
|
+
</TabsContent>
|
|
571
598
|
</Tabs>
|
|
572
599
|
|
|
573
600
|
{/* Custom MCP server dialog (lifted to store view level) */}
|
|
574
601
|
<CustomMcpServerDialog
|
|
575
602
|
open={customMcpDialogOpen}
|
|
576
603
|
onClose={() => setCustomMcpDialogOpen(false)}
|
|
577
|
-
projectPath={projectPath}
|
|
578
|
-
cliStatus={effectiveCliStatus}
|
|
579
|
-
cliStatusLoading={effectiveCliStatusLoading}
|
|
580
604
|
/>
|
|
581
605
|
</div>
|
|
582
606
|
</div>
|
|
583
607
|
</div>
|
|
608
|
+
<StoreExtensionToast />
|
|
584
609
|
</TooltipProvider>
|
|
585
610
|
);
|
|
586
611
|
};
|
|
@@ -5,7 +5,7 @@ import { Info } from 'lucide-react';
|
|
|
5
5
|
import type { LucideIcon } from 'lucide-react';
|
|
6
6
|
|
|
7
7
|
interface ExtensionsSubTabTriggerProps {
|
|
8
|
-
value: 'mcp-servers' | 'skills';
|
|
8
|
+
value: 'plugins' | 'mcp-servers' | 'skills' | 'env-vars';
|
|
9
9
|
label: string;
|
|
10
10
|
description: string;
|
|
11
11
|
icon: LucideIcon;
|