Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -1096,23 +1096,22 @@ sandbox:
domain: "localhost"

# Plugin configuration for installing plugins before workflow execution. Supports
# both array format (list of repos) and object format (repos + custom token).
# array format (list of repos/plugin configs) and object format (repos + custom
# token).
# (optional)
# This field supports multiple formats (oneOf):

# Option 1: List of plugin repository slugs to install. Each plugin is installed
# using the engine's plugin installation command with default token resolution.
# Option 1: List of plugins to install. Each item can be either a repository slug
# string (e.g., 'org/repo') or an object with url and optional MCP configuration.
plugins: []
# Array items: Plugin repository slug in the format 'org/repo' (e.g.,
# 'github/example-plugin')
# Array items: undefined
Comment on lines +1104 to +1107
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin section docs still refer to plugin objects having url, but the implementation/schema use id. Also the docs currently say "Array items: undefined", which makes the reference unclear. Please update this section to document the actual shape (string item or { id, mcp: { env } }).

See below for a potential fix:

# string (e.g., 'org/repo') or an object with id and optional MCP configuration.
plugins: []
  # Array items can be either:
  # - string: Repository slug (e.g., 'org/repo')
  # - object: Plugin configuration with the following shape:
  #     id: Repository slug (e.g., 'org/repo')
  #     mcp:
  #       env:
  #         # Environment variables for the MCP gateway
  #         # (optional)
  #         KEY: "VALUE"

# Option 2: Plugin configuration with custom GitHub token. Repos can be either
# strings or objects with MCP configuration.
plugins:
  # List of plugins to install. Each item can be either a repository slug string or
  # an object with id and optional MCP configuration. Objects use the shape:
  #   { id: string, mcp: { env: { [key: string]: string } } }

Copilot uses AI. Check for mistakes.

# Option 2: Plugin configuration with custom GitHub token. The custom token
# overrides the default token resolution chain.
# Option 2: Plugin configuration with custom GitHub token. Repos can be either
# strings or objects with MCP configuration.
plugins:
# List of plugin repository slugs to install
# List of plugins to install. Each item can be either a repository slug string or
# an object with url and optional MCP configuration.
repos: []
# Array of Plugin repository slug in the format 'org/repo' (e.g.,
# 'github/example-plugin')

