package skills import ( "context" "fmt" "log/slog" "strings" "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry" "git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk" ) // Workflow is a Skill expanded into its executable form: the base system prompt // plus the ordered steps, references, and guardrails, ready to be handed to the // loop/agent executor (M2). The Runner only assembles workflows; it does not // execute commands. type Workflow struct { Name string Description string Prompt string Steps []sdk.Step Refs []sdk.Ref Guardrails []sdk.Guardrail } // Runner expands skills into workflows. It is the M5 placeholder for the // skill-execution wrapper: expansion (the real deliverable) is wired now, while // Run awaits M12/loop integration. type Runner struct { reg *registry.Registry log *slog.Logger } // NewRunner builds a Runner over the given registry, used by ExpandByName to // look up registered skills. log may be nil. func NewRunner(reg *registry.Registry, log *slog.Logger) *Runner { return &Runner{reg: reg, log: sourcedLog(log)} } // Expand converts s into a Workflow, copying the step, ref, and guardrail slices // so the caller owns the result. It returns ErrInvalidSkill when s is nil or has // a blank name. func (r *Runner) Expand(ctx context.Context, s sdk.Skill) (Workflow, error) { if err := ctx.Err(); err != nil { return Workflow{}, fmt.Errorf("skills: runner: expand: %w", err) } if s == nil || strings.TrimSpace(s.Name()) == "" { return Workflow{}, fmt.Errorf("skills: runner: expand: %w", ErrInvalidSkill) } wf := Workflow{ Name: s.Name(), Description: s.Description(), Prompt: s.Prompt(), Steps: cloneSteps(s.Steps()), Refs: cloneRefs(s.Refs()), Guardrails: cloneGuardrails(s.Guardrails()), } r.log.Info("skill expanded", "skill", wf.Name) return wf, nil } // ExpandByName looks up a skill in the registry by name and expands it. It // returns ErrNilRegistrar when the runner has no registry, the registry's error // (registry.ErrNotFound) when the name is absent, and ErrNotFound when the // registered value is not a Skill. func (r *Runner) ExpandByName(ctx context.Context, name string) (Workflow, error) { if r.reg == nil { return Workflow{}, fmt.Errorf("skills: runner: expand %q: %w", name, ErrNilRegistrar) } cap, err := r.reg.Get(name) if err != nil { return Workflow{}, fmt.Errorf("skills: runner: expand %q: %w", name, err) } skill, ok := cap.Value.(sdk.Skill) if !ok { return Workflow{}, fmt.Errorf("skills: runner: expand %q: %w", name, ErrNotFound) } return r.Expand(ctx, skill) } // Run is a placeholder for executing a registered skill end to end. Execution // lands in the loop/agent executor (M2) and the full wiring (M12); it always // returns ErrRunPending. func (r *Runner) Run(_ context.Context, _ string, _ any) (any, error) { return nil, ErrRunPending }