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
16 changes: 8 additions & 8 deletions pkg/cli/bootstrap_profile_github_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func runBootstrapGitHubAppAction(ctx context.Context, repo string, action reposi
return nil, err
}

clientID := strings.TrimSpace(os.Getenv(bootstrapGitHubAppClientIDEnv))
privateKey := strings.TrimRight(os.Getenv(bootstrapGitHubAppPrivateKeyEnv), "\r\n")
clientID := strings.TrimSpace(lookupEnv(bootstrapGitHubAppClientIDEnv))
privateKey := strings.TrimRight(lookupEnv(bootstrapGitHubAppPrivateKeyEnv), "\r\n")
handled, err := handleBootstrapGitHubAppExistingFlow(ctx, repo, action, overrides, clientID, privateKey)
if err != nil {
return nil, err
Expand Down Expand Up @@ -324,22 +324,22 @@ func buildBootstrapGitHubAppMux(ctx context.Context, csrfState, owner, ownerType
func loadBootstrapGitHubAppOverrides() (bootstrapGitHubAppOverrides, error) {
overrides := bootstrapGitHubAppOverrides{
Mode: "",
Owner: strings.TrimSpace(os.Getenv(bootstrapGitHubAppOwnerEnv)),
Name: strings.TrimSpace(os.Getenv(bootstrapGitHubAppNameEnv)),
HomepageURL: strings.TrimSpace(os.Getenv(bootstrapGitHubAppURLEnv)),
Description: strings.TrimSpace(os.Getenv(bootstrapGitHubAppDescriptionEnv)),
Owner: strings.TrimSpace(lookupEnv(bootstrapGitHubAppOwnerEnv)),
Name: strings.TrimSpace(lookupEnv(bootstrapGitHubAppNameEnv)),
HomepageURL: strings.TrimSpace(lookupEnv(bootstrapGitHubAppURLEnv)),
Description: strings.TrimSpace(lookupEnv(bootstrapGitHubAppDescriptionEnv)),
OpenBrowser: true,
}

switch mode := strings.ToLower(strings.TrimSpace(os.Getenv(bootstrapGitHubAppModeEnv))); mode {
switch mode := strings.ToLower(strings.TrimSpace(lookupEnv(bootstrapGitHubAppModeEnv))); mode {
case "", "auto":
case "create", "existing":
overrides.Mode = mode
default:
return bootstrapGitHubAppOverrides{}, fmt.Errorf("%s must be one of: auto, create, existing. Example: export %s=create", bootstrapGitHubAppModeEnv, bootstrapGitHubAppModeEnv)
}

if raw := strings.TrimSpace(os.Getenv(bootstrapNoOpenBrowserEnv)); raw != "" {
if raw := strings.TrimSpace(lookupEnv(bootstrapNoOpenBrowserEnv)); raw != "" {
disabled, err := parseBootstrapBool(raw)
if err != nil {
return bootstrapGitHubAppOverrides{}, fmt.Errorf("%s: %w", bootstrapNoOpenBrowserEnv, err)
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/bootstrap_profile_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func parseBootstrapNames(output []byte) []string {

func resolveBootstrapTextValue(envName, title, description, defaultValue string, allowed []string, optional bool) (string, bool, error) {
bootstrapProfileHelpersLog.Printf("Resolving text value: env=%s, optional=%v, hasDefault=%v", envName, optional, defaultValue != "")
if envValue := strings.TrimSpace(os.Getenv(envName)); envValue != "" {
if envValue := strings.TrimSpace(lookupEnv(envName)); envValue != "" {
bootstrapProfileHelpersLog.Printf("Resolved %s from environment variable", envName)
if err := validateBootstrapEnumValue(envValue, allowed, optional); err != nil {
return "", false, err
Expand Down Expand Up @@ -127,7 +127,7 @@ func resolveBootstrapTextValue(envName, title, description, defaultValue string,
}

func resolveBootstrapSecretValue(envName, title, description string, optional bool) (string, bool, error) {
if envValue := strings.TrimRight(os.Getenv(envName), "\r\n"); envValue != "" {
if envValue := strings.TrimRight(lookupEnv(envName), "\r\n"); envValue != "" {
return envValue, true, nil
}
if !tty.IsStderrTerminal() {
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/bootstrap_profile_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,9 @@ func stringMapValue(value any) (map[string]string, error) {

func manifestBootstrapFieldError(manifestPath string, index int, field string, err error) error {
if example, ok := manifestBootstrapFieldExample(field); ok {
return fmt.Errorf("invalid Agentic Workflow manifest %q: config[%d].%s %s. Example: config[%d].%s: %s", manifestPath, index, field, err.Error(), index, field, example)
return fmt.Errorf("invalid Agentic Workflow manifest %q: config[%d].%s %w. Example: config[%d].%s: %s", manifestPath, index, field, err, index, field, example)
}
return fmt.Errorf("invalid Agentic Workflow manifest %q: config[%d].%s %s", manifestPath, index, field, err.Error())
return fmt.Errorf("invalid Agentic Workflow manifest %q: config[%d].%s %w", manifestPath, index, field, err)
}

func manifestBootstrapFieldExample(field string) (string, bool) {
Expand Down
6 changes: 4 additions & 2 deletions pkg/cli/copilot_agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ var copilotAgentsLog = logger.New("cli:copilot_agents")

const agenticWorkflowsSkillFileListPlaceholder = "{{AW_FILE_LIST}}"
const ghAWMarkdownFilesAPIURL = "https://api.github.com/repos/github/gh-aw/contents/.github/aw?ref=main"
const agenticWorkflowsSkillDirDescription = ".github/skills/agentic-workflows directory"
const agenticWorkflowsAgentDirDescription = ".github/agents directory"

//go:embed data/agentic_workflows_agent.md
var agenticWorkflowsAgentTemplate string
Expand Down Expand Up @@ -84,7 +86,7 @@ func ensureAgenticWorkflowsDispatcher(verbose bool, skipInstructions bool, write
return nil
}

if err := writeGeneratedRepositoryInstructionFile(targetPath, []byte(skillContent), write, ".github/skills/agentic-workflows directory", "dispatcher skill"); err != nil {
if err := writeGeneratedRepositoryInstructionFile(targetPath, []byte(skillContent), write, agenticWorkflowsSkillDirDescription, "dispatcher skill"); err != nil {
copilotAgentsLog.Printf("Failed to write dispatcher skill: %s, error: %v", targetPath, err)
return fmt.Errorf("failed to write dispatcher skill: %w", err)
}
Expand Down Expand Up @@ -144,7 +146,7 @@ func ensureAgenticWorkflowsAgent(verbose bool, write bool) error {
return nil
}

if err := writeGeneratedRepositoryInstructionFile(targetPath, []byte(agenticWorkflowsAgentContent), write, ".github/agents directory", "Agentic Workflows custom agent"); err != nil {
if err := writeGeneratedRepositoryInstructionFile(targetPath, []byte(agenticWorkflowsAgentContent), write, agenticWorkflowsAgentDirDescription, "Agentic Workflows custom agent"); err != nil {
return fmt.Errorf("failed to write Agentic Workflows custom agent: %w", err)
}

Expand Down
57 changes: 34 additions & 23 deletions pkg/cli/mcp_inspect_inspector.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ func spawnMCPInspector(ctx context.Context, workflowFile string, serverFilter st

g, gctx := errgroup.WithContext(ctx)

// Set up cleanup function for stdio servers. Registered here, before any
// server is started, so that early returns (e.g. context cancellation during
// the startup wait) still kill started processes and drain monitor goroutines.
defer func() {
if len(serverProcesses) > 0 {
mcpInspectorLog.Printf("Cleaning up %d MCP server processes", len(serverProcesses))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Cleaning up MCP servers..."))
for i, cmd := range serverProcesses {
if cmd.Process != nil {
if err := cmd.Process.Kill(); err != nil && verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to kill server process %d: %v", cmd.Process.Pid, err)))
}
}
// Give each process a chance to clean up
if i < len(serverProcesses)-1 {
timer := time.NewTimer(mcpProcessCleanupDelay)
defer timer.Stop()
select {
case <-timer.C:
case <-gctx.Done():
}
}
}
if err := g.Wait(); err != nil {
mcpInspectorLog.Printf("Error from MCP server monitor goroutine: %v", err)
}
}
}()

// If workflow file is specified, extract MCP configurations and start servers
if workflowFile != "" {
// Resolve the workflow file path (supports shared workflows)
Expand Down Expand Up @@ -180,7 +209,11 @@ func spawnMCPInspector(ctx context.Context, workflowFile string, serverFilter st
}

// Give servers a moment to start up
time.Sleep(mcpStdioServerStartupDelay)
select {
case <-time.After(mcpStdioServerStartupDelay):
case <-gctx.Done():
return gctx.Err()
Comment thread
github-actions[bot] marked this conversation as resolved.
Comment thread
github-actions[bot] marked this conversation as resolved.
}
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("All stdio servers started successfully"))
}

Expand Down Expand Up @@ -211,28 +244,6 @@ func spawnMCPInspector(ctx context.Context, workflowFile string, serverFilter st
}
}

// Set up cleanup function for stdio servers
defer func() {
if len(serverProcesses) > 0 {
mcpInspectorLog.Printf("Cleaning up %d MCP server processes", len(serverProcesses))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Cleaning up MCP servers..."))
for i, cmd := range serverProcesses {
if cmd.Process != nil {
if err := cmd.Process.Kill(); err != nil && verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to kill server process %d: %v", cmd.Process.Pid, err)))
}
}
// Give each process a chance to clean up
if i < len(serverProcesses)-1 {
time.Sleep(mcpProcessCleanupDelay)
}
}
if err := g.Wait(); err != nil {
mcpInspectorLog.Printf("Error from MCP server monitor goroutine: %v", err)
}
}
}()

mcpInspectorLog.Print("Launching @modelcontextprotocol/inspector")
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Launching @modelcontextprotocol/inspector..."))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Visit http://localhost:5173 after the inspector starts"))
Expand Down
3 changes: 1 addition & 2 deletions pkg/workflow/github_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ func filteredGHCLIEnv(ghToken, githubToken, ghHost string) []string {
// cmd := ExecGH("api", "/user")
// output, err := cmd.Output()
func ExecGH(args ...string) *exec.Cmd {
//nolint:staticcheck // Passing nil context to use exec.Command instead of exec.CommandContext
return setupGHCommand(nil, args...)
return setupGHCommand(context.Background(), args...)
}

// ExecGHContext wraps gh CLI calls with context support and ensures proper token configuration.
Expand Down
5 changes: 3 additions & 2 deletions pkg/workflow/llm_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ type llmProviderProfile struct {
}

func normalizeLLMProvider(provider string) string {
normalized := strings.ToLower(strings.TrimSpace(provider))
if len(normalized) == 0 {
normalized := strings.TrimSpace(provider)
if normalized == "" {
return LLMProviderAnthropic
}
normalized = strings.ToLower(normalized)
if alias, ok := llmProviderAliases[normalized]; ok {
return alias
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/workflow/safe_outputs_tools_generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,13 @@ func computePropertyInjections(safeOutputs *SafeOutputsConfig) map[string]map[st
// Validate each configured value against the supported API values so that
// invalid strings (e.g. "done", "wontfix") are caught at compile time rather
// than producing a GitHub API 422 at runtime.
supported := make(map[string]bool, len(closeIssueStateReasonValues))
supported := make(map[string]struct{}, len(closeIssueStateReasonValues))
for _, v := range closeIssueStateReasonValues {
supported[v] = true
supported[v] = struct{}{}
}
valid := make([]string, 0, len(enumValues))
for _, v := range enumValues {
if supported[v] {
if _, ok := supported[v]; ok {
valid = append(valid, v)
} else {
safeOutputsConfigLog.Printf("Warning: allowed-state-reason value %q is not a supported GitHub API value; valid values: %v", v, closeIssueStateReasonValues)
Expand Down
10 changes: 5 additions & 5 deletions pkg/workflow/workflow_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,17 +502,17 @@ func mergeExcludedEnvVarNames(fromImports, fromMain []string) []string {
// Use max() for capacity hints: overflow-safe (no addition) and a tighter
// lower-bound than either length alone.
hint := max(len(fromImports), len(fromMain))
seen := make(map[string]bool, hint)
seen := make(map[string]struct{}, hint)
merged := make([]string, 0, hint)
for _, name := range fromImports {
if !seen[name] {
seen[name] = true
if _, ok := seen[name]; !ok {
seen[name] = struct{}{}
merged = append(merged, name)
}
}
for _, name := range fromMain {
if !seen[name] {
seen[name] = true
if _, ok := seen[name]; !ok {
seen[name] = struct{}{}
merged = append(merged, name)
}
}
Expand Down
Loading