Merge pull request #1499 from trheyi/main
feat(sandbox): enhance assistant directory handling and update RunPre…
This commit is contained in:
commit
e9892784f5
6 changed files with 68 additions and 55 deletions
|
|
@ -94,10 +94,12 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options)
|
|||
return nil, nil, nil, "", fmt.Errorf("get runner %q: %w", cfg.Runner.Name, err)
|
||||
}
|
||||
|
||||
// 5. Resolve skills directory.
|
||||
// 5. Resolve assistant directory and skills subdirectory.
|
||||
assistantDir := ""
|
||||
skillsDir := ""
|
||||
if ast.Path != "" {
|
||||
dir := filepath.Join(config.Conf.AppSource, ast.Path, "skills")
|
||||
assistantDir = filepath.Join(config.Conf.AppSource, ast.Path)
|
||||
dir := filepath.Join(assistantDir, "skills")
|
||||
if info, e := os.Stat(dir); e == nil && info.IsDir() {
|
||||
skillsDir = dir
|
||||
}
|
||||
|
|
@ -117,13 +119,14 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options)
|
|||
|
||||
// 7. Runner.Prepare (standard context).
|
||||
err = runner.Prepare(stdCtx, &sandboxTypes.PrepareRequest{
|
||||
Computer: computer,
|
||||
Config: cfg,
|
||||
Connector: conn,
|
||||
SkillsDir: skillsDir,
|
||||
MCPServers: mcpServers,
|
||||
ConfigHash: ast.ConfigHash,
|
||||
RunSteps: sandboxv2.RunPrepareSteps,
|
||||
Computer: computer,
|
||||
Config: cfg,
|
||||
Connector: conn,
|
||||
SkillsDir: skillsDir,
|
||||
AssistantDir: assistantDir,
|
||||
MCPServers: mcpServers,
|
||||
ConfigHash: ast.ConfigHash,
|
||||
RunSteps: sandboxv2.RunPrepareSteps,
|
||||
})
|
||||
if err != nil {
|
||||
runner.Cleanup(stdCtx, computer)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (r *ClaudeRunner) Prepare(ctx context.Context, req *types.PrepareRequest) e
|
|||
}
|
||||
|
||||
if req.RunSteps != nil && len(steps) > 0 {
|
||||
if err := req.RunSteps(ctx, steps, req.Computer, req.Config.ID, req.ConfigHash); err != nil {
|
||||
if err := req.RunSteps(ctx, steps, req.Computer, req.Config.ID, req.ConfigHash, req.AssistantDir); err != nil {
|
||||
return fmt.Errorf("claude prepare steps: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
pathpkg "path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/yaoapp/yao/agent/sandbox/v2/types"
|
||||
|
|
@ -17,7 +18,9 @@ const onceMarkerDir = ".yao/prepare"
|
|||
// RunPrepareSteps executes a list of PrepareStep actions on the given Computer.
|
||||
// file/copy/marker operations use computer.Workplace() (gRPC volume, cross-platform).
|
||||
// exec operations use shell via Computer.Exec.
|
||||
func RunPrepareSteps(ctx context.Context, steps []types.PrepareStep, computer infra.Computer, assistantID, configHash string) error {
|
||||
// assistantDir is the absolute host path to the assistant source directory;
|
||||
// copy steps with a relative src resolve against it (host → workspace push).
|
||||
func RunPrepareSteps(ctx context.Context, steps []types.PrepareStep, computer infra.Computer, assistantID, configHash, assistantDir string) error {
|
||||
if len(steps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -52,7 +55,7 @@ func RunPrepareSteps(ctx context.Context, steps []types.PrepareStep, computer in
|
|||
case "file":
|
||||
err = runFileStep(ws, step)
|
||||
case "copy":
|
||||
err = runCopyStep(ws, step)
|
||||
err = runCopyStep(ws, step, assistantDir)
|
||||
case "exec":
|
||||
err = runExecStep(ctx, computer, step)
|
||||
case "process":
|
||||
|
|
@ -103,7 +106,14 @@ func runFileStep(ws workspace.FS, step types.PrepareStep) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func runCopyStep(ws workspace.FS, step types.PrepareStep) error {
|
||||
// runCopyStep copies files into the workspace using ws.Copy which supports
|
||||
// the "local:///" URI scheme for host-to-workspace transfers.
|
||||
//
|
||||
// src resolution:
|
||||
// - Already a host URI ("local:///..." or "tmp:///...") → used as-is
|
||||
// - Relative path + assistantDir provided → resolved to "local:///<assistantDir>/<src>"
|
||||
// - Relative path without assistantDir → treated as workspace-internal path
|
||||
func runCopyStep(ws workspace.FS, step types.PrepareStep, assistantDir string) error {
|
||||
if step.Src == "" || step.Dst == "" {
|
||||
return fmt.Errorf("copy step requires src and dst")
|
||||
}
|
||||
|
|
@ -111,24 +121,21 @@ func runCopyStep(ws workspace.FS, step types.PrepareStep) error {
|
|||
return fmt.Errorf("copy step requires workspace")
|
||||
}
|
||||
|
||||
data, err := ws.ReadFile(step.Src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read src %s: %w", step.Src, err)
|
||||
src := step.Src
|
||||
if !isHostURI(src) && assistantDir != "" {
|
||||
src = "local:///" + pathpkg.Join(assistantDir, src)
|
||||
}
|
||||
|
||||
dir := path.Dir(step.Dst)
|
||||
if dir != "." && dir != "/" {
|
||||
if err := ws.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("mkdir %s: %w", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := ws.WriteFile(step.Dst, data, 0644); err != nil {
|
||||
return fmt.Errorf("write dst %s: %w", step.Dst, err)
|
||||
if _, err := ws.Copy(src, step.Dst); err != nil {
|
||||
return fmt.Errorf("copy %s -> %s: %w", src, step.Dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isHostURI(s string) bool {
|
||||
return strings.HasPrefix(s, "local:///") || strings.HasPrefix(s, "tmp:///")
|
||||
}
|
||||
|
||||
func runExecStep(ctx context.Context, computer infra.Computer, step types.PrepareStep) error {
|
||||
if step.Cmd == "" {
|
||||
return fmt.Errorf("exec step requires cmd")
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func TestRunPrepareSteps_Exec(t *testing.T) {
|
|||
{Action: "exec", Cmd: "echo world >> /tmp/prep-test"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps: %v", err)
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ func TestRunPrepareSteps_File(t *testing.T) {
|
|||
{Action: "file", Path: "config/test.txt", Content: []byte("file-content-v2")},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps: %v", err)
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ func TestRunPrepareSteps_Copy(t *testing.T) {
|
|||
{Action: "copy", Src: "src.txt", Dst: "dst.txt"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps: %v", err)
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ func TestRunPrepareSteps_OnceMarker(t *testing.T) {
|
|||
hash := "abc123"
|
||||
assistantID := "test-once"
|
||||
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, box, assistantID, hash); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, box, assistantID, hash, ""); err != nil {
|
||||
t.Fatalf("first run: %v", err)
|
||||
}
|
||||
r1, _ := box.Exec(ctx, []string{"cat", counter})
|
||||
|
|
@ -151,7 +151,7 @@ func TestRunPrepareSteps_OnceMarker(t *testing.T) {
|
|||
t.Fatalf("first run: got %q, want %q", r1.Stdout, "x")
|
||||
}
|
||||
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, box, assistantID, hash); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, box, assistantID, hash, ""); err != nil {
|
||||
t.Fatalf("second run: %v", err)
|
||||
}
|
||||
r2, _ := box.Exec(ctx, []string{"cat", counter})
|
||||
|
|
@ -159,7 +159,7 @@ func TestRunPrepareSteps_OnceMarker(t *testing.T) {
|
|||
t.Errorf("second run: got %q, want %q (once step should be skipped)", r2.Stdout, "x")
|
||||
}
|
||||
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, box, assistantID, "new-hash"); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, box, assistantID, "new-hash", ""); err != nil {
|
||||
t.Fatalf("third run: %v", err)
|
||||
}
|
||||
r3, _ := box.Exec(ctx, []string{"cat", counter})
|
||||
|
|
@ -193,10 +193,10 @@ func TestRunPrepareSteps_OnceIsolation(t *testing.T) {
|
|||
|
||||
hash := "same-hash"
|
||||
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsA, box, "assistant-a", hash); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsA, box, "assistant-a", hash, ""); err != nil {
|
||||
t.Fatalf("assistant-a: %v", err)
|
||||
}
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsB, box, "assistant-b", hash); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsB, box, "assistant-b", hash, ""); err != nil {
|
||||
t.Fatalf("assistant-b: %v", err)
|
||||
}
|
||||
|
||||
|
|
@ -209,10 +209,10 @@ func TestRunPrepareSteps_OnceIsolation(t *testing.T) {
|
|||
t.Errorf("assistant-b: got %q, want %q", rB.Stdout, "B")
|
||||
}
|
||||
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsA, box, "assistant-a", hash); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsA, box, "assistant-a", hash, ""); err != nil {
|
||||
t.Fatalf("assistant-a re-run: %v", err)
|
||||
}
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsB, box, "assistant-b", hash); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, stepsB, box, "assistant-b", hash, ""); err != nil {
|
||||
t.Fatalf("assistant-b re-run: %v", err)
|
||||
}
|
||||
rA2, _ := box.Exec(ctx, []string{"cat", "/tmp/iso-a"})
|
||||
|
|
@ -244,7 +244,7 @@ func TestRunPrepareSteps_IgnoreError(t *testing.T) {
|
|||
{Action: "exec", Cmd: "echo survived > /tmp/survived"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps: %v (ignore_error should have prevented failure)", err)
|
||||
}
|
||||
|
|
@ -274,7 +274,7 @@ func TestRunPrepareSteps_FailOnError(t *testing.T) {
|
|||
{Action: "exec", Cmd: "echo should-not-reach > /tmp/unreachable"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "", "")
|
||||
if err == nil {
|
||||
t.Fatal("expected error from failing step without ignore_error")
|
||||
}
|
||||
|
|
@ -303,7 +303,7 @@ func TestRunPrepareSteps_UnknownAction(t *testing.T) {
|
|||
{Action: "unknown_action"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, nil, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, nil, "test-assistant", "", "")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown action")
|
||||
}
|
||||
|
|
@ -315,7 +315,7 @@ func TestRunPrepareSteps_UnknownAction(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRunPrepareSteps_EmptySteps(t *testing.T) {
|
||||
err := sandboxv2.RunPrepareSteps(context.Background(), nil, nil, "test-assistant", "hash")
|
||||
err := sandboxv2.RunPrepareSteps(context.Background(), nil, nil, "test-assistant", "hash", "")
|
||||
if err != nil {
|
||||
t.Fatalf("empty steps should succeed: %v", err)
|
||||
}
|
||||
|
|
@ -338,7 +338,7 @@ func TestRunPrepareSteps_Background(t *testing.T) {
|
|||
{Action: "exec", Cmd: "echo after-bg > /tmp/after-bg"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps: %v", err)
|
||||
}
|
||||
|
|
@ -371,7 +371,7 @@ func TestRunPrepareSteps_MixedActions(t *testing.T) {
|
|||
{Action: "copy", Src: "mixed.conf", Dst: "mixed-copy.conf"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, box, "test-assistant", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps: %v", err)
|
||||
}
|
||||
|
|
@ -425,7 +425,7 @@ func TestRunPrepareSteps_HostExec(t *testing.T) {
|
|||
{Action: "exec", Cmd: cmd},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, host, "test-host", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, host, "test-host", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps on host: %v", err)
|
||||
}
|
||||
|
|
@ -451,7 +451,7 @@ func TestRunPrepareSteps_HostExecFile(t *testing.T) {
|
|||
{Action: "file", Path: "host-test.txt", Content: []byte("host-file-data")},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, host, "test-host", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, host, "test-host", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps file: %v", err)
|
||||
}
|
||||
|
|
@ -489,7 +489,7 @@ func TestRunPrepareSteps_HostExecCopy(t *testing.T) {
|
|||
{Action: "copy", Src: "copy-src.txt", Dst: "copy-dst.txt"},
|
||||
}
|
||||
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, host, "test-host", "")
|
||||
err := sandboxv2.RunPrepareSteps(ctx, steps, host, "test-host", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("RunPrepareSteps copy: %v", err)
|
||||
}
|
||||
|
|
@ -532,7 +532,7 @@ func TestRunPrepareSteps_HostExecOnce(t *testing.T) {
|
|||
hash := "host-once-hash"
|
||||
aid := "host-once-aid"
|
||||
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, host, aid, hash); err != nil {
|
||||
if err := sandboxv2.RunPrepareSteps(ctx, steps, host, aid, hash, ""); err != nil {
|
||||
t.Fatalf("first run: %v", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,18 +28,21 @@ type MCPServer struct {
|
|||
}
|
||||
|
||||
// RunStepsFunc is the signature of RunPrepareSteps. Workspace is obtained
|
||||
// internally via computer.Workplace().
|
||||
type RunStepsFunc func(ctx context.Context, steps []PrepareStep, computer infra.Computer, assistantID, configHash string) error
|
||||
// internally via computer.Workplace(). assistantDir is the absolute path to
|
||||
// the assistant source directory on the host; copy steps resolve relative src
|
||||
// paths against it.
|
||||
type RunStepsFunc func(ctx context.Context, steps []PrepareStep, computer infra.Computer, assistantID, configHash, assistantDir string) error
|
||||
|
||||
// PrepareRequest carries everything needed by Runner.Prepare.
|
||||
type PrepareRequest struct {
|
||||
Computer infra.Computer
|
||||
Config *SandboxConfig
|
||||
Connector connector.Connector
|
||||
SkillsDir string
|
||||
MCPServers []MCPServer
|
||||
ConfigHash string
|
||||
RunSteps RunStepsFunc
|
||||
Computer infra.Computer
|
||||
Config *SandboxConfig
|
||||
Connector connector.Connector
|
||||
SkillsDir string
|
||||
AssistantDir string // absolute host path to the assistant source directory
|
||||
MCPServers []MCPServer
|
||||
ConfigHash string
|
||||
RunSteps RunStepsFunc
|
||||
}
|
||||
|
||||
// StreamRequest carries everything needed by Runner.Stream.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func (r *YaoRunner) Name() string { return "yao" }
|
|||
// no runner-specific steps. Connector is not required.
|
||||
func (r *YaoRunner) Prepare(ctx context.Context, req *types.PrepareRequest) error {
|
||||
if req.RunSteps != nil && len(req.Config.Prepare) > 0 {
|
||||
return req.RunSteps(ctx, req.Config.Prepare, req.Computer, req.Config.ID, req.ConfigHash)
|
||||
return req.RunSteps(ctx, req.Config.Prepare, req.Computer, req.Config.ID, req.ConfigHash, req.AssistantDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue