Skip to content

Commit 1e57a20

Browse files
authored
Merge pull request #1442 from trheyi/main
Enhance Sandbox Integration and CI Workflow for AI Tests
2 parents 3fb23d8 + e160f30 commit 1e57a20

37 files changed

+6344
-55
lines changed

.github/workflows/pr-test.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,8 +541,19 @@ jobs:
541541
echo "YAO_DB_DRIVER=sqlite3" >> $GITHUB_ENV
542542
echo "YAO_DB_PRIMARY=${{ github.WORKSPACE }}/../app/db/yao.db" >> $GITHUB_ENV
543543
544+
- name: Pull Sandbox Test Images
545+
run: |
546+
docker pull alpine:latest
547+
docker pull yaoapp/sandbox-base:latest || true
548+
docker pull yaoapp/sandbox-claude:latest || true
549+
544550
- name: Run AI Tests (agent, aigc)
545-
run: make unit-test-ai
551+
env:
552+
YAO_SANDBOX_WORKSPACE: ${{ runner.temp }}/sandbox/workspace
553+
YAO_SANDBOX_IPC: ${{ runner.temp }}/sandbox/ipc
554+
run: |
555+
export YAO_SANDBOX_CONTAINER_USER="$(id -u):$(id -g)"
556+
make unit-test-ai
546557
547558
- name: Codecov Report
548559
uses: codecov/codecov-action@v4

.github/workflows/unit-test.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,19 @@ jobs:
435435
echo "YAO_DB_DRIVER=sqlite3" >> $GITHUB_ENV
436436
echo "YAO_DB_PRIMARY=${{ github.WORKSPACE }}/../app/db/yao.db" >> $GITHUB_ENV
437437
438+
- name: Pull Sandbox Test Images
439+
run: |
440+
docker pull alpine:latest
441+
docker pull yaoapp/sandbox-base:latest || true
442+
docker pull yaoapp/sandbox-claude:latest || true
443+
438444
- name: Run AI Tests (agent, aigc)
439-
run: make unit-test-ai
445+
env:
446+
YAO_SANDBOX_WORKSPACE: ${{ runner.temp }}/sandbox/workspace
447+
YAO_SANDBOX_IPC: ${{ runner.temp }}/sandbox/ipc
448+
run: |
449+
export YAO_SANDBOX_CONTAINER_USER="$(id -u):$(id -g)"
450+
make unit-test-ai
440451
441452
- name: Codecov Report
442453
uses: codecov/codecov-action@v4

agent/assistant/agent.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/yaoapp/yao/agent/i18n"
1313
"github.com/yaoapp/yao/agent/llm"
1414
"github.com/yaoapp/yao/agent/output/message"
15+
agentsandbox "github.com/yaoapp/yao/agent/sandbox"
1516
)
1617

1718
// Stream stream the agent
@@ -150,6 +151,33 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
150151
}
151152
ctx.Logger.PhaseComplete("History")
152153

154+
// ================================================
155+
// Initialize Sandbox (if configured)
156+
// ================================================
157+
// Sandbox must be created BEFORE hooks so that hooks can access ctx.sandbox
158+
var sandboxExecutor agentsandbox.Executor
159+
var sandboxCleanup func()
160+
if ast.HasSandbox() {
161+
ctx.Logger.Phase("Sandbox")
162+
var err error
163+
sandboxExecutor, sandboxCleanup, err = ast.initSandbox(ctx, opts)
164+
if err != nil {
165+
ast.traceAgentFail(agentNode, err)
166+
ast.sendStreamEndOnError(ctx, streamHandler, streamStartTime, err)
167+
return nil, err
168+
}
169+
// Set sandbox executor in context so hooks can access ctx.sandbox
170+
// The executor implements both agentsandbox.Executor and context.SandboxExecutor
171+
ctx.SetSandboxExecutor(sandboxExecutor)
172+
ctx.Logger.PhaseComplete("Sandbox")
173+
}
174+
// Ensure sandbox cleanup on exit
175+
defer func() {
176+
if sandboxCleanup != nil {
177+
sandboxCleanup()
178+
}
179+
}()
180+
153181
// ================================================
154182
// Execute Create Hook
155183
// ================================================
@@ -254,7 +282,14 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
254282
})
255283

256284
// Execute the LLM streaming call
257-
completionResponse, err = ast.executeLLMStream(ctx, completionMessages, completionOptions, agentNode, streamHandler, opts)
285+
// Choose between sandbox execution or direct LLM execution
286+
if ast.HasSandbox() {
287+
// Sandbox execution path (Claude CLI, Cursor CLI, etc.)
288+
completionResponse, err = ast.executeSandboxStream(ctx, completionMessages, agentNode, streamHandler, sandboxExecutor)
289+
} else {
290+
// Direct LLM execution path
291+
completionResponse, err = ast.executeLLMStream(ctx, completionMessages, completionOptions, agentNode, streamHandler, opts)
292+
}
258293
if err != nil {
259294
finalStatus = context.ResumeStatusFailed
260295
finalError = err
@@ -282,8 +317,9 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
282317
// ================================================
283318
// Execute tool calls with retry
284319
// ================================================
320+
// Note: Skip MCP tool calls execution for sandbox mode - Claude CLI handles them internally
285321
var toolCallResponses []context.ToolCallResponse = nil
286-
if completionResponse != nil && completionResponse.ToolCalls != nil {
322+
if completionResponse != nil && completionResponse.ToolCalls != nil && !ast.HasSandbox() {
287323

288324
maxToolRetries := 3
289325
currentMessages := completionMessages

agent/assistant/load.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,15 @@ func loadMap(data map[string]interface{}) (*Assistant, error) {
714714
assistant.Workflow = wf
715715
}
716716

717+
// sandbox (for coding agents like Claude CLI, Cursor CLI)
718+
if sandbox, has := data["sandbox"]; has {
719+
sb, err := store.ToSandbox(sandbox)
720+
if err != nil {
721+
return nil, err
722+
}
723+
assistant.Sandbox = sb
724+
}
725+
717726
// uses (wrapper configurations for vision, audio, etc.)
718727
// Merge hierarchy: global uses < assistant uses
719728
if uses, has := data["uses"]; has {

0 commit comments

Comments
 (0)