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
2 changes: 1 addition & 1 deletion .github/workflows/glossary-maintainer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/glossary-maintainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ permissions:

engine:
id: copilot
agent: technical-doc-writer

network:
allowed:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/hourly-ci-cleaner.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/workflows/hourly-ci-cleaner.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ tracker-id: hourly-ci-cleaner
# - Target: Focus on systematic fix application with minimal iteration
# - Budget target: 15-20 turns for typical CI fixes
# Note: max-turns not available for Copilot engine (Claude only)
engine: copilot
engine:
id: copilot
agent: ci-cleaner
network:
allowed:
- defaults
Expand Down
34 changes: 0 additions & 34 deletions .github/workflows/smoke-claude-tmp.lock.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/technical-doc-writer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/technical-doc-writer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ permissions:

engine:
id: copilot
agent: technical-doc-writer

network:
allowed:
Expand Down
5 changes: 5 additions & 0 deletions docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,11 @@ engine:
# (optional)
config: "example-value"

# Agent identifier to pass to copilot --agent flag (copilot engine only).
# Specifies which custom agent to use for the workflow.
# (optional)
agent: "example-value"

# Optional array of command-line arguments to pass to the AI engine CLI. These
# arguments are injected after all other args but before the prompt.
# (optional)
Expand Down
4 changes: 4 additions & 0 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -6752,6 +6752,10 @@
"type": "string",
"description": "Additional TOML configuration text that will be appended to the generated config.toml in the action (codex engine only)"
},
"agent": {
"type": "string",
"description": "Agent identifier to pass to copilot --agent flag (copilot engine only). Specifies which custom agent to use for the workflow."
},
"args": {
"type": "array",
"items": {
Expand Down
11 changes: 6 additions & 5 deletions pkg/workflow/copilot_engine_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@ func (e *CopilotEngine) GetExecutionSteps(workflowData *WorkflowData, logFile st
copilotArgs = append(copilotArgs, "--model", workflowData.EngineConfig.Model)
}

// Add --agent flag if custom agent file is specified (via imports)
// Copilot CLI expects agent identifier (filename without extension), not full path
if workflowData.AgentFile != "" {
agentIdentifier := ExtractAgentIdentifier(workflowData.AgentFile)
copilotExecLog.Printf("Using custom agent: %s (identifier: %s)", workflowData.AgentFile, agentIdentifier)
// Add --agent flag if specified via engine.agent
// Note: Agent imports (.github/agents/*.md) still work for importing markdown content,
// but they do NOT automatically set the --agent flag. Only engine.agent controls the flag.
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Agent != "" {
agentIdentifier := workflowData.EngineConfig.Agent
copilotExecLog.Printf("Using agent from engine.agent: %s", agentIdentifier)
copilotArgs = append(copilotArgs, "--agent", agentIdentifier)
}

Expand Down
9 changes: 9 additions & 0 deletions pkg/workflow/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type EngineConfig struct {
Config string
Args []string
Firewall *FirewallConfig // AWF firewall configuration
Agent string // Agent identifier for copilot --agent flag (copilot engine only)
}

// NetworkPermissions represents network access permissions for workflow execution
Expand Down Expand Up @@ -200,6 +201,14 @@ func (c *Compiler) ExtractEngineConfig(frontmatter map[string]any) (string, *Eng
}
}

// Extract optional 'agent' field (string - copilot engine only)
if agent, hasAgent := engineObj["agent"]; hasAgent {
if agentStr, ok := agent.(string); ok {
config.Agent = agentStr
engineLog.Printf("Extracted agent identifier: %s", agentStr)
}
}

// Extract optional 'firewall' field (object format)
if firewall, hasFirewall := engineObj["firewall"]; hasFirewall {
if firewallObj, ok := firewall.(map[string]any); ok {
Expand Down
64 changes: 60 additions & 4 deletions pkg/workflow/engine_agent_import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,33 @@ import (
"testing"
)

// TestCopilotEngineWithAgentFromImports tests that copilot engine includes --agent flag when agent file is imported
// TestCopilotEngineWithAgentFromEngineConfig tests that copilot engine includes --agent flag when specified in engine.agent
func TestCopilotEngineWithAgentFromEngineConfig(t *testing.T) {
engine := NewCopilotEngine()
workflowData := &WorkflowData{
Name: "test-workflow",
EngineConfig: &EngineConfig{
ID: "copilot",
Agent: "my-custom-agent",
},
}

steps := engine.GetExecutionSteps(workflowData, "/tmp/gh-aw/test.log")

if len(steps) != 1 {
t.Fatalf("Expected 1 execution step, got %d", len(steps))
}

stepContent := strings.Join([]string(steps[0]), "\n")

// Copilot CLI expects agent identifier
if !strings.Contains(stepContent, `--agent my-custom-agent`) {
t.Errorf("Expected '--agent my-custom-agent' in copilot command, got:\n%s", stepContent)
}
}

// TestCopilotEngineWithAgentFromImports tests that agent imports do NOT set --agent flag
// Agent imports only import markdown content, not agent configuration
func TestCopilotEngineWithAgentFromImports(t *testing.T) {
engine := NewCopilotEngine()
workflowData := &WorkflowData{
Expand All @@ -28,9 +54,39 @@ func TestCopilotEngineWithAgentFromImports(t *testing.T) {

stepContent := strings.Join([]string(steps[0]), "\n")

// Copilot CLI expects agent identifier (filename without extension), not full path
if !strings.Contains(stepContent, `--agent test-agent`) {
t.Errorf("Expected '--agent test-agent' in copilot command, got:\n%s", stepContent)
// Agent imports should NOT set --agent flag (only engine.agent does)
if strings.Contains(stepContent, `--agent`) {
t.Errorf("Did not expect '--agent' flag when only AgentFile is set (without engine.agent), got:\n%s", stepContent)
}
}

// TestCopilotEngineAgentOnlyFromEngineConfig tests that --agent flag is only set by engine.agent
func TestCopilotEngineAgentOnlyFromEngineConfig(t *testing.T) {
engine := NewCopilotEngine()
workflowData := &WorkflowData{
Name: "test-workflow",
EngineConfig: &EngineConfig{
ID: "copilot",
Agent: "explicit-agent",
},
AgentFile: ".github/agents/import-agent.md",
}

steps := engine.GetExecutionSteps(workflowData, "/tmp/gh-aw/test.log")

if len(steps) != 1 {
t.Fatalf("Expected 1 execution step, got %d", len(steps))
}

stepContent := strings.Join([]string(steps[0]), "\n")

// Should only use explicit agent from engine.agent
if !strings.Contains(stepContent, `--agent explicit-agent`) {
t.Errorf("Expected '--agent explicit-agent' in copilot command, got:\n%s", stepContent)
}
// Should not use agent from imports
if strings.Contains(stepContent, `--agent import-agent`) {
t.Errorf("Did not expect '--agent import-agent' when engine.agent is set, got:\n%s", stepContent)
}
}

Expand Down
Loading