package runtimews import ( "fmt" "io" "os" "path/filepath" "strings" ) const envWorkspaceDir = "AGENT_WORKSPACE_DIR" func PrepareFromEnv() (string, error) { workspaceDir := strings.TrimSpace(os.Getenv(envWorkspaceDir)) if workspaceDir == "" { workspaceDir = filepath.Join(".", "workspace", "agent_runtime") } absWorkspace, err := filepath.Abs(workspaceDir) if err != nil { return "", err } if err := os.MkdirAll(absWorkspace, 0o755); err != nil { return "", err } if err := seedRuntimeWorkspace(absWorkspace); err != nil { return "", err } if err := os.Setenv(envWorkspaceDir, absWorkspace); err != nil { return "", err } _ = os.Setenv("CONFIG_ENV_FILE", filepath.Join(absWorkspace, "configs", "env")) return absWorkspace, nil } func seedRuntimeWorkspace(workspaceRoot string) error { seedDirs := []string{"configs", "data", "bot_context", "skills"} for _, name := range seedDirs { src := filepath.Join(".", name) dst := filepath.Join(workspaceRoot, name) if err := copyDirIfMissing(src, dst); err != nil { return fmt.Errorf("seed %s failed: %w", name, err) } } if err := os.MkdirAll(filepath.Join(workspaceRoot, "workspace"), 0o755); err != nil { return err } return nil } func copyDirIfMissing(src, dst string) error { info, err := os.Stat(src) if err != nil { if os.IsNotExist(err) { return nil } return err } if !info.IsDir() { return nil } if err := os.MkdirAll(dst, 0o755); err != nil { return err } return filepath.WalkDir(src, func(path string, d os.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } rel, err := filepath.Rel(src, path) if err != nil { return err } target := filepath.Join(dst, rel) if d.IsDir() { return os.MkdirAll(target, 0o755) } if _, err := os.Stat(target); err == nil { return nil } if err := copyFile(path, target); err != nil { return err } return nil }) } func copyFile(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err } out, err := os.Create(dst) if err != nil { return err } defer out.Close() if _, err := io.Copy(out, in); err != nil { return err } return out.Sync() }