- Introduce new MCP and Robot command groups in the CLI, allowing for better organization of package management commands. - Add corresponding command descriptions for MCP and Robot functionalities to improve user guidance. - Update the agent command group to include additional commands for enhanced agent management. - Modify .gitignore to exclude specific design markdown files from version control. - Add environment variable for test application path in GitHub workflows to streamline testing setup.
157 lines
4.3 KiB
Go
157 lines
4.3 KiB
Go
package robot
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
agentmgr "github.com/yaoapp/yao/registry/manager/agent"
|
|
"github.com/yaoapp/yao/registry/manager/common"
|
|
mcpmgr "github.com/yaoapp/yao/registry/manager/mcp"
|
|
)
|
|
|
|
// AddOptions configures the Add operation.
|
|
type AddOptions struct {
|
|
Version string
|
|
TeamID string // required: which team to add the robot to
|
|
}
|
|
|
|
// Add installs a robot package from the registry.
|
|
// Per DESIGN-ROBOT.md:
|
|
// 1. Pull .yao.zip
|
|
// 2. Parse robot.json + pkg.yao
|
|
// 3. Install dependencies (assistants + MCPs)
|
|
// 4. Write member DB record (deferred to CLI layer which has DB access)
|
|
// 5. Write registry.yao
|
|
func (m *Manager) Add(pkgID string, opts AddOptions) (*RobotJSON, error) {
|
|
if opts.Version == "" {
|
|
opts.Version = "latest"
|
|
}
|
|
if opts.TeamID == "" {
|
|
return nil, fmt.Errorf("--team is required for robot add")
|
|
}
|
|
|
|
scope, name, err := common.ParsePackageID(pkgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lf, err := common.LoadLockfile(m.appRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
regType := common.TypeToRegistryType(common.TypeRobot)
|
|
zipData, digest, err := m.client.Pull(regType, "@"+scope, name, opts.Version)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("pull %s: %w", pkgID, err)
|
|
}
|
|
|
|
manifest, err := common.ReadManifest(zipData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read manifest: %w", err)
|
|
}
|
|
|
|
// Read robot.json from zip
|
|
robotData, err := common.ExtractFile(zipData, "robot.json")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("extract robot.json: %w", err)
|
|
}
|
|
|
|
var robot RobotJSON
|
|
if err := json.Unmarshal(robotData, &robot); err != nil {
|
|
return nil, fmt.Errorf("parse robot.json: %w", err)
|
|
}
|
|
|
|
// Analyze dependencies from robot configuration
|
|
analyzedDeps := AnalyzeDeps(&robot)
|
|
|
|
// Merge with pkg.yao declared dependencies
|
|
allDeps := map[string]string{}
|
|
for _, dep := range analyzedDeps {
|
|
allDeps[dep.PackageID] = "*"
|
|
}
|
|
for depID, ver := range manifest.Dependencies {
|
|
allDeps[depID] = ver
|
|
}
|
|
|
|
// Install dependencies
|
|
if len(allDeps) > 0 {
|
|
missing, _, _ := common.CheckDependencies(allDeps, lf)
|
|
if len(missing) > 0 {
|
|
var summary string
|
|
for _, dep := range missing {
|
|
summary += fmt.Sprintf(" %s %s\n", dep.PackageID, dep.RequiredVersion)
|
|
}
|
|
if !m.prompter.Confirm(fmt.Sprintf("The following dependencies need to be installed:\n%sInstall?", summary)) {
|
|
return nil, fmt.Errorf("dependency installation declined, aborting")
|
|
}
|
|
|
|
for _, dep := range missing {
|
|
depScope, depName, err := common.ParsePackageID(dep.PackageID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Reload lockfile before each dep install — a previous dep may
|
|
// have recursively installed this one already.
|
|
freshLF, _ := common.LoadLockfile(m.appRoot)
|
|
if _, already := freshLF.GetPackage(dep.PackageID); already {
|
|
fmt.Printf(" ✓ Dependency %s already installed (transitive)\n", dep.PackageID)
|
|
continue
|
|
}
|
|
|
|
depType := depTypeFor(dep.PackageID, analyzedDeps)
|
|
|
|
switch depType {
|
|
case "mcp":
|
|
err = m.mcpMgr.Add(dep.PackageID, mcpmgr.AddOptions{})
|
|
default:
|
|
err = m.agentMgr.Add(dep.PackageID, agentmgr.AddOptions{})
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to install dependency %s (%s/%s): %w", dep.PackageID, depScope, depName, err)
|
|
}
|
|
fmt.Printf(" ✓ Dependency %s installed\n", dep.PackageID)
|
|
}
|
|
|
|
// Reload lockfile after dependency installation
|
|
lf, err = common.LoadLockfile(m.appRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write to registry.yao (member record writing is done by CLI layer)
|
|
info := common.PackageInfo{
|
|
Type: common.TypeRobot,
|
|
Version: manifest.Version,
|
|
Integrity: digest,
|
|
Dependencies: allDeps,
|
|
TeamID: opts.TeamID,
|
|
}
|
|
lf.SetPackage(pkgID, info)
|
|
|
|
// Add required_by references
|
|
for depID := range allDeps {
|
|
lf.AddRequiredBy(depID, pkgID)
|
|
}
|
|
|
|
if err := common.SaveLockfile(m.appRoot, lf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fmt.Printf("✓ Robot %s@%s installed (dependencies ready, team: %s)\n", pkgID, manifest.Version, opts.TeamID)
|
|
fmt.Printf(" The member record needs to be created in the database.\n")
|
|
return &robot, nil
|
|
}
|
|
|
|
// depTypeFor finds the type of a dependency from the analyzed deps list.
|
|
func depTypeFor(pkgID string, analyzedDeps []RobotDep) string {
|
|
for _, d := range analyzedDeps {
|
|
if d.PackageID == pkgID {
|
|
return d.Type
|
|
}
|
|
}
|
|
return "assistant"
|
|
}
|