11package agent
22
33import (
4+ "bytes"
45 "context"
56 "fmt"
67 "io"
@@ -18,12 +19,19 @@ import (
1819 "github.com/mackeh/AegisClaw/internal/skill"
1920)
2021
22+ // ExecutionResult holds the captured output of a skill run
23+ type ExecutionResult struct {
24+ ExitCode int
25+ Stdout string
26+ Stderr string
27+ }
28+
2129// ExecuteSkill handles the end-to-end execution of a skill command
22- func ExecuteSkill (ctx context.Context , m * skill.Manifest , cmdName string , userArgs []string ) error {
30+ func ExecuteSkill (ctx context.Context , m * skill.Manifest , cmdName string , userArgs []string ) ( * ExecutionResult , error ) {
2331 // 1. Find Command
2432 skillCmd , ok := m .Commands [cmdName ]
2533 if ! ok {
26- return fmt .Errorf ("command '%s' not found in skill '%s'" , cmdName , m .Name )
34+ return nil , fmt .Errorf ("command '%s' not found in skill '%s'" , cmdName , m .Name )
2735 }
2836
2937 // 2. Prepare Scopes
@@ -50,7 +58,7 @@ func ExecuteSkill(ctx context.Context, m *skill.Manifest, cmdName string, userAr
5058 // 3. Load Policy & Evaluate
5159 p , err := policy .LoadDefaultPolicy ()
5260 if err != nil {
53- return fmt .Errorf ("failed to load policy: %w" , err )
61+ return nil , fmt .Errorf ("failed to load policy: %w" , err )
5462 }
5563 engine := policy .NewEngine (p )
5664 decision , riskyScopes := engine .EvaluateRequest (req )
@@ -61,13 +69,13 @@ func ExecuteSkill(ctx context.Context, m *skill.Manifest, cmdName string, userAr
6169 switch decision {
6270 case policy .Deny :
6371 fmt .Println ("❌ Policy DENIED this action." )
64- return fmt .Errorf ("policy denied action" )
72+ return nil , fmt .Errorf ("policy denied action" )
6573
6674 case policy .RequireApproval :
6775 // Check persistent approvals
6876 store , err := approval .NewStore ()
6977 if err != nil {
70- return err
78+ return nil , err
7179 }
7280
7381 allApproved := true
@@ -85,12 +93,12 @@ func ExecuteSkill(ctx context.Context, m *skill.Manifest, cmdName string, userAr
8593 // Prompt User
8694 userDec , err := approval .RequestApproval (req )
8795 if err != nil {
88- return err
96+ return nil , err
8997 }
9098
9199 if userDec == "deny" {
92100 fmt .Println ("❌ User denied the request." )
93- return fmt .Errorf ("user denied request" )
101+ return nil , fmt .Errorf ("user denied request" )
94102 }
95103
96104 finalDecision = "allow"
@@ -119,7 +127,7 @@ func ExecuteSkill(ctx context.Context, m *skill.Manifest, cmdName string, userAr
119127 }
120128
121129 if finalDecision != "allow" {
122- return fmt .Errorf ("execution blocked" )
130+ return nil , fmt .Errorf ("execution blocked" )
123131 }
124132
125133 // 6. Prepare Execution Environment
@@ -128,7 +136,6 @@ func ExecuteSkill(ctx context.Context, m *skill.Manifest, cmdName string, userAr
128136
129137 // Inject Secrets if allowed
130138 if finalDecision == "allow" {
131- cfgDir , _ := config .DefaultConfigDir ()
132139 secretsDir := filepath .Join (cfgDir , "secrets" )
133140 mgr := secrets .NewManager (secretsDir )
134141
@@ -144,11 +151,12 @@ func ExecuteSkill(ctx context.Context, m *skill.Manifest, cmdName string, userAr
144151 }
145152 }
146153
154+ // 7. Execute
147155 fmt .Printf ("🚀 Running skill: %s\n " , m .Name )
148156
149157 exec , err := sandbox .NewDockerExecutor ()
150158 if err != nil {
151- return fmt .Errorf ("failed to initialize executor: %w" , err )
159+ return nil , fmt .Errorf ("failed to initialize executor: %w" , err )
152160 }
153161
154162 // Set a default timeout for execution
@@ -163,13 +171,23 @@ func ExecuteSkill(ctx context.Context, m *skill.Manifest, cmdName string, userAr
163171 AllowedDomains : allowedDomains ,
164172 })
165173 if err != nil {
166- return fmt .Errorf ("execution failed: %w" , err )
174+ return nil , fmt .Errorf ("execution failed: %w" , err )
167175 }
168176
169- // Stream output
170- io .Copy (os .Stdout , result .Stdout )
171- io .Copy (os .Stderr , result .Stderr )
172-
173- fmt .Printf ("✅ Skill finished (exit code %d)\n " , result .ExitCode )
174- return nil
177+ // Capture output
178+ stdoutBuf := new (bytes.Buffer )
179+ stderrBuf := new (bytes.Buffer )
180+
181+ // We still stream to console for better visibility during 'run' or manual CLI use
182+ stdoutTee := io .TeeReader (result .Stdout , stdoutBuf )
183+ stderrTee := io .TeeReader (result .Stderr , stderrBuf )
184+
185+ io .Copy (os .Stdout , stdoutTee )
186+ io .Copy (os .Stderr , stderrTee )
187+
188+ return & ExecutionResult {
189+ ExitCode : result .ExitCode ,
190+ Stdout : stdoutBuf .String (),
191+ Stderr : stderrBuf .String (),
192+ }, nil
175193}
0 commit comments