# Custom GitHub token expression to use for plugin installation. Overrides the
# default cascading token resolution (GH_AW_PLUGINS_TOKEN -> GH_AW_GITHUB_TOKEN ->
Expand Down
36 changes: 25 additions & 11 deletions pkg/parser/import_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a
var toolsBuilder strings.Builder
var mcpServersBuilder strings.Builder
var markdownBuilder strings.Builder // Only used for imports WITH inputs (compile-time substitution)
var importPaths []string // NEW: Track import paths for runtime-import macro generation
var importPaths []string // NEW: Track import paths for runtime-import macro generation
var stepsBuilder strings.Builder
var copilotSetupStepsBuilder strings.Builder // Track copilot-setup-steps.yml separately
var runtimesBuilder strings.Builder
Expand Down Expand Up @@ -317,7 +317,7 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a
} else {
// Has inputs - must inline for compile-time substitution
log.Printf("Agent file has inputs - will be inlined instead of runtime-imported")

// For agent files, extract markdown content (only when inputs are present)
markdownContent, err := processIncludedFileWithVisited(item.fullPath, item.sectionName, false, visited)
if err != nil {
Expand Down Expand Up @@ -489,7 +489,7 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a
} else {
// Has inputs - must inline for compile-time substitution
log.Printf("Import %s has inputs - will be inlined for compile-time substitution", importRelPath)

// Extract markdown content from imported file (only for imports with inputs)
markdownContent, err := processIncludedFileWithVisited(item.fullPath, item.sectionName, false, visited)
if err != nil {
Expand Down Expand Up @@ -584,15 +584,29 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a
}

// Extract plugins from imported file (merge into set to avoid duplicates)
// This now handles both simple string format and object format with MCP configs
pluginsContent, err := extractPluginsFromContent(string(content))
if err == nil && pluginsContent != "" && pluginsContent != "[]" {
// Parse plugins JSON array
var importedPlugins []string
if jsonErr := json.Unmarshal([]byte(pluginsContent), &importedPlugins); jsonErr == nil {
for _, plugin := range importedPlugins {
if !pluginsSet[plugin] {
pluginsSet[plugin] = true
plugins = append(plugins, plugin)
// Parse plugins - can be array of strings or objects
var pluginsRaw []any
if jsonErr := json.Unmarshal([]byte(pluginsContent), &pluginsRaw); jsonErr == nil {
for _, item := range pluginsRaw {
// Handle string format: "org/repo"
if pluginStr, ok := item.(string); ok {
if !pluginsSet[pluginStr] {
pluginsSet[pluginStr] = true
plugins = append(plugins, pluginStr)
}
} else if pluginObj, ok := item.(map[string]any); ok {
// Handle object format: { "id": "org/repo", "mcp": {...} }
if idVal, hasID := pluginObj["id"]; hasID {
if pluginID, ok := idVal.(string); ok && !pluginsSet[pluginID] {
pluginsSet[pluginID] = true
plugins = append(plugins, pluginID)
// Note: MCP configs from imports are currently not merged
// They would need to be handled at a higher level in compiler_orchestrator_tools.go
Comment on lines +590 to +607
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractPluginsFromContent can return either a JSON array (plugins: [...]) or a JSON object (plugins: { repos: [...], github-token: ... }). This code always unmarshals into []any, so imported workflows that use the object form will silently contribute zero plugins to MergedPlugins. Consider unmarshaling into any first and handling both array and object shapes (at least extracting .repos).

Suggested change
// Parse plugins - can be array of strings or objects
var pluginsRaw []any
if jsonErr := json.Unmarshal([]byte(pluginsContent), &pluginsRaw); jsonErr == nil {
for _, item := range pluginsRaw {
// Handle string format: "org/repo"
if pluginStr, ok := item.(string); ok {
if !pluginsSet[pluginStr] {
pluginsSet[pluginStr] = true
plugins = append(plugins, pluginStr)
}
} else if pluginObj, ok := item.(map[string]any); ok {
// Handle object format: { "id": "org/repo", "mcp": {...} }
if idVal, hasID := pluginObj["id"]; hasID {
if pluginID, ok := idVal.(string); ok && !pluginsSet[pluginID] {
pluginsSet[pluginID] = true
plugins = append(plugins, pluginID)
// Note: MCP configs from imports are currently not merged
// They would need to be handled at a higher level in compiler_orchestrator_tools.go
// Parse plugins - can be array of strings/objects or an object with repos
var pluginsRaw any
if jsonErr := json.Unmarshal([]byte(pluginsContent), &pluginsRaw); jsonErr == nil {
switch v := pluginsRaw.(type) {
case []any:
for _, item := range v {
// Handle string format: "org/repo"
if pluginStr, ok := item.(string); ok {
if !pluginsSet[pluginStr] {
pluginsSet[pluginStr] = true
plugins = append(plugins, pluginStr)
}
} else if pluginObj, ok := item.(map[string]any); ok {
// Handle object format: { "id": "org/repo", "mcp": {...} }
if idVal, hasID := pluginObj["id"]; hasID {
if pluginID, ok := idVal.(string); ok && !pluginsSet[pluginID] {
pluginsSet[pluginID] = true
plugins = append(plugins, pluginID)
// Note: MCP configs from imports are currently not merged
// They would need to be handled at a higher level in compiler_orchestrator_tools.go
}
}
}
}
case map[string]any:
// Handle object format: { "repos": [...], "github-token": "..." }
if reposVal, hasRepos := v["repos"]; hasRepos {
if reposSlice, ok := reposVal.([]any); ok {
for _, repoItem := range reposSlice {
if pluginStr, ok := repoItem.(string); ok {
if !pluginsSet[pluginStr] {
pluginsSet[pluginStr] = true
plugins = append(plugins, pluginStr)
}
}

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imported plugin objects can include MCP env config, but import processing currently drops those configs (only the plugin ID is merged). This means MCP env vars for imported plugins won't be available when starting the gateway. Consider extending ImportsResult to carry merged plugin MCP configs and merging them into WorkflowData.PluginInfo.MCPConfigs, or emitting a validation warning/error when an imported plugin specifies mcp.env that can't be honored.

Suggested change
// They would need to be handled at a higher level in compiler_orchestrator_tools.go
// They would need to be handled at a higher level in compiler_orchestrator_tools.go
// Emit a warning if an imported plugin defines MCP env config that cannot be honored here.
if mcpCfg, hasMCP := pluginObj["mcp"]; hasMCP && mcpCfg != nil {
if mcpMap, ok := mcpCfg.(map[string]any); ok {
if _, hasEnv := mcpMap["env"]; hasEnv {
importLog.Warnf("imported plugin %s defines mcp.env, which is currently not merged into workflow MCP configs", pluginID)
}
}
}

Copilot uses AI. Check for mistakes.
}
}
}
}
}
Expand Down Expand Up @@ -639,7 +653,7 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a
MergedSafeOutputs: safeOutputs,
MergedSafeInputs: safeInputs,
MergedMarkdown: markdownBuilder.String(), // Only imports WITH inputs (for compile-time substitution)
ImportPaths: importPaths, // Import paths for runtime-import macro generation
ImportPaths: importPaths, // Import paths for runtime-import macro generation
MergedSteps: stepsBuilder.String(),
CopilotSetupSteps: copilotSetupStepsBuilder.String(),
MergedRuntimes: runtimesBuilder.String(),
Expand Down
101 changes: 90 additions & 11 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2483,9 +2483,20 @@
]
},
"plugins": {
"description": "Plugin configuration for installing plugins before workflow execution. Supports both array format (list of repos) and object format (repos + custom token).",
"description": "Plugin configuration for installing plugins before workflow execution. Supports array format (list of repos/plugin configs) and object format (repos + custom token).",
"examples": [
["github/copilot-plugin", "acme/custom-tools"],
[
"github/simple-plugin",
{
"id": "github/mcp-plugin",
"mcp": {
"env": {
"API_KEY": "${{ secrets.API_KEY }}"
}
}
}
],
{
"repos": ["github/copilot-plugin", "acme/custom-tools"],
"github-token": "${{ secrets.CUSTOM_PLUGIN_TOKEN }}"
Expand All @@ -2494,25 +2505,93 @@
"oneOf": [
{
"type": "array",
"description": "List of plugin repository slugs to install. Each plugin is installed using the engine's plugin installation command with default token resolution.",
"description": "List of plugins to install. Each item can be either a repository slug string (e.g., 'org/repo') or an object with id and optional MCP configuration.",
"items": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$",
"description": "Plugin repository slug in the format 'org/repo' (e.g., 'github/example-plugin')"
"oneOf": [
{
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$",
"description": "Plugin repository slug in the format 'org/repo' (e.g., 'github/example-plugin')"
},
{
"type": "object",
"description": "Plugin configuration with ID and optional MCP settings for environment variables",
"required": ["id"],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$",
"description": "Plugin repository slug in the format 'org/repo' (e.g., 'github/example-plugin')"
},
"mcp": {
"type": "object",
"description": "MCP server configuration for this plugin. When defined, the compiler scans the plugin's MCP JSON and mounts it in the gateway with the specified environment variables.",
"properties": {
"env": {
"type": "object",
"description": "Environment variables to pass when instantiating the MCP server. These variables are made available to the MCP server at runtime and can reference secrets using ${{ secrets.SECRET_NAME }} syntax.",
"additionalProperties": {
"type": "string"
},
"examples": [
{
"API_KEY": "${{ secrets.API_KEY }}",
"API_URL": "https://api.example.com"
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}
},
{
"type": "object",
"description": "Plugin configuration with custom GitHub token. The custom token overrides the default token resolution chain.",
"description": "Plugin configuration with custom GitHub token. Repos can be either strings or objects with MCP configuration.",
"required": ["repos"],
"properties": {
"repos": {
"type": "array",
"description": "List of plugin repository slugs to install",
"description": "List of plugins to install. Each item can be either a repository slug string or an object with id and optional MCP configuration.",
"items": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$",
"description": "Plugin repository slug in the format 'org/repo' (e.g., 'github/example-plugin')"
"oneOf": [
{
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$",
"description": "Plugin repository slug in the format 'org/repo' (e.g., 'github/example-plugin')"
},
{
"type": "object",
"description": "Plugin configuration with ID and optional MCP settings",
"required": ["id"],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$",
"description": "Plugin repository slug in the format 'org/repo' (e.g., 'github/example-plugin')"
},
"mcp": {
"type": "object",
"description": "MCP server configuration for this plugin",
"properties": {
"env": {
"type": "object",
"description": "Environment variables for the MCP server",
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}
},
"github-token": {
Expand Down Expand Up @@ -3537,7 +3616,7 @@
},
"description": "Toolsets to enable"
},
"url": {
"id": {
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the generic tool/MCP server schema, the HTTP server URL field appears to have been renamed from url to id, but the description still says "URL for HTTP mode MCP servers". This is likely a breaking schema regression for existing workflows that set url. Consider reverting this property name back to url (or updating all related schema refs + parsers consistently if the rename was intentional).

Suggested change
"id": {
"url": {

Copilot uses AI. Check for mistakes.
"type": "string",
"description": "URL for HTTP mode MCP servers"
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/workflow/claude_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ func (e *ClaudeEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHub
}

// Add plugin installation steps after Claude CLI installation
if len(workflowData.Plugins) > 0 {
claudeLog.Printf("Adding plugin installation steps: %d plugins", len(workflowData.Plugins))
if workflowData.PluginInfo != nil && len(workflowData.PluginInfo.Plugins) > 0 {
claudeLog.Printf("Adding plugin installation steps: %d plugins", len(workflowData.PluginInfo.Plugins))
// Use plugin-specific token if provided, otherwise use top-level github-token
tokenToUse := workflowData.PluginsToken
tokenToUse := workflowData.PluginInfo.CustomToken
if tokenToUse == "" {
tokenToUse = workflowData.GitHubToken
}
pluginSteps := GeneratePluginInstallationSteps(workflowData.Plugins, "claude", tokenToUse)
pluginSteps := GeneratePluginInstallationSteps(workflowData.PluginInfo.Plugins, "claude", tokenToUse)
steps = append(steps, pluginSteps...)
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/workflow/codex_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,14 @@ func (e *CodexEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubA
}

// Add plugin installation steps after Codex CLI installation
if len(workflowData.Plugins) > 0 {
codexEngineLog.Printf("Adding plugin installation steps: %d plugins", len(workflowData.Plugins))
if workflowData.PluginInfo != nil && len(workflowData.PluginInfo.Plugins) > 0 {
codexEngineLog.Printf("Adding plugin installation steps: %d plugins", len(workflowData.PluginInfo.Plugins))
// Use plugin-specific token if provided, otherwise use top-level github-token
tokenToUse := workflowData.PluginsToken
tokenToUse := workflowData.PluginInfo.CustomToken
if tokenToUse == "" {
tokenToUse = workflowData.GitHubToken
}
pluginSteps := GeneratePluginInstallationSteps(workflowData.Plugins, "codex", tokenToUse)
pluginSteps := GeneratePluginInstallationSteps(workflowData.PluginInfo.Plugins, "codex", tokenToUse)
steps = append(steps, pluginSteps...)
}

Expand Down
25 changes: 15 additions & 10 deletions pkg/workflow/compiler_orchestrator_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ var orchestratorToolsLog = logger.New("workflow:compiler_orchestrator_tools")
type toolsProcessingResult struct {
tools map[string]any
runtimes map[string]any
plugins []string
pluginsToken string
pluginInfo *PluginInfo // Consolidated plugin information
toolsTimeout int
toolsStartupTimeout int
markdownContent string
Expand Down Expand Up @@ -140,13 +139,20 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle
}

// Extract plugins from frontmatter
plugins, pluginsToken := extractPluginsFromFrontmatter(result.Frontmatter)
if len(plugins) > 0 {
orchestratorToolsLog.Printf("Extracted %d plugins from frontmatter (custom_token=%v)", len(plugins), pluginsToken != "")
pluginInfo := extractPluginsFromFrontmatter(result.Frontmatter)
if pluginInfo != nil && len(pluginInfo.Plugins) > 0 {
orchestratorToolsLog.Printf("Extracted %d plugins from frontmatter (custom_token=%v, mcp_configs=%d)",
len(pluginInfo.Plugins), pluginInfo.CustomToken != "", len(pluginInfo.MCPConfigs))
}

// Merge plugins from imports with top-level plugins
if len(importsResult.MergedPlugins) > 0 {
if pluginInfo == nil {
pluginInfo = &PluginInfo{
MCPConfigs: make(map[string]*PluginMCPConfig),
}
Comment on lines 149 to +153
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugins are merged from imports via importsResult.MergedPlugins, but any per-plugin MCP configs from imports are not merged into pluginInfo.MCPConfigs. If imports can contain {id, mcp: {env}} items, this will drop required env vars for imported MCP-enabled plugins. Consider carrying merged MCP configs through imports processing and merging them here with a deterministic precedence rule.

Copilot uses AI. Check for mistakes.
}

orchestratorToolsLog.Printf("Merging %d plugins from imports", len(importsResult.MergedPlugins))
// Create a set to track unique plugins
pluginsSet := make(map[string]bool)
Expand All @@ -157,7 +163,7 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle
}

// Add top-level plugins (these override/supplement imports)
for _, plugin := range plugins {
for _, plugin := range pluginInfo.Plugins {
pluginsSet[plugin] = true
}

Expand All @@ -169,9 +175,9 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle

// Sort for deterministic output
sort.Strings(mergedPlugins)
plugins = mergedPlugins
pluginInfo.Plugins = mergedPlugins

orchestratorToolsLog.Printf("Merged plugins: %d total unique plugins", len(plugins))
orchestratorToolsLog.Printf("Merged plugins: %d total unique plugins", len(pluginInfo.Plugins))
}

// Add MCP fetch server if needed (when web-fetch is requested but engine doesn't support it)
Expand Down Expand Up @@ -292,8 +298,7 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle
return &toolsProcessingResult{
tools: tools,
runtimes: runtimes,
plugins: plugins,
pluginsToken: pluginsToken,
pluginInfo: pluginInfo,
toolsTimeout: toolsTimeout,
toolsStartupTimeout: toolsStartupTimeout,
markdownContent: markdownContent,
Expand Down
3 changes: 1 addition & 2 deletions pkg/workflow/compiler_orchestrator_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ func (c *Compiler) buildInitialWorkflowData(
Tools: toolsResult.tools,
ParsedTools: NewTools(toolsResult.tools),
Runtimes: toolsResult.runtimes,
Plugins: toolsResult.plugins,
PluginsToken: toolsResult.pluginsToken,
PluginInfo: toolsResult.pluginInfo,
MarkdownContent: toolsResult.markdownContent,
AI: engineSetup.engineSetting,
EngineConfig: engineSetup.engineConfig,
Expand Down
3 changes: 1 addition & 2 deletions pkg/workflow/compiler_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,7 @@ type WorkflowData struct {
CacheMemoryConfig *CacheMemoryConfig // parsed cache-memory configuration
RepoMemoryConfig *RepoMemoryConfig // parsed repo-memory configuration
Runtimes map[string]any // runtime version overrides from frontmatter
Plugins []string // plugin repository slugs to install (e.g., ["org/repo", "org2/repo2"])
PluginsToken string // custom github-token for plugin installation (from plugins.github-token field)
PluginInfo *PluginInfo // Consolidated plugin information (plugins, custom token, MCP configs)
ToolsTimeout int // timeout in seconds for tool/MCP operations (0 = use engine default)
GitHubToken string // top-level github-token expression from frontmatter
ToolsStartupTimeout int // timeout in seconds for MCP server startup (0 = use engine default)
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/copilot_engine_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ func (e *CopilotEngine) GetExecutionSteps(workflowData *WorkflowData, logFile st
// Add Copilot config directory when plugins are declared so the CLI can discover installed plugins
// Plugins are installed to ~/.copilot/plugins/ via copilot plugin install command
// The CLI also reads plugin-index.json from ~/.copilot/ to discover installed plugins
if len(workflowData.Plugins) > 0 {
if workflowData.PluginInfo != nil && len(workflowData.PluginInfo.Plugins) > 0 {
copilotArgs = append(copilotArgs, "--add-dir", "/home/runner/.copilot/")
copilotExecLog.Printf("Added Copilot config directory to --add-dir for plugin discovery (%d plugins)", len(workflowData.Plugins))
copilotExecLog.Printf("Added Copilot config directory to --add-dir for plugin discovery (%d plugins)", len(workflowData.PluginInfo.Plugins))
}

copilotExecLog.Print("Using firewall mode with simplified arguments")
Expand Down
8 changes: 4 additions & 4 deletions pkg/workflow/copilot_engine_installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,14 @@ func (e *CopilotEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHu
}

// Add plugin installation steps after Copilot CLI installation
if len(workflowData.Plugins) > 0 {
copilotInstallLog.Printf("Adding plugin installation steps: %d plugins", len(workflowData.Plugins))
if workflowData.PluginInfo != nil && len(workflowData.PluginInfo.Plugins) > 0 {
copilotInstallLog.Printf("Adding plugin installation steps: %d plugins", len(workflowData.PluginInfo.Plugins))
// Use plugin-specific token if provided, otherwise use top-level github-token
tokenToUse := workflowData.PluginsToken
tokenToUse := workflowData.PluginInfo.CustomToken
if tokenToUse == "" {
tokenToUse = workflowData.GitHubToken
}
pluginSteps := GeneratePluginInstallationSteps(workflowData.Plugins, "copilot", tokenToUse)
pluginSteps := GeneratePluginInstallationSteps(workflowData.PluginInfo.Plugins, "copilot", tokenToUse)
steps = append(steps, pluginSteps...)
}

Expand Down
Loading
Loading