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/agent-persona-explorer.lock.yml

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

2 changes: 2 additions & 0 deletions .github/workflows/agent-persona-explorer.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ safe-outputs:
max: 1
close-older-issues: true
expires: false
threat-detection:
engine: copilot
Comment on lines +51 to +52
timeout-minutes: 180
imports:
- shared/reporting.md
Expand Down
33 changes: 18 additions & 15 deletions pkg/workflow/threat_detection.go
Original file line number Diff line number Diff line change
Expand Up @@ -890,34 +890,37 @@ func getThreatDetectionAdditionalAllowedDomains(data *WorkflowData) []string {
// getThreatDetectionEngineID returns the effective engine ID for the detection job.
// It mirrors threat-detection engine resolution: threat-detection.engine overrides main engine.
func (c *Compiler) getThreatDetectionEngineID(data *WorkflowData) string {
var engineID string

if data.SafeOutputs != nil && data.SafeOutputs.ThreatDetection != nil &&
data.SafeOutputs.ThreatDetection.EngineConfig != nil &&
data.SafeOutputs.ThreatDetection.EngineConfig.ID != "" {
return data.SafeOutputs.ThreatDetection.EngineConfig.ID
engineID = data.SafeOutputs.ThreatDetection.EngineConfig.ID
} else {
engineID = data.AI
if engineID == "" && data.EngineConfig != nil && data.EngineConfig.ID != "" {
engineID = data.EngineConfig.ID
}
}

mainEngineID := data.AI
if mainEngineID == "" && data.EngineConfig != nil && data.EngineConfig.ID != "" {
mainEngineID = data.EngineConfig.ID
if engineID == "" {
engineID = "claude"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot default engine for detection is copilot

}

if mainEngineID != "" {
return mainEngineID
// Threat detection currently does not support the Pi engine backend.
// Normalize to Copilot so workflows with engine: pi still get a working detector.
if engineID == "pi" {
return "copilot"
}

return "claude"
return engineID
}

// getExternalThreatDetectionEngineID returns the engine used by the external
// threat-detect path. Pi workflows currently fall back to the Copilot engine
// because threat-detect only knows how to launch the built-in gh-aw agentic
// engines, and Pi is not one of those supported detector backends yet.
// threat-detect path. Threat-detection engine resolution is centralized in
// getThreatDetectionEngineID, including Pi -> Copilot normalization.
func (c *Compiler) getExternalThreatDetectionEngineID(data *WorkflowData) string {
engineID := c.getThreatDetectionEngineID(data)
if engineID == "pi" {
return "copilot"
}
return engineID
return c.getThreatDetectionEngineID(data)
}

func canReuseThreatDetectionEngineConfigForExternalDetector(data *WorkflowData, engineID string) bool {
Expand Down
50 changes: 50 additions & 0 deletions pkg/workflow/threat_detection_isolation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,56 @@ Test workflow`
}
}

func TestInlineDetectionUsesCopilotForPiWorkflows(t *testing.T) {
compiler := NewCompiler()

tmpDir := testutil.TempDir(t, "test-inline-detector-pi-*")
workflowPath := filepath.Join(tmpDir, "test-inline-detector-pi.md")

workflowContent := `---
on: push
engine:
id: pi
model: copilot/gpt-5.4
safe-outputs:
create-issue:
tools:
github:
mode: gh-proxy
cli-proxy: true
---
Test workflow`

if err := os.WriteFile(workflowPath, []byte(workflowContent), 0644); err != nil {
t.Fatalf("Failed to write workflow file: %v", err)
}

if err := compiler.CompileWorkflow(workflowPath); err != nil {
t.Fatalf("Failed to compile workflow: %v", err)
}

lockFile := stringutil.MarkdownToLockFile(workflowPath)
result, err := os.ReadFile(lockFile)
if err != nil {
t.Fatalf("Failed to read compiled workflow: %v", err)
}

detectionSection := extractJobSection(string(result), "detection")
if detectionSection == "" {
t.Fatal("Detection job not found in compiled workflow")
}

if !strings.Contains(detectionSection, "install_copilot_cli.sh") {
t.Error("Pi inline detection path must install the Copilot engine")
}
if strings.Contains(detectionSection, "@earendil-works/pi-coding-agent") {
t.Error("Pi inline detection path must not install the Pi engine")
}
if !strings.Contains(detectionSection, "COPILOT_GITHUB_TOKEN:") {
t.Error("Pi inline detection path must inherit Copilot auth env")
}
}

func TestExternalDetectorPathPreparesCodexConfig(t *testing.T) {
compiler := NewCompiler()

Expand Down
10 changes: 5 additions & 5 deletions pkg/workflow/threat_detection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2104,7 +2104,7 @@ func TestBuildDetectionEngineExecutionStepPropagatesHarnessScriptOverride(t *tes
}
}

func TestBuildDetectionEngineExecutionStepOmitsPiCooldownEnv(t *testing.T) {
func TestBuildDetectionEngineExecutionStepUsesCopilotForPi(t *testing.T) {
compiler := NewCompiler()

data := &WorkflowData{
Expand All @@ -2123,11 +2123,11 @@ func TestBuildDetectionEngineExecutionStepOmitsPiCooldownEnv(t *testing.T) {
}

rendered := strings.Join(steps, "")
if !strings.Contains(rendered, "Install Pi CLI") {
t.Fatal("expected detection steps to include the Pi install step")
if !strings.Contains(rendered, "Install GitHub Copilot CLI") {
t.Fatal("expected detection steps to include the Copilot install step for pi workflows")
}
if strings.Contains(rendered, "NPM_CONFIG_MIN_RELEASE_AGE:") {
t.Fatalf("expected detection steps to omit npm cooldown env for Pi installs")
if strings.Contains(rendered, "Install Pi CLI") {
t.Fatal("expected detection steps to avoid Pi install step")
}
}

Expand Down
Loading