agent/pkg/agentic/actions.go
Virgil 6aebdc07b6 feat(agentic): expose epic command aliases
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 00:01:23 +00:00

836 lines
24 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
// c.Action("agentic.dispatch").Run(ctx, options)
// c.Actions()
package agentic
import (
"context"
"dappco.re/go/agent/pkg/lib"
"dappco.re/go/agent/pkg/messages"
core "dappco.re/go/core"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// result := c.Action("agentic.dispatch").Run(ctx, core.NewOptions(
//
// core.Option{Key: "repo", Value: "go-io"},
// core.Option{Key: "task", Value: "Fix tests"},
//
// ))
func (s *PrepSubsystem) handleDispatch(ctx context.Context, options core.Options) core.Result {
if s.Core() != nil {
entitlement := s.Core().Entitled("agentic.concurrency", 1)
if !entitlement.Allowed {
reason := core.Trim(entitlement.Reason)
if reason == "" {
reason = "dispatch concurrency not available"
}
return core.Result{Value: core.E("agentic.dispatch", reason, nil), OK: false}
}
}
input := dispatchInputFromOptions(options)
_, out, err := s.dispatch(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
if s.Core() != nil {
s.Core().RecordUsage("agentic.dispatch")
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.prep").Run(ctx, core.NewOptions(
//
// core.Option{Key: "repo", Value: "go-io"},
// core.Option{Key: "issue", Value: 42},
//
// ))
func (s *PrepSubsystem) handlePrep(ctx context.Context, options core.Options) core.Result {
input := prepInputFromOptions(options)
_, out, err := s.prepWorkspace(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.status").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleStatus(ctx context.Context, options core.Options) core.Result {
input := StatusInput{
Workspace: options.String("workspace"),
Limit: options.Int("limit"),
Status: options.String("status"),
}
_, out, err := s.status(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.resume").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
//
// ))
func (s *PrepSubsystem) handleResume(ctx context.Context, options core.Options) core.Result {
input := resumeInputFromOptions(options)
_, out, err := s.resume(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.scan").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleScan(ctx context.Context, options core.Options) core.Result {
input := scanInputFromOptions(options)
_, out, err := s.scan(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.watch").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
//
// ))
func (s *PrepSubsystem) handleWatch(ctx context.Context, options core.Options) core.Result {
input := watchInputFromOptions(options)
_, out, err := s.watch(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
//
// core.Option{Key: "slug", Value: "coding"},
//
// ))
func (s *PrepSubsystem) handlePrompt(_ context.Context, options core.Options) core.Result {
return lib.Prompt(options.String("slug"))
}
// result := c.Action("agentic.task").Run(ctx, core.NewOptions(
//
// core.Option{Key: "slug", Value: "bug-fix"},
//
// ))
func (s *PrepSubsystem) handleTask(_ context.Context, options core.Options) core.Result {
return lib.Task(options.String("slug"))
}
// result := c.Action("agentic.flow").Run(ctx, core.NewOptions(
//
// core.Option{Key: "slug", Value: "go"},
//
// ))
func (s *PrepSubsystem) handleFlow(_ context.Context, options core.Options) core.Result {
return lib.Flow(options.String("slug"))
}
// result := c.Action("agentic.persona").Run(ctx, core.NewOptions(
//
// core.Option{Key: "path", Value: "code/backend-architect"},
//
// ))
func (s *PrepSubsystem) handlePersona(_ context.Context, options core.Options) core.Result {
return lib.Persona(options.String("path"))
}
// result := c.Action("agentic.complete").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "/srv/.core/workspace/core/go-io/task-42"},
//
// ))
func (s *PrepSubsystem) handleComplete(ctx context.Context, options core.Options) core.Result {
return s.Core().Task("agent.completion").Run(ctx, s.Core(), options)
}
// input := agentic.CompleteInput{Workspace: "/srv/.core/workspace/core/go-io/task-42"}
type CompleteInput struct {
Workspace string `json:"workspace"`
}
// out := agentic.CompleteOutput{Success: true, Workspace: "core/go-io/task-42"}
type CompleteOutput struct {
Success bool `json:"success"`
Workspace string `json:"workspace"`
}
func (s *PrepSubsystem) completeTool(ctx context.Context, _ *mcp.CallToolRequest, input CompleteInput) (*mcp.CallToolResult, CompleteOutput, error) {
if input.Workspace == "" {
return nil, CompleteOutput{}, core.E("agentic.complete", "workspace is required", nil)
}
result := s.handleComplete(ctx, core.NewOptions(core.Option{Key: "workspace", Value: input.Workspace}))
if !result.OK {
return nil, CompleteOutput{}, resultErrorValue("agentic.complete", result)
}
return nil, CompleteOutput{
Success: true,
Workspace: input.Workspace,
}, nil
}
// result := c.Action("agentic.qa").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
//
// ))
func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core.Result {
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-qa") {
return core.Result{Value: true, OK: true}
}
workspaceDir := options.String("workspace")
if workspaceDir == "" {
return core.Result{Value: core.E("agentic.qa", "workspace is required", nil), OK: false}
}
passed := s.runQA(workspaceDir)
if !passed {
if result := ReadStatusResult(workspaceDir); result.OK {
workspaceStatus, ok := workspaceStatusValue(result)
if ok {
workspaceStatus.Status = "failed"
workspaceStatus.Question = "QA check failed — build or tests did not pass"
writeStatusResult(workspaceDir, workspaceStatus)
}
}
}
if s.ServiceRuntime != nil {
result := ReadStatusResult(workspaceDir)
workspaceStatus, ok := workspaceStatusValue(result)
repo := ""
if ok {
repo = workspaceStatus.Repo
}
s.Core().ACTION(messages.QAResult{
Workspace: WorkspaceName(workspaceDir),
Repo: repo,
Passed: passed,
})
}
return core.Result{Value: passed, OK: passed}
}
// result := c.Action("agentic.auto-pr").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
//
// ))
func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options) core.Result {
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-pr") {
return core.Result{OK: true}
}
workspaceDir := options.String("workspace")
if workspaceDir == "" {
return core.Result{Value: core.E("agentic.auto-pr", "workspace is required", nil), OK: false}
}
s.autoCreatePR(workspaceDir)
if s.ServiceRuntime != nil {
result := ReadStatusResult(workspaceDir)
workspaceStatus, ok := workspaceStatusValue(result)
if ok && workspaceStatus.PRURL != "" {
s.Core().ACTION(messages.PRCreated{
Repo: workspaceStatus.Repo,
Branch: workspaceStatus.Branch,
PRURL: workspaceStatus.PRURL,
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
})
}
}
return core.Result{OK: true}
}
// result := c.Action("agentic.verify").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
//
// ))
func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options) core.Result {
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-merge") {
return core.Result{OK: true}
}
workspaceDir := options.String("workspace")
if workspaceDir == "" {
return core.Result{Value: core.E("agentic.verify", "workspace is required", nil), OK: false}
}
s.autoVerifyAndMerge(workspaceDir)
if s.ServiceRuntime != nil {
result := ReadStatusResult(workspaceDir)
workspaceStatus, ok := workspaceStatusValue(result)
if ok {
if workspaceStatus.Status == "merged" {
s.Core().ACTION(messages.PRMerged{
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
})
} else if workspaceStatus.Question != "" {
s.Core().ACTION(messages.PRNeedsReview{
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
Reason: workspaceStatus.Question,
})
}
}
}
return core.Result{OK: true}
}
// result := c.Action("agentic.ingest").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
//
// ))
func (s *PrepSubsystem) handleIngest(ctx context.Context, options core.Options) core.Result {
workspaceDir := options.String("workspace")
if workspaceDir == "" {
return core.Result{Value: core.E("agentic.ingest", "workspace is required", nil), OK: false}
}
s.ingestFindings(workspaceDir)
return core.Result{OK: true}
}
// result := c.Action("agentic.poke").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handlePoke(ctx context.Context, _ core.Options) core.Result {
if s.ServiceRuntime != nil && s.Core().Action("runner.poke").Exists() {
return s.Core().Action("runner.poke").Run(ctx, core.NewOptions())
}
s.Poke()
return core.Result{OK: true}
}
// result := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
//
// core.Option{Key: "repo", Value: "go-io"},
//
// ))
func (s *PrepSubsystem) handleMirror(ctx context.Context, options core.Options) core.Result {
input := mirrorInputFromOptions(options)
_, out, err := s.mirror(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.issue.get").Run(ctx, core.NewOptions(
//
// core.Option{Key: "repo", Value: "go-io"},
// core.Option{Key: "number", Value: "42"},
//
// ))
func (s *PrepSubsystem) handleIssueGet(ctx context.Context, options core.Options) core.Result {
return s.cmdIssueGet(normaliseForgeActionOptions(options))
}
// result := c.Action("agentic.issue.list").Run(ctx, core.NewOptions(
//
// core.Option{Key: "_arg", Value: "go-io"},
//
// ))
func (s *PrepSubsystem) handleIssueList(ctx context.Context, options core.Options) core.Result {
return s.cmdIssueList(normaliseForgeActionOptions(options))
}
// result := c.Action("agentic.issue.create").Run(ctx, core.NewOptions(
//
// core.Option{Key: "_arg", Value: "go-io"},
// core.Option{Key: "title", Value: "Bug report"},
//
// ))
func (s *PrepSubsystem) handleIssueCreate(ctx context.Context, options core.Options) core.Result {
return s.cmdIssueCreate(normaliseForgeActionOptions(options))
}
// result := c.Action("agentic.pr.get").Run(ctx, core.NewOptions(
//
// core.Option{Key: "_arg", Value: "go-io"},
// core.Option{Key: "number", Value: "12"},
//
// ))
func (s *PrepSubsystem) handlePRGet(ctx context.Context, options core.Options) core.Result {
return s.cmdPRGet(normaliseForgeActionOptions(options))
}
// result := c.Action("agentic.pr.list").Run(ctx, core.NewOptions(
//
// core.Option{Key: "_arg", Value: "go-io"},
//
// ))
func (s *PrepSubsystem) handlePRList(ctx context.Context, options core.Options) core.Result {
return s.cmdPRList(normaliseForgeActionOptions(options))
}
// result := c.Action("agentic.pr.merge").Run(ctx, core.NewOptions(
//
// core.Option{Key: "_arg", Value: "go-io"},
// core.Option{Key: "number", Value: "12"},
//
// ))
func (s *PrepSubsystem) handlePRMerge(ctx context.Context, options core.Options) core.Result {
return s.cmdPRMerge(normaliseForgeActionOptions(options))
}
// result := c.Action("agentic.pr.close").Run(ctx, core.NewOptions(
//
// core.Option{Key: "_arg", Value: "go-io"},
// core.Option{Key: "number", Value: "12"},
//
// ))
func (s *PrepSubsystem) handlePRClose(ctx context.Context, options core.Options) core.Result {
return s.cmdPRClose(normaliseForgeActionOptions(options))
}
// result := c.Action("agentic.review-queue").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
//
// ))
func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, options core.Options) core.Result {
input := reviewQueueInputFromOptions(options)
_, out, err := s.reviewQueue(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Action("agentic.epic").Run(ctx, core.NewOptions(
//
// core.Option{Key: "task", Value: "Update all repos to v0.8.0"},
//
// ))
func (s *PrepSubsystem) handleEpic(ctx context.Context, options core.Options) core.Result {
input := epicInputFromOptions(options)
_, out, err := s.createEpic(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: out, OK: true}
}
// result := c.Command("epic").Run(core.NewOptions(
//
// core.Option{Key: "repo", Value: "go-io"},
// core.Option{Key: "title", Value: "Stabilise agent dispatch"},
// core.Option{Key: "tasks", Value: []string{"Fix the queue race", "Add regression tests"}},
//
// ))
func (s *PrepSubsystem) cmdEpic(options core.Options) core.Result {
return s.handleEpic(s.commandContext(), options)
}
func dispatchInputFromOptions(options core.Options) DispatchInput {
return DispatchInput{
Repo: optionStringValue(options, "repo"),
Org: optionStringValue(options, "org"),
Task: optionStringValue(options, "task"),
Agent: optionStringValue(options, "agent"),
Template: optionStringValue(options, "template"),
PlanTemplate: optionStringValue(options, "plan_template", "plan-template"),
Variables: optionStringMapValue(options, "variables"),
Persona: optionStringValue(options, "persona"),
Issue: optionIntValue(options, "issue"),
PR: optionIntValue(options, "pr"),
Branch: optionStringValue(options, "branch"),
Tag: optionStringValue(options, "tag"),
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
}
}
func prepInputFromOptions(options core.Options) PrepInput {
return PrepInput{
Repo: optionStringValue(options, "repo"),
Org: optionStringValue(options, "org"),
Task: optionStringValue(options, "task"),
Agent: optionStringValue(options, "agent"),
Issue: optionIntValue(options, "issue"),
PR: optionIntValue(options, "pr"),
Branch: optionStringValue(options, "branch"),
Tag: optionStringValue(options, "tag"),
Template: optionStringValue(options, "template"),
PlanTemplate: optionStringValue(options, "plan_template", "plan-template"),
Variables: optionStringMapValue(options, "variables"),
Persona: optionStringValue(options, "persona"),
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
}
}
func resumeInputFromOptions(options core.Options) ResumeInput {
return ResumeInput{
Workspace: optionStringValue(options, "workspace"),
Answer: optionStringValue(options, "answer"),
Agent: optionStringValue(options, "agent"),
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
}
}
func scanInputFromOptions(options core.Options) ScanInput {
return ScanInput{
Org: optionStringValue(options, "org"),
Labels: optionStringSliceValue(options, "labels"),
Limit: optionIntValue(options, "limit"),
}
}
func watchInputFromOptions(options core.Options) WatchInput {
workspaces := optionStringSliceValue(options, "workspaces")
if len(workspaces) == 0 {
if workspace := optionStringValue(options, "workspace"); workspace != "" {
workspaces = []string{workspace}
}
}
return WatchInput{
Workspaces: workspaces,
PollInterval: optionIntValue(options, "poll_interval", "poll-interval"),
Timeout: optionIntValue(options, "timeout"),
}
}
func mirrorInputFromOptions(options core.Options) MirrorInput {
return MirrorInput{
Repo: optionStringValue(options, "repo"),
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
MaxFiles: optionIntValue(options, "max_files", "max-files"),
}
}
func reviewQueueInputFromOptions(options core.Options) ReviewQueueInput {
return ReviewQueueInput{
Limit: optionIntValue(options, "limit"),
Reviewer: optionStringValue(options, "reviewer"),
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
LocalOnly: optionBoolValue(options, "local_only", "local-only"),
}
}
func epicInputFromOptions(options core.Options) EpicInput {
return EpicInput{
Repo: optionStringValue(options, "repo"),
Org: optionStringValue(options, "org"),
Title: optionStringValue(options, "title"),
Body: optionStringValue(options, "body"),
Tasks: optionStringSliceValue(options, "tasks"),
Labels: optionStringSliceValue(options, "labels"),
Dispatch: optionBoolValue(options, "dispatch"),
Agent: optionStringValue(options, "agent"),
Template: optionStringValue(options, "template"),
}
}
func normaliseForgeActionOptions(options core.Options) core.Options {
normalised := core.NewOptions(options.Items()...)
if normalised.String("_arg") == "" {
if repo := optionStringValue(options, "repo"); repo != "" {
normalised.Set("_arg", repo)
}
}
if number := optionStringValue(options, "number"); number != "" {
normalised.Set("number", number)
}
return normalised
}
func optionStringValue(options core.Options, keys ...string) string {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
if value := stringValue(result.Value); value != "" {
return value
}
}
return ""
}
func optionIntValue(options core.Options, keys ...string) int {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
switch value := result.Value.(type) {
case int:
return value
case int64:
return int(value)
case float64:
return int(value)
case string:
parsed := parseInt(value)
if parsed != 0 || core.Trim(value) == "0" {
return parsed
}
return parseIntString(value)
}
}
return 0
}
func optionBoolValue(options core.Options, keys ...string) bool {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
switch value := result.Value.(type) {
case bool:
return value
case string:
switch core.Lower(core.Trim(value)) {
case "1", "true", "yes", "on":
return true
}
}
}
return false
}
func optionStringSliceValue(options core.Options, keys ...string) []string {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
values := stringSliceValue(result.Value)
if len(values) > 0 {
return values
}
}
return nil
}
func optionStringMapValue(options core.Options, keys ...string) map[string]string {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
values := stringMapValue(result.Value)
if len(values) > 0 {
return values
}
}
return nil
}
func optionAnyValue(options core.Options, keys ...string) any {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
return normaliseOptionValue(result.Value)
}
return nil
}
func stringValue(value any) string {
switch typed := value.(type) {
case string:
return typed
case int:
return core.Sprint(typed)
case int64:
return core.Sprint(typed)
case float64:
return core.Sprint(int(typed))
case bool:
return core.Sprint(typed)
}
return ""
}
func stringSliceValue(value any) []string {
switch typed := value.(type) {
case []string:
return cleanStrings(typed)
case []any:
var values []string
for _, item := range typed {
if text := stringValue(item); text != "" {
values = append(values, text)
}
}
return cleanStrings(values)
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return nil
}
if core.HasPrefix(trimmed, "[") {
var values []string
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return cleanStrings(values)
}
var generic []any
if result := core.JSONUnmarshalString(trimmed, &generic); result.OK {
return stringSliceValue(generic)
}
}
return cleanStrings(core.Split(trimmed, ","))
default:
if text := stringValue(value); text != "" {
return []string{text}
}
}
return nil
}
func normaliseOptionValue(value any) any {
switch typed := value.(type) {
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return ""
}
if core.HasPrefix(trimmed, "{") {
var values map[string]any
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return values
}
}
if core.HasPrefix(trimmed, "[") {
var values []any
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return values
}
}
switch core.Lower(trimmed) {
case "true":
return true
case "false":
return false
}
if parsed := parseInt(trimmed); parsed != 0 || trimmed == "0" {
return parsed
}
return typed
default:
return value
}
}
func stringMapValue(value any) map[string]string {
switch typed := value.(type) {
case map[string]string:
out := make(map[string]string, len(typed))
for key, val := range typed {
if text := core.Trim(val); text != "" {
out[key] = text
}
}
return out
case map[string]any:
out := make(map[string]string, len(typed))
for key, val := range typed {
if text := stringValue(val); text != "" {
out[key] = text
}
}
return out
case []string:
out := make(map[string]string, len(typed))
for _, item := range typed {
mergeStringMapEntry(out, item)
}
return out
case []any:
out := make(map[string]string, len(typed))
for _, item := range typed {
mergeStringMapEntry(out, stringValue(item))
}
return out
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return nil
}
if core.HasPrefix(trimmed, "{") {
var values map[string]string
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return stringMapValue(values)
}
var generic map[string]any
if result := core.JSONUnmarshalString(trimmed, &generic); result.OK {
return stringMapValue(generic)
}
}
out := make(map[string]string)
for _, pair := range core.Split(trimmed, ",") {
mergeStringMapEntry(out, pair)
}
if len(out) > 0 {
return out
}
}
return nil
}
func mergeStringMapEntry(values map[string]string, entry string) {
trimmed := core.Trim(entry)
if trimmed == "" {
return
}
parts := core.SplitN(trimmed, "=", 2)
if len(parts) != 2 {
return
}
key := core.Trim(parts[0])
value := core.Trim(parts[1])
if key == "" || value == "" {
return
}
values[key] = value
}
func cleanStrings(values []string) []string {
var cleaned []string
for _, value := range values {
trimmed := core.Trim(value)
if trimmed != "" {
cleaned = append(cleaned, trimmed)
}
}
return cleaned
}
// result := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-42"})
// result := c.QUERY(agentic.WorkspaceQuery{Status: "blocked"})
func (s *PrepSubsystem) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Result {
workspaceQuery, ok := query.(WorkspaceQuery)
if !ok {
return core.Result{}
}
if workspaceQuery.Name != "" {
return s.workspaces.Get(workspaceQuery.Name)
}
if workspaceQuery.Status != "" {
var names []string
s.workspaces.Each(func(name string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status == workspaceQuery.Status {
names = append(names, name)
}
})
return core.Result{Value: names, OK: true}
}
return core.Result{Value: s.workspaces, OK: true}
}