fix(ax): continue AX comment cleanup
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
ce7c81a15b
commit
40a26ca28c
18 changed files with 152 additions and 393 deletions
|
|
@ -14,10 +14,8 @@ type applicationCommandSet struct {
|
|||
core *core.Core
|
||||
}
|
||||
|
||||
// startupArgs applies early log flags, then returns args for c.Cli().Run().
|
||||
//
|
||||
// args := startupArgs()
|
||||
// _ = c.Cli().Run(args...)
|
||||
// args := startupArgs()
|
||||
// _ = c.Cli().Run(args...)
|
||||
func startupArgs() []string {
|
||||
previous := flag.CommandLine
|
||||
commandLine := flag.NewFlagSet("core-agent", flag.ContinueOnError)
|
||||
|
|
@ -48,10 +46,8 @@ func startupArgs() []string {
|
|||
return applyLogLevel(commandLine.Args())
|
||||
}
|
||||
|
||||
// applyLogLevel strips log-level flags from args and applies the level in-order.
|
||||
//
|
||||
// args := applyLogLevel([]string{"version", "-q"})
|
||||
// args := applyLogLevel([]string{"--debug", "mcp"})
|
||||
// args := applyLogLevel([]string{"version", "-q"})
|
||||
// args := applyLogLevel([]string{"--debug", "mcp"})
|
||||
func applyLogLevel(args []string) []string {
|
||||
var cleaned []string
|
||||
for _, arg := range args {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ import (
|
|||
"forge.lthn.ai/core/mcp/pkg/mcp"
|
||||
)
|
||||
|
||||
// registerMCPService builds the MCP service from registered AX subsystems.
|
||||
//
|
||||
// c := core.New(core.WithService(registerMCPService))
|
||||
// _, ok := core.ServiceFor[*mcp.Service](c, "mcp")
|
||||
// c := core.New(core.WithService(registerMCPService))
|
||||
// _, ok := core.ServiceFor[*mcp.Service](c, "mcp")
|
||||
func registerMCPService(c *core.Core) core.Result {
|
||||
if c == nil {
|
||||
return core.Result{Value: core.E("main.registerMCPService", "core is required", nil), OK: false}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ package main
|
|||
|
||||
import agentpkg "dappco.re/go/agent"
|
||||
|
||||
// updateChannel maps the build version to the release channel.
|
||||
//
|
||||
// agentpkg.Version = "0.15.0"
|
||||
// updateChannel() // "stable"
|
||||
// agentpkg.Version = "0.15.0"
|
||||
// updateChannel() // "stable"
|
||||
func updateChannel() string {
|
||||
switch {
|
||||
case agentpkg.Version == "" || agentpkg.Version == "dev":
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// Package agentic provides MCP tools for agent orchestration.
|
||||
// Prepares workspaces and dispatches subagents.
|
||||
// core.New(core.WithService(agentic.Register))
|
||||
package agentic
|
||||
|
||||
import (
|
||||
|
|
@ -18,15 +17,10 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// AgentOptions configures the agentic service.
|
||||
//
|
||||
// options := agentic.AgentOptions{}
|
||||
// options := agentic.AgentOptions{}
|
||||
type AgentOptions struct{}
|
||||
|
||||
// PrepSubsystem provides agentic MCP tools for workspace orchestration.
|
||||
// Agent lifecycle events are broadcast via s.Core().ACTION(messages.AgentCompleted{}).
|
||||
//
|
||||
// core.New(core.WithService(agentic.Register))
|
||||
// core.New(core.WithService(agentic.Register))
|
||||
type PrepSubsystem struct {
|
||||
*core.ServiceRuntime[AgentOptions]
|
||||
forge *forge.Forge
|
||||
|
|
@ -47,10 +41,8 @@ type PrepSubsystem struct {
|
|||
|
||||
var _ coremcp.Subsystem = (*PrepSubsystem)(nil)
|
||||
|
||||
// NewPrep creates an agentic subsystem.
|
||||
//
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.SetCompletionNotifier(monitor)
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.SetCompletionNotifier(monitor)
|
||||
func NewPrep() *PrepSubsystem {
|
||||
home := HomeDir()
|
||||
|
||||
|
|
@ -81,18 +73,13 @@ func NewPrep() *PrepSubsystem {
|
|||
}
|
||||
}
|
||||
|
||||
// Use core.New(core.WithService(agentic.Register)) for new code.
|
||||
//
|
||||
// prep.SetCore(c)
|
||||
// prep.SetCore(c)
|
||||
func (s *PrepSubsystem) SetCore(c *core.Core) {
|
||||
s.ServiceRuntime = core.NewServiceRuntime(c, AgentOptions{})
|
||||
}
|
||||
|
||||
// OnStartup implements core.Startable — registers named Actions, starts the queue runner,
|
||||
// and registers CLI commands. The Action registry IS the capability map.
|
||||
//
|
||||
// c.Action("agentic.dispatch").Run(ctx, options)
|
||||
// c.Actions() // ["agentic.dispatch", "agentic.prep", "agentic.status", ...]
|
||||
// c.Action("agentic.dispatch").Run(ctx, options)
|
||||
// c.Actions() // ["agentic.dispatch", "agentic.prep", "agentic.status", ...]
|
||||
func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
|
||||
c := s.Core()
|
||||
|
||||
|
|
@ -214,10 +201,8 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
|
|||
|
||||
// registerCommands is in commands.go
|
||||
|
||||
// OnShutdown implements core.Stoppable and freezes the queue.
|
||||
//
|
||||
// subsystem := agentic.NewPrep()
|
||||
// _ = subsystem.OnShutdown(context.Background())
|
||||
// subsystem := agentic.NewPrep()
|
||||
// _ = subsystem.OnShutdown(context.Background())
|
||||
func (s *PrepSubsystem) OnShutdown(ctx context.Context) core.Result {
|
||||
s.frozen = true
|
||||
return core.Result{OK: true}
|
||||
|
|
@ -243,20 +228,16 @@ func (s *PrepSubsystem) hydrateWorkspaces() {
|
|||
}
|
||||
}
|
||||
|
||||
// TrackWorkspace registers or updates a workspace in the in-memory Registry.
|
||||
//
|
||||
// s.TrackWorkspace("core/go-io/task-5", st)
|
||||
// s.TrackWorkspace("core/go-io/task-5", st)
|
||||
func (s *PrepSubsystem) TrackWorkspace(name string, st *WorkspaceStatus) {
|
||||
if s.workspaces != nil {
|
||||
s.workspaces.Set(name, st)
|
||||
}
|
||||
}
|
||||
|
||||
// Workspaces returns the workspace Registry for cross-cutting queries.
|
||||
//
|
||||
// s.Workspaces().Names() // all workspace names
|
||||
// s.Workspaces().List("core/*") // org-scoped workspaces
|
||||
// s.Workspaces().Each(func(name string, st *WorkspaceStatus) { ... })
|
||||
// s.Workspaces().Names() // all workspace names
|
||||
// s.Workspaces().List("core/*") // org-scoped workspaces
|
||||
// s.Workspaces().Each(func(name string, st *WorkspaceStatus) { ... })
|
||||
func (s *PrepSubsystem) Workspaces() *core.Registry[*WorkspaceStatus] {
|
||||
return s.workspaces
|
||||
}
|
||||
|
|
@ -268,17 +249,13 @@ func envOr(key, fallback string) string {
|
|||
return fallback
|
||||
}
|
||||
|
||||
// Name identifies the MCP subsystem.
|
||||
//
|
||||
// subsystem := agentic.NewPrep()
|
||||
// name := subsystem.Name()
|
||||
// _ = name // "agentic"
|
||||
// subsystem := agentic.NewPrep()
|
||||
// name := subsystem.Name()
|
||||
// _ = name // "agentic"
|
||||
func (s *PrepSubsystem) Name() string { return "agentic" }
|
||||
|
||||
// RegisterTools publishes the agentic MCP tools on the server.
|
||||
//
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.RegisterTools(server)
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.RegisterTools(server)
|
||||
func (s *PrepSubsystem) RegisterTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "agentic_prep_workspace",
|
||||
|
|
@ -306,18 +283,13 @@ func (s *PrepSubsystem) RegisterTools(server *mcp.Server) {
|
|||
s.registerWatchTool(server)
|
||||
}
|
||||
|
||||
// Shutdown satisfies mcp.SubsystemWithShutdown for clean server teardown.
|
||||
//
|
||||
// subsystem := agentic.NewPrep()
|
||||
// _ = subsystem.Shutdown(context.Background())
|
||||
// subsystem := agentic.NewPrep()
|
||||
// _ = subsystem.Shutdown(context.Background())
|
||||
func (s *PrepSubsystem) Shutdown(_ context.Context) error { return nil }
|
||||
|
||||
// --- Input/Output types ---
|
||||
|
||||
// PrepInput is the input for agentic_prep_workspace.
|
||||
// One of Issue, PR, Branch, or Tag is required.
|
||||
//
|
||||
// input := agentic.PrepInput{Repo: "go-io", Issue: 15, Task: "Migrate to Core primitives"}
|
||||
// input := agentic.PrepInput{Repo: "go-io", Issue: 15, Task: "Migrate to Core primitives"}
|
||||
type PrepInput struct {
|
||||
Repo string `json:"repo"` // required: e.g. "go-io"
|
||||
Org string `json:"org,omitempty"` // default "core"
|
||||
|
|
@ -334,9 +306,7 @@ type PrepInput struct {
|
|||
DryRun bool `json:"dry_run,omitempty"` // preview without executing
|
||||
}
|
||||
|
||||
// PrepOutput is the output for agentic_prep_workspace.
|
||||
//
|
||||
// out := agentic.PrepOutput{Success: true, WorkspaceDir: ".core/workspace/core/go-io/task-15"}
|
||||
// out := agentic.PrepOutput{Success: true, WorkspaceDir: ".core/workspace/core/go-io/task-15"}
|
||||
type PrepOutput struct {
|
||||
Success bool `json:"success"`
|
||||
WorkspaceDir string `json:"workspace_dir"`
|
||||
|
|
@ -348,10 +318,8 @@ type PrepOutput struct {
|
|||
Resumed bool `json:"resumed"`
|
||||
}
|
||||
|
||||
// workspaceDir resolves the workspace path from the input identifier.
|
||||
//
|
||||
// dir := workspaceDir("core", "go-io", PrepInput{Issue: 15})
|
||||
// // → ".core/workspace/core/go-io/task-15"
|
||||
// dir := workspaceDir("core", "go-io", PrepInput{Issue: 15})
|
||||
// dir == ".core/workspace/core/go-io/task-15"
|
||||
func workspaceDir(org, repo string, input PrepInput) (string, error) {
|
||||
r := workspaceDirResult(org, repo, input)
|
||||
if !r.OK {
|
||||
|
|
@ -368,10 +336,8 @@ func workspaceDir(org, repo string, input PrepInput) (string, error) {
|
|||
return workspaceDir, nil
|
||||
}
|
||||
|
||||
// workspaceDirResult resolves the workspace path and returns core.Result.
|
||||
//
|
||||
// r := workspaceDirResult("core", "go-io", PrepInput{Issue: 15})
|
||||
// if r.OK { workspaceDir := r.Value.(string) }
|
||||
// r := workspaceDirResult("core", "go-io", PrepInput{Issue: 15})
|
||||
// if r.OK { workspaceDir := r.Value.(string) }
|
||||
func workspaceDirResult(org, repo string, input PrepInput) core.Result {
|
||||
orgName := core.ValidateName(org)
|
||||
if !orgName.OK {
|
||||
|
|
|
|||
|
|
@ -10,18 +10,14 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// DispatchConfig controls agent dispatch behaviour.
|
||||
//
|
||||
// config := agentic.DispatchConfig{DefaultAgent: "claude", DefaultTemplate: "coding"}
|
||||
// config := agentic.DispatchConfig{DefaultAgent: "claude", DefaultTemplate: "coding"}
|
||||
type DispatchConfig struct {
|
||||
DefaultAgent string `yaml:"default_agent"`
|
||||
DefaultTemplate string `yaml:"default_template"`
|
||||
WorkspaceRoot string `yaml:"workspace_root"`
|
||||
}
|
||||
|
||||
// RateConfig controls pacing between task dispatches.
|
||||
//
|
||||
// rate := agentic.RateConfig{ResetUTC: "06:00", SustainedDelay: 120, BurstWindow: 2, BurstDelay: 15}
|
||||
// rate := agentic.RateConfig{ResetUTC: "06:00", SustainedDelay: 120, BurstWindow: 2, BurstDelay: 15}
|
||||
type RateConfig struct {
|
||||
ResetUTC string `yaml:"reset_utc"` // Daily quota reset time (UTC), e.g. "06:00"
|
||||
DailyLimit int `yaml:"daily_limit"` // Max requests per day (0 = unknown)
|
||||
|
|
@ -31,13 +27,12 @@ type RateConfig struct {
|
|||
BurstDelay int `yaml:"burst_delay"` // Delay during burst window
|
||||
}
|
||||
|
||||
// ConcurrencyLimit supports both flat (int) and nested (map with total + per-model) formats.
|
||||
// claude: 1 → Total=1, Models=nil
|
||||
// codex: → Total=2, Models={"gpt-5.4": 1, "gpt-5.3-codex-spark": 1}
|
||||
//
|
||||
// claude: 1 → Total=1, Models=nil
|
||||
// codex: → Total=2, Models={"gpt-5.4": 1, "gpt-5.3-codex-spark": 1}
|
||||
// total: 2
|
||||
// gpt-5.4: 1
|
||||
// gpt-5.3-codex-spark: 1
|
||||
// total: 2
|
||||
// gpt-5.4: 1
|
||||
// gpt-5.3-codex-spark: 1
|
||||
type ConcurrencyLimit struct {
|
||||
Total int
|
||||
Models map[string]int
|
||||
|
|
@ -65,9 +60,7 @@ func (c *ConcurrencyLimit) UnmarshalYAML(value *yaml.Node) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AgentsConfig is the root of config/agents.yaml.
|
||||
//
|
||||
// config := agentic.AgentsConfig{Version: 1, Dispatch: agentic.DispatchConfig{DefaultAgent: "claude"}}
|
||||
// config := agentic.AgentsConfig{Version: 1, Dispatch: agentic.DispatchConfig{DefaultAgent: "claude"}}
|
||||
type AgentsConfig struct {
|
||||
Version int `yaml:"version"`
|
||||
Dispatch DispatchConfig `yaml:"dispatch"`
|
||||
|
|
@ -235,9 +228,7 @@ func baseAgent(agent string) string {
|
|||
return core.SplitN(agent, ":", 2)[0]
|
||||
}
|
||||
|
||||
// canDispatchAgent checks both pool-level and per-model concurrency limits.
|
||||
//
|
||||
// codex: {total: 2, models: {gpt-5.4: 1}} → max 2 codex total, max 1 gpt-5.4
|
||||
// codex: {total: 2, models: {gpt-5.4: 1}} → max 2 codex total, max 1 gpt-5.4
|
||||
func (s *PrepSubsystem) canDispatchAgent(agent string) bool {
|
||||
var concurrency map[string]ConcurrencyLimit
|
||||
if s.ServiceRuntime != nil {
|
||||
|
|
|
|||
|
|
@ -10,14 +10,8 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// statusPath := agentic.WorkspaceStatusPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
// blockedPath := agentic.WorkspaceBlockedPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
// logs := agentic.WorkspaceLogFiles("/srv/.core/workspace/core/go-io/task-5")
|
||||
|
||||
// WorkspaceStatus represents the current state of an agent workspace.
|
||||
//
|
||||
// result := ReadStatusResult(workspaceDir)
|
||||
// if result.OK && result.Value.(*WorkspaceStatus).Status == "completed" { autoCreatePR(workspaceDir) }
|
||||
// result := ReadStatusResult(workspaceDir)
|
||||
// if result.OK && result.Value.(*WorkspaceStatus).Status == "completed" { autoCreatePR(workspaceDir) }
|
||||
type WorkspaceStatus struct {
|
||||
Status string `json:"status"` // running, completed, blocked, failed
|
||||
Agent string `json:"agent"` // gemini, claude, codex
|
||||
|
|
@ -35,12 +29,9 @@ type WorkspaceStatus struct {
|
|||
PRURL string `json:"pr_url,omitempty"` // pull request URL (after PR created)
|
||||
}
|
||||
|
||||
// WorkspaceQuery is the QUERY type for workspace state lookups.
|
||||
// Returns the workspace Registry via c.QUERY(agentic.WorkspaceQuery{}).
|
||||
//
|
||||
// r := c.QUERY(agentic.WorkspaceQuery{})
|
||||
// if r.OK { reg := r.Value.(*core.Registry[*WorkspaceStatus]) }
|
||||
// r := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-5"})
|
||||
// r := c.QUERY(agentic.WorkspaceQuery{})
|
||||
// if r.OK { reg := r.Value.(*core.Registry[*WorkspaceStatus]) }
|
||||
// r := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-5"})
|
||||
type WorkspaceQuery struct {
|
||||
Name string // specific workspace (empty = all)
|
||||
Status string // filter by status (empty = all)
|
||||
|
|
@ -58,10 +49,8 @@ func writeStatus(workspaceDir string, status *WorkspaceStatus) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// writeStatusResult writes status.json and returns core.Result.
|
||||
//
|
||||
// result := writeStatusResult("/srv/core/workspace/core/go-io/task-5", &WorkspaceStatus{Status: "running"})
|
||||
// if result.OK { return }
|
||||
// result := writeStatusResult("/srv/core/workspace/core/go-io/task-5", &WorkspaceStatus{Status: "running"})
|
||||
// if result.OK { return }
|
||||
func writeStatusResult(workspaceDir string, status *WorkspaceStatus) core.Result {
|
||||
if status == nil {
|
||||
return core.Result{Value: core.E("writeStatus", "status is required", nil), OK: false}
|
||||
|
|
@ -80,10 +69,8 @@ func writeStatusResult(workspaceDir string, status *WorkspaceStatus) core.Result
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// ReadStatusResult parses status.json and returns a WorkspaceStatus pointer.
|
||||
//
|
||||
// result := ReadStatusResult("/path/to/workspace")
|
||||
// if result.OK { workspaceStatus := result.Value.(*WorkspaceStatus) }
|
||||
// result := ReadStatusResult("/path/to/workspace")
|
||||
// if result.OK { workspaceStatus := result.Value.(*WorkspaceStatus) }
|
||||
func ReadStatusResult(workspaceDir string) core.Result {
|
||||
r := fs.Read(WorkspaceStatusPath(workspaceDir))
|
||||
if !r.OK {
|
||||
|
|
@ -104,10 +91,8 @@ func ReadStatusResult(workspaceDir string) core.Result {
|
|||
return core.Result{Value: &s, OK: true}
|
||||
}
|
||||
|
||||
// workspaceStatusValue extracts a WorkspaceStatus from a Result.
|
||||
//
|
||||
// result := ReadStatusResult("/path/to/workspace")
|
||||
// workspaceStatus, ok := workspaceStatusValue(result)
|
||||
// result := ReadStatusResult("/path/to/workspace")
|
||||
// workspaceStatus, ok := workspaceStatusValue(result)
|
||||
func workspaceStatusValue(result core.Result) (*WorkspaceStatus, bool) {
|
||||
workspaceStatus, ok := result.Value.(*WorkspaceStatus)
|
||||
if !ok || workspaceStatus == nil {
|
||||
|
|
@ -118,19 +103,14 @@ func workspaceStatusValue(result core.Result) (*WorkspaceStatus, bool) {
|
|||
|
||||
// --- agentic_status tool ---
|
||||
|
||||
// StatusInput is the input for agentic_status.
|
||||
//
|
||||
// input := agentic.StatusInput{Workspace: "core/go-io/task-42", Limit: 50}
|
||||
// input := agentic.StatusInput{Workspace: "core/go-io/task-42", Limit: 50}
|
||||
type StatusInput struct {
|
||||
Workspace string `json:"workspace,omitempty"` // specific workspace name, or empty for all
|
||||
Limit int `json:"limit,omitempty"` // max results (default 100)
|
||||
Status string `json:"status,omitempty"` // filter: running, completed, failed, blocked
|
||||
}
|
||||
|
||||
// StatusOutput is the output for agentic_status.
|
||||
// Returns stats by default. Only blocked workspaces are listed (they need attention).
|
||||
//
|
||||
// out := agentic.StatusOutput{Total: 42, Running: 3, Queued: 10, Completed: 25}
|
||||
// out := agentic.StatusOutput{Total: 42, Running: 3, Queued: 10, Completed: 25}
|
||||
type StatusOutput struct {
|
||||
Total int `json:"total"`
|
||||
Running int `json:"running"`
|
||||
|
|
@ -140,9 +120,7 @@ type StatusOutput struct {
|
|||
Blocked []BlockedInfo `json:"blocked,omitempty"`
|
||||
}
|
||||
|
||||
// BlockedInfo shows a workspace that needs human input.
|
||||
//
|
||||
// info := agentic.BlockedInfo{Name: "core/go-io/task-4", Repo: "go-io", Question: "Which API version?"}
|
||||
// info := agentic.BlockedInfo{Name: "core/go-io/task-4", Repo: "go-io", Question: "Which API version?"}
|
||||
type BlockedInfo struct {
|
||||
Name string `json:"name"`
|
||||
Repo string `json:"repo"`
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// Package brain gives MCP and HTTP services the same OpenBrain capability map.
|
||||
//
|
||||
// subsystem := brain.New(nil)
|
||||
// core.Println(subsystem.Name())
|
||||
// subsystem := brain.New(nil)
|
||||
// core.Println(subsystem.Name()) // "brain"
|
||||
package brain
|
||||
|
||||
import (
|
||||
|
|
@ -15,9 +13,8 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// fs provides unrestricted filesystem access for shared brain credentials.
|
||||
// keyPath := core.JoinPath(home, ".claude", "brain.key")
|
||||
//
|
||||
// keyPath := core.JoinPath(home, ".claude", "brain.key")
|
||||
// if readResult := fs.Read(keyPath); readResult.OK {
|
||||
// apiKey = core.Trim(readResult.Value.(string))
|
||||
// }
|
||||
|
|
@ -27,40 +24,31 @@ func stringField(values map[string]any, key string) string {
|
|||
return core.Sprint(values[key])
|
||||
}
|
||||
|
||||
// errBridgeNotAvailable is returned when a tool requires the Laravel bridge
|
||||
// but it has not been initialised (headless mode).
|
||||
// core.E("brain", "bridge not available", nil)
|
||||
var errBridgeNotAvailable = core.E("brain", "bridge not available", nil)
|
||||
|
||||
// Subsystem routes `brain_*` MCP tools through the shared IDE bridge.
|
||||
//
|
||||
// subsystem := brain.New(nil)
|
||||
// core.Println(subsystem.Name()) // "brain"
|
||||
// subsystem := brain.New(nil)
|
||||
// core.Println(subsystem.Name()) // "brain"
|
||||
type Subsystem struct {
|
||||
bridge *ide.Bridge
|
||||
}
|
||||
|
||||
// New builds the bridge-backed OpenBrain subsystem used by MCP.
|
||||
//
|
||||
// subsystem := brain.New(nil)
|
||||
// core.Println(subsystem.Name())
|
||||
// subsystem := brain.New(nil)
|
||||
// core.Println(subsystem.Name())
|
||||
func New(bridge *ide.Bridge) *Subsystem {
|
||||
return &Subsystem{bridge: bridge}
|
||||
}
|
||||
|
||||
// name := subsystem.Name() // "brain"
|
||||
// name := subsystem.Name() // "brain"
|
||||
func (s *Subsystem) Name() string { return "brain" }
|
||||
|
||||
// RegisterTools publishes the bridge-backed brain tools on an MCP server.
|
||||
//
|
||||
// subsystem := brain.New(nil)
|
||||
// subsystem.RegisterTools(server)
|
||||
// subsystem := brain.New(nil)
|
||||
// subsystem.RegisterTools(server)
|
||||
func (s *Subsystem) RegisterTools(server *mcp.Server) {
|
||||
s.registerBrainTools(server)
|
||||
}
|
||||
|
||||
// Shutdown satisfies the MCP subsystem lifecycle without extra cleanup.
|
||||
//
|
||||
// _ = subsystem.Shutdown(context.Background())
|
||||
// _ = subsystem.Shutdown(context.Background())
|
||||
func (s *Subsystem) Shutdown(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,8 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// DirectSubsystem talks to OpenBrain over HTTP without the IDE bridge.
|
||||
//
|
||||
// subsystem := brain.NewDirect()
|
||||
// core.Println(subsystem.Name()) // "brain"
|
||||
// subsystem := brain.NewDirect()
|
||||
// core.Println(subsystem.Name()) // "brain"
|
||||
type DirectSubsystem struct {
|
||||
apiURL string
|
||||
apiKey string
|
||||
|
|
@ -23,10 +21,8 @@ type DirectSubsystem struct {
|
|||
|
||||
var _ coremcp.Subsystem = (*DirectSubsystem)(nil)
|
||||
|
||||
// NewDirect builds the HTTP-backed OpenBrain subsystem.
|
||||
//
|
||||
// subsystem := brain.NewDirect()
|
||||
// core.Println(subsystem.Name())
|
||||
// subsystem := brain.NewDirect()
|
||||
// core.Println(subsystem.Name())
|
||||
func NewDirect() *DirectSubsystem {
|
||||
apiURL := core.Env("CORE_BRAIN_URL")
|
||||
if apiURL == "" {
|
||||
|
|
@ -56,13 +52,11 @@ func NewDirect() *DirectSubsystem {
|
|||
}
|
||||
}
|
||||
|
||||
// name := subsystem.Name() // "brain"
|
||||
// name := subsystem.Name() // "brain"
|
||||
func (s *DirectSubsystem) Name() string { return "brain" }
|
||||
|
||||
// RegisterTools publishes the direct `brain_*` and `agent_*` tools on an MCP server.
|
||||
//
|
||||
// subsystem := brain.NewDirect()
|
||||
// subsystem.RegisterTools(server)
|
||||
// subsystem := brain.NewDirect()
|
||||
// subsystem.RegisterTools(server)
|
||||
func (s *DirectSubsystem) RegisterTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "brain_remember",
|
||||
|
|
@ -83,9 +77,7 @@ func (s *DirectSubsystem) RegisterTools(server *mcp.Server) {
|
|||
s.RegisterMessagingTools(server)
|
||||
}
|
||||
|
||||
// Shutdown satisfies the MCP subsystem lifecycle without extra cleanup.
|
||||
//
|
||||
// _ = subsystem.Shutdown(context.Background())
|
||||
// _ = subsystem.Shutdown(context.Background())
|
||||
func (s *DirectSubsystem) Shutdown(_ context.Context) error { return nil }
|
||||
|
||||
func brainKeyPath(home string) string {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// RegisterMessagingTools adds direct agent messaging tools to an MCP server.
|
||||
//
|
||||
// subsystem := brain.NewDirect()
|
||||
// subsystem.RegisterMessagingTools(server)
|
||||
// subsystem := brain.NewDirect()
|
||||
// subsystem.RegisterMessagingTools(server)
|
||||
func (s *DirectSubsystem) RegisterMessagingTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "agent_send",
|
||||
|
|
@ -33,34 +31,26 @@ func (s *DirectSubsystem) RegisterMessagingTools(server *mcp.Server) {
|
|||
|
||||
// Input/Output types
|
||||
|
||||
// SendInput is the payload for `agent_send`.
|
||||
//
|
||||
// brain.SendInput{To: "charon", Subject: "status update", Content: "deploy complete"}
|
||||
// brain.SendInput{To: "charon", Subject: "status update", Content: "deploy complete"}
|
||||
type SendInput struct {
|
||||
To string `json:"to"`
|
||||
Content string `json:"content"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
}
|
||||
|
||||
// SendOutput reports the stored direct message.
|
||||
//
|
||||
// brain.SendOutput{Success: true, ID: 42, To: "charon"}
|
||||
// brain.SendOutput{Success: true, ID: 42, To: "charon"}
|
||||
type SendOutput struct {
|
||||
Success bool `json:"success"`
|
||||
ID int `json:"id"`
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
// InboxInput selects which inbox `agent_inbox` should read.
|
||||
//
|
||||
// brain.InboxInput{Agent: "cladius"}
|
||||
// brain.InboxInput{Agent: "cladius"}
|
||||
type InboxInput struct {
|
||||
Agent string `json:"agent,omitempty"`
|
||||
}
|
||||
|
||||
// MessageItem is one inbox or conversation entry.
|
||||
//
|
||||
// brain.MessageItem{ID: 7, From: "cladius", To: "charon", Content: "all green"}
|
||||
// brain.MessageItem{ID: 7, From: "cladius", To: "charon", Content: "all green"}
|
||||
type MessageItem struct {
|
||||
ID int `json:"id"`
|
||||
From string `json:"from"`
|
||||
|
|
@ -71,24 +61,18 @@ type MessageItem struct {
|
|||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// InboxOutput returns the latest direct messages for one agent.
|
||||
//
|
||||
// brain.InboxOutput{Success: true, Messages: []brain.MessageItem{{ID: 1, From: "charon", To: "cladius"}}}
|
||||
// brain.InboxOutput{Success: true, Messages: []brain.MessageItem{{ID: 1, From: "charon", To: "cladius"}}}
|
||||
type InboxOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Messages []MessageItem `json:"messages"`
|
||||
}
|
||||
|
||||
// ConversationInput selects the thread `agent_conversation` should load.
|
||||
//
|
||||
// brain.ConversationInput{Agent: "charon"}
|
||||
// brain.ConversationInput{Agent: "charon"}
|
||||
type ConversationInput struct {
|
||||
Agent string `json:"agent"`
|
||||
}
|
||||
|
||||
// ConversationOutput returns a direct message thread with another agent.
|
||||
//
|
||||
// brain.ConversationOutput{Success: true, Messages: []brain.MessageItem{{ID: 10, From: "cladius", To: "charon"}}}
|
||||
// brain.ConversationOutput{Success: true, Messages: []brain.MessageItem{{ID: 10, From: "cladius", To: "charon"}}}
|
||||
type ConversationOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Messages []MessageItem `json:"messages"`
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@ const (
|
|||
statusServiceUnavailable = 503
|
||||
)
|
||||
|
||||
// NewProvider builds the HTTP provider around the IDE bridge and WS hub.
|
||||
//
|
||||
// p := brain.NewProvider(bridge, hub)
|
||||
// core.Println(p.BasePath())
|
||||
// p := brain.NewProvider(bridge, hub)
|
||||
// core.Println(p.BasePath())
|
||||
func NewProvider(bridge *ide.Bridge, hub *ws.Hub) *BrainProvider {
|
||||
return &BrainProvider{
|
||||
bridge: bridge,
|
||||
|
|
@ -47,15 +45,11 @@ func NewProvider(bridge *ide.Bridge, hub *ws.Hub) *BrainProvider {
|
|||
// name := p.Name() // "brain"
|
||||
func (p *BrainProvider) Name() string { return "brain" }
|
||||
|
||||
// BasePath shows where the provider mounts its routes.
|
||||
//
|
||||
// base := p.BasePath() // "/api/brain"
|
||||
// base := p.BasePath() // "/api/brain"
|
||||
func (p *BrainProvider) BasePath() string { return "/api/brain" }
|
||||
|
||||
// Channels lists the WS events emitted after brain actions complete.
|
||||
//
|
||||
// channels := p.Channels()
|
||||
// core.Println(channels[0]) // "brain.remember.complete"
|
||||
// channels := p.Channels()
|
||||
// core.Println(channels[0]) // "brain.remember.complete"
|
||||
func (p *BrainProvider) Channels() []string {
|
||||
return []string{
|
||||
"brain.remember.complete",
|
||||
|
|
@ -64,10 +58,8 @@ func (p *BrainProvider) Channels() []string {
|
|||
}
|
||||
}
|
||||
|
||||
// Element describes the browser component that renders the brain panel.
|
||||
//
|
||||
// spec := p.Element()
|
||||
// core.Println(spec.Tag) // "core-brain-panel"
|
||||
// spec := p.Element()
|
||||
// core.Println(spec.Tag) // "core-brain-panel"
|
||||
func (p *BrainProvider) Element() provider.ElementSpec {
|
||||
return provider.ElementSpec{
|
||||
Tag: "core-brain-panel",
|
||||
|
|
@ -75,9 +67,7 @@ func (p *BrainProvider) Element() provider.ElementSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes mounts the provider handlers onto a router group.
|
||||
//
|
||||
// p.RegisterRoutes(router.Group("/api/brain"))
|
||||
// p.RegisterRoutes(router.Group("/api/brain"))
|
||||
func (p *BrainProvider) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
rg.POST("/remember", p.remember)
|
||||
rg.POST("/recall", p.recall)
|
||||
|
|
@ -86,10 +76,8 @@ func (p *BrainProvider) RegisterRoutes(rg *gin.RouterGroup) {
|
|||
rg.GET("/status", p.status)
|
||||
}
|
||||
|
||||
// Describe returns the route contract used by API discovery and docs.
|
||||
//
|
||||
// routes := p.Describe()
|
||||
// core.Println(routes[0].Path) // "/remember"
|
||||
// routes := p.Describe()
|
||||
// core.Println(routes[0].Path) // "/remember"
|
||||
func (p *BrainProvider) Describe() []api.RouteDescription {
|
||||
return []api.RouteDescription{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ import (
|
|||
|
||||
// -- Input/Output types -------------------------------------------------------
|
||||
|
||||
// RememberInput is the input for brain_remember.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// input := brain.RememberInput{
|
||||
// Content: "Use core.Env for system paths.",
|
||||
// Type: "convention",
|
||||
|
|
@ -31,10 +27,6 @@ type RememberInput struct {
|
|||
ExpiresIn int `json:"expires_in,omitempty"`
|
||||
}
|
||||
|
||||
// RememberOutput is the output for brain_remember.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// output := brain.RememberOutput{
|
||||
// Success: true,
|
||||
// MemoryID: "mem_123",
|
||||
|
|
@ -45,10 +37,6 @@ type RememberOutput struct {
|
|||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// RecallInput is the input for brain_recall.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// input := brain.RecallInput{
|
||||
// Query: "core.Env conventions",
|
||||
// TopK: 5,
|
||||
|
|
@ -59,10 +47,6 @@ type RecallInput struct {
|
|||
Filter RecallFilter `json:"filter,omitempty"`
|
||||
}
|
||||
|
||||
// RecallFilter holds optional filter criteria for brain_recall.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// filter := brain.RecallFilter{
|
||||
// Project: "agent",
|
||||
// Type: "convention",
|
||||
|
|
@ -74,10 +58,6 @@ type RecallFilter struct {
|
|||
MinConfidence float64 `json:"min_confidence,omitempty"`
|
||||
}
|
||||
|
||||
// RecallOutput is the output for brain_recall.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// output := brain.RecallOutput{
|
||||
// Success: true,
|
||||
// Count: 1,
|
||||
|
|
@ -88,10 +68,6 @@ type RecallOutput struct {
|
|||
Memories []Memory `json:"memories"`
|
||||
}
|
||||
|
||||
// Memory is a single memory entry returned by recall or list.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// memory := brain.Memory{
|
||||
// ID: "mem_123",
|
||||
// Type: "convention",
|
||||
|
|
@ -111,10 +87,6 @@ type Memory struct {
|
|||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ForgetInput is the input for brain_forget.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// input := brain.ForgetInput{
|
||||
// ID: "mem_123",
|
||||
// Reason: "superseded",
|
||||
|
|
@ -124,10 +96,6 @@ type ForgetInput struct {
|
|||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// ForgetOutput is the output for brain_forget.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// output := brain.ForgetOutput{
|
||||
// Success: true,
|
||||
// Forgotten: "mem_123",
|
||||
|
|
@ -138,10 +106,6 @@ type ForgetOutput struct {
|
|||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// ListInput is the input for brain_list.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// input := brain.ListInput{
|
||||
// Project: "agent",
|
||||
// Limit: 20,
|
||||
|
|
@ -153,10 +117,6 @@ type ListInput struct {
|
|||
Limit int `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
// ListOutput is the output for brain_list.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// output := brain.ListOutput{
|
||||
// Success: true,
|
||||
// Count: 2,
|
||||
|
|
|
|||
|
|
@ -1,26 +1,18 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// Package messages defines IPC message types for inter-service communication
|
||||
// within core-agent. Services emit these via c.ACTION() and handle them via
|
||||
// c.RegisterAction(). No service imports another — they share only these types.
|
||||
//
|
||||
// c.ACTION(messages.AgentCompleted{Agent: "codex", Repo: "go-io", Status: "completed"})
|
||||
// c.ACTION(messages.AgentCompleted{Agent: "codex", Repo: "go-io", Status: "completed"})
|
||||
package messages
|
||||
|
||||
// --- Agent Lifecycle ---
|
||||
|
||||
// AgentStarted is broadcast when a subagent process is spawned.
|
||||
//
|
||||
// c.ACTION(messages.AgentStarted{Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-5"})
|
||||
// c.ACTION(messages.AgentStarted{Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-5"})
|
||||
type AgentStarted struct {
|
||||
Agent string
|
||||
Repo string
|
||||
Workspace string
|
||||
}
|
||||
|
||||
// AgentCompleted is broadcast when a subagent process exits.
|
||||
//
|
||||
// c.ACTION(messages.AgentCompleted{Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-5", Status: "completed"})
|
||||
// c.ACTION(messages.AgentCompleted{Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-5", Status: "completed"})
|
||||
type AgentCompleted struct {
|
||||
Agent string
|
||||
Repo string
|
||||
|
|
@ -30,9 +22,7 @@ type AgentCompleted struct {
|
|||
|
||||
// --- QA & PR Pipeline ---
|
||||
|
||||
// QAResult is broadcast after QA runs on a completed workspace.
|
||||
//
|
||||
// c.ACTION(messages.QAResult{Workspace: "core/go-io/task-5", Repo: "go-io", Passed: true})
|
||||
// c.ACTION(messages.QAResult{Workspace: "core/go-io/task-5", Repo: "go-io", Passed: true})
|
||||
type QAResult struct {
|
||||
Workspace string
|
||||
Repo string
|
||||
|
|
@ -40,9 +30,7 @@ type QAResult struct {
|
|||
Output string
|
||||
}
|
||||
|
||||
// PRCreated is broadcast after a PR is auto-created on Forge.
|
||||
//
|
||||
// c.ACTION(messages.PRCreated{Repo: "go-io", Branch: "agent/fix-tests", PRURL: "https://...", PRNum: 12})
|
||||
// c.ACTION(messages.PRCreated{Repo: "go-io", Branch: "agent/fix-tests", PRURL: "https://...", PRNum: 12})
|
||||
type PRCreated struct {
|
||||
Repo string
|
||||
Branch string
|
||||
|
|
@ -50,18 +38,14 @@ type PRCreated struct {
|
|||
PRNum int
|
||||
}
|
||||
|
||||
// PRMerged is broadcast after a PR is auto-verified and merged.
|
||||
//
|
||||
// c.ACTION(messages.PRMerged{Repo: "go-io", PRURL: "https://...", PRNum: 12})
|
||||
// c.ACTION(messages.PRMerged{Repo: "go-io", PRURL: "https://...", PRNum: 12})
|
||||
type PRMerged struct {
|
||||
Repo string
|
||||
PRURL string
|
||||
PRNum int
|
||||
}
|
||||
|
||||
// PRNeedsReview is broadcast when auto-merge fails and human attention is needed.
|
||||
//
|
||||
// c.ACTION(messages.PRNeedsReview{Repo: "go-io", PRNum: 12, Reason: "merge conflict"})
|
||||
// c.ACTION(messages.PRNeedsReview{Repo: "go-io", PRNum: 12, Reason: "merge conflict"})
|
||||
type PRNeedsReview struct {
|
||||
Repo string
|
||||
PRURL string
|
||||
|
|
@ -71,31 +55,22 @@ type PRNeedsReview struct {
|
|||
|
||||
// --- Queue ---
|
||||
|
||||
// QueueDrained is broadcast when running=0 and queued=0 (genuinely empty).
|
||||
//
|
||||
// c.ACTION(messages.QueueDrained{Completed: 3})
|
||||
// c.ACTION(messages.QueueDrained{Completed: 3})
|
||||
type QueueDrained struct {
|
||||
Completed int
|
||||
}
|
||||
|
||||
// PokeQueue signals the runner to drain the queue immediately.
|
||||
//
|
||||
// c.ACTION(messages.PokeQueue{})
|
||||
// c.ACTION(messages.PokeQueue{})
|
||||
type PokeQueue struct{}
|
||||
|
||||
// SpawnQueued is sent by the runner to request agentic spawn a queued workspace.
|
||||
// Runner gates (frozen + concurrency), agentic owns the actual process spawn.
|
||||
//
|
||||
// c.ACTION(messages.SpawnQueued{Workspace: "core/go-io/task-5", Agent: "codex", Task: "review"})
|
||||
// c.ACTION(messages.SpawnQueued{Workspace: "core/go-io/task-5", Agent: "codex", Task: "review"})
|
||||
type SpawnQueued struct {
|
||||
Workspace string
|
||||
Agent string
|
||||
Task string
|
||||
}
|
||||
|
||||
// RateLimitDetected is broadcast when fast failures trigger agent pool backoff.
|
||||
//
|
||||
// c.ACTION(messages.RateLimitDetected{Pool: "codex", Duration: "30m"})
|
||||
// c.ACTION(messages.RateLimitDetected{Pool: "codex", Duration: "30m"})
|
||||
type RateLimitDetected struct {
|
||||
Pool string
|
||||
Duration string
|
||||
|
|
@ -103,27 +78,21 @@ type RateLimitDetected struct {
|
|||
|
||||
// --- Monitor Events ---
|
||||
|
||||
// HarvestComplete is broadcast when a workspace branch is ready for review.
|
||||
//
|
||||
// c.ACTION(messages.HarvestComplete{Repo: "go-io", Branch: "agent/fix-tests", Files: 5})
|
||||
// c.ACTION(messages.HarvestComplete{Repo: "go-io", Branch: "agent/fix-tests", Files: 5})
|
||||
type HarvestComplete struct {
|
||||
Repo string
|
||||
Branch string
|
||||
Files int
|
||||
}
|
||||
|
||||
// HarvestRejected is broadcast when a workspace fails safety checks (binaries, size).
|
||||
//
|
||||
// c.ACTION(messages.HarvestRejected{Repo: "go-io", Branch: "agent/fix-tests", Reason: "binary detected"})
|
||||
// c.ACTION(messages.HarvestRejected{Repo: "go-io", Branch: "agent/fix-tests", Reason: "binary detected"})
|
||||
type HarvestRejected struct {
|
||||
Repo string
|
||||
Branch string
|
||||
Reason string
|
||||
}
|
||||
|
||||
// InboxMessage is broadcast when a new inter-agent message arrives.
|
||||
//
|
||||
// c.ACTION(messages.InboxMessage{From: "charon", Subject: "status", Content: "all green"})
|
||||
// c.ACTION(messages.InboxMessage{From: "charon", Subject: "status", Content: "all green"})
|
||||
type InboxMessage struct {
|
||||
From string
|
||||
Subject string
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// Package monitor keeps workspace state and inbox status visible to MCP clients.
|
||||
//
|
||||
// service := monitor.New(monitor.Options{Interval: 30 * time.Second})
|
||||
// service.RegisterTools(server)
|
||||
// service := monitor.New(monitor.Options{Interval: 30 * time.Second})
|
||||
// service.RegisterTools(server)
|
||||
package monitor
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// Package runner owns agent dispatch and workspace lifecycle.
|
||||
//
|
||||
// service := runner.New()
|
||||
// service.TrackWorkspace("core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// service := runner.New()
|
||||
// service.TrackWorkspace("core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
package runner
|
||||
|
||||
import (
|
||||
|
|
@ -16,17 +14,11 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// Options configures the runner service.
|
||||
//
|
||||
// options := runner.Options{}
|
||||
// options := runner.Options{}
|
||||
type Options struct{}
|
||||
|
||||
// Service is the agent dispatch runner.
|
||||
// Manages concurrency limits, queue drain, workspace lifecycle, and frozen state.
|
||||
// All dispatch requests — MCP tool, CLI, or IPC — go through this service.
|
||||
//
|
||||
// service := runner.New()
|
||||
// service.TrackWorkspace("core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// service := runner.New()
|
||||
// service.TrackWorkspace("core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
type Service struct {
|
||||
*core.ServiceRuntime[Options]
|
||||
dispatchMu sync.Mutex
|
||||
|
|
@ -73,13 +65,13 @@ func Register(coreApp *core.Core) core.Result {
|
|||
return core.Result{Value: service, OK: true}
|
||||
}
|
||||
|
||||
// OnStartup registers Actions and starts the queue runner.
|
||||
// c.Action("runner.dispatch").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// c.Action("runner.dispatch").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
// core.Option{Key: "agent", Value: "codex"},
|
||||
// ))
|
||||
// c.Action("runner.status").Run(ctx, core.NewOptions())
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
// core.Option{Key: "agent", Value: "codex"},
|
||||
//
|
||||
// ))
|
||||
// c.Action("runner.status").Run(ctx, core.NewOptions())
|
||||
func (s *Service) OnStartup(ctx context.Context) core.Result {
|
||||
coreApp := s.Core()
|
||||
|
||||
|
|
@ -99,9 +91,8 @@ func (s *Service) OnStartup(ctx context.Context) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// OnShutdown freezes the queue.
|
||||
// result := service.OnShutdown(context.Background())
|
||||
//
|
||||
// result := service.OnShutdown(context.Background())
|
||||
// if result.OK {
|
||||
// core.Println(service.IsFrozen())
|
||||
// }
|
||||
|
|
@ -110,9 +101,8 @@ func (s *Service) OnShutdown(_ context.Context) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// HandleIPCEvents applies runner side-effects for IPC messages.
|
||||
// service.HandleIPCEvents(c, messages.PokeQueue{})
|
||||
//
|
||||
// service.HandleIPCEvents(c, messages.PokeQueue{})
|
||||
// service.HandleIPCEvents(c, messages.AgentCompleted{
|
||||
// Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-5", Status: "completed",
|
||||
// })
|
||||
|
|
@ -190,16 +180,12 @@ func (s *Service) HandleIPCEvents(coreApp *core.Core, msg core.Message) core.Res
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// IsFrozen returns whether dispatch is currently frozen.
|
||||
//
|
||||
// if s.IsFrozen() { return "queue is frozen" }
|
||||
// if s.IsFrozen() { return "queue is frozen" }
|
||||
func (s *Service) IsFrozen() bool {
|
||||
return s.frozen
|
||||
}
|
||||
|
||||
// Poke signals the runner to check the queue immediately.
|
||||
//
|
||||
// s.Poke()
|
||||
// s.Poke()
|
||||
func (s *Service) Poke() {
|
||||
if s.pokeCh == nil {
|
||||
return
|
||||
|
|
@ -210,11 +196,8 @@ func (s *Service) Poke() {
|
|||
}
|
||||
}
|
||||
|
||||
// TrackWorkspace registers or updates a workspace in the in-memory Registry.
|
||||
// Accepts the runner projection directly and the agentic projection from IPC.
|
||||
//
|
||||
// s.TrackWorkspace("core/go-io/task-5", &WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// s.TrackWorkspace("core/go-io/task-5", &agentic.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// s.TrackWorkspace("core/go-io/task-5", &WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// s.TrackWorkspace("core/go-io/task-5", &agentic.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
func (s *Service) TrackWorkspace(name string, status any) {
|
||||
if s.workspaces == nil {
|
||||
return
|
||||
|
|
@ -240,17 +223,13 @@ func (s *Service) TrackWorkspace(name string, status any) {
|
|||
s.workspaces.Delete(core.Concat("pending/", workspaceStatus.Repo))
|
||||
}
|
||||
|
||||
// Workspaces returns the workspace Registry.
|
||||
//
|
||||
// s.Workspaces().Each(func(name string, workspaceStatus *WorkspaceStatus) { ... })
|
||||
// s.Workspaces().Each(func(name string, workspaceStatus *WorkspaceStatus) { ... })
|
||||
func (s *Service) Workspaces() *core.Registry[*WorkspaceStatus] {
|
||||
return s.workspaces
|
||||
}
|
||||
|
||||
// handleWorkspaceQuery answers workspace state queries from Core QUERY calls.
|
||||
//
|
||||
// result := c.QUERY(runner.WorkspaceQuery{Name: "core/go-io/task-42"})
|
||||
// result := c.QUERY(runner.WorkspaceQuery{Status: "running"})
|
||||
// result := c.QUERY(runner.WorkspaceQuery{Name: "core/go-io/task-42"})
|
||||
// result := c.QUERY(runner.WorkspaceQuery{Status: "running"})
|
||||
func (s *Service) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Result {
|
||||
workspaceQuery, ok := query.(WorkspaceQuery)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ConfigData supplies values when setup renders workspace templates.
|
||||
//
|
||||
// data := setup.ConfigData{Name: "agent", Type: "go", Repository: "core/agent"}
|
||||
// data := setup.ConfigData{Name: "agent", Type: "go", Repository: "core/agent"}
|
||||
type ConfigData struct {
|
||||
Name string
|
||||
Description string
|
||||
|
|
@ -22,17 +20,13 @@ type ConfigData struct {
|
|||
Env map[string]string
|
||||
}
|
||||
|
||||
// Target describes one build target.
|
||||
//
|
||||
// target := setup.Target{OS: "linux", Arch: "amd64"}
|
||||
// target := setup.Target{OS: "linux", Arch: "amd64"}
|
||||
type Target struct {
|
||||
OS string
|
||||
Arch string
|
||||
}
|
||||
|
||||
// Command defines one named command in generated config.
|
||||
//
|
||||
// command := setup.Command{Name: "unit", Run: "go test ./..."}
|
||||
// command := setup.Command{Name: "unit", Run: "go test ./..."}
|
||||
type Command struct {
|
||||
Name string
|
||||
Run string
|
||||
|
|
@ -48,10 +42,8 @@ type configValue struct {
|
|||
Value any
|
||||
}
|
||||
|
||||
// GenerateBuildConfig renders `build.yaml` content for a detected repo type.
|
||||
//
|
||||
// r := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func GenerateBuildConfig(path string, projectType ProjectType) core.Result {
|
||||
name := core.PathBase(path)
|
||||
sections := []configSection{
|
||||
|
|
@ -95,10 +87,8 @@ func GenerateBuildConfig(path string, projectType ProjectType) core.Result {
|
|||
return renderConfig(core.Concat(name, " build configuration"), sections)
|
||||
}
|
||||
|
||||
// GenerateTestConfig renders `test.yaml` content for a detected repo type.
|
||||
//
|
||||
// r := setup.GenerateTestConfig(setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := setup.GenerateTestConfig(setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func GenerateTestConfig(projectType ProjectType) core.Result {
|
||||
var sections []configSection
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// Package setup provisions `.core/` files and workspace scaffolds for a repo.
|
||||
//
|
||||
// service := core.ServiceFor[*setup.Service](core.New(core.WithService(setup.Register)), "setup")
|
||||
// service := core.ServiceFor[*setup.Service](core.New(core.WithService(setup.Register)), "setup")
|
||||
package setup
|
||||
|
||||
import (
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// ProjectType records what setup detected in a repository path.
|
||||
//
|
||||
// projectType := setup.Detect("/srv/repos/agent")
|
||||
// if projectType == setup.TypeGo { /* generate Go defaults */ }
|
||||
// projectType := setup.Detect("/srv/repos/agent")
|
||||
// if projectType == setup.TypeGo { /* generate Go defaults */ }
|
||||
type ProjectType string
|
||||
|
||||
const (
|
||||
|
|
@ -23,12 +19,10 @@ const (
|
|||
TypeUnknown ProjectType = "unknown"
|
||||
)
|
||||
|
||||
// fs provides unrestricted filesystem access for setup operations.
|
||||
// fs := (&core.Fs{}).NewUnrestricted()
|
||||
var fs = (&core.Fs{}).NewUnrestricted()
|
||||
|
||||
// Detect inspects a repository path and returns the primary project type.
|
||||
//
|
||||
// projectType := setup.Detect("./repo")
|
||||
// projectType := setup.Detect("./repo")
|
||||
func Detect(path string) ProjectType {
|
||||
base := absolutePath(path)
|
||||
checks := []struct {
|
||||
|
|
@ -48,9 +42,7 @@ func Detect(path string) ProjectType {
|
|||
return TypeUnknown
|
||||
}
|
||||
|
||||
// DetectAll returns every detected project type for a polyglot repository.
|
||||
//
|
||||
// types := setup.DetectAll("./repo")
|
||||
// types := setup.DetectAll("./repo")
|
||||
func DetectAll(path string) []ProjectType {
|
||||
base := absolutePath(path)
|
||||
var projectTypes []ProjectType
|
||||
|
|
|
|||
|
|
@ -26,16 +26,12 @@ func Register(c *core.Core) core.Result {
|
|||
return core.Result{Value: service, OK: true}
|
||||
}
|
||||
|
||||
// OnStartup keeps the setup service ready for Core startup hooks.
|
||||
//
|
||||
// result := service.OnStartup(context.Background())
|
||||
// result := service.OnStartup(context.Background())
|
||||
func (s *Service) OnStartup(ctx context.Context) core.Result {
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// DetectGitRemote reads `origin` and returns `owner/repo` when available.
|
||||
//
|
||||
// remote := service.DetectGitRemote("/srv/repos/agent")
|
||||
// remote := service.DetectGitRemote("/srv/repos/agent")
|
||||
func (s *Service) DetectGitRemote(path string) string {
|
||||
result := s.Core().Process().RunIn(context.Background(), path, "git", "remote", "get-url", "origin")
|
||||
if !result.OK {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// Options controls one setup run.
|
||||
//
|
||||
// result := service.Run(setup.Options{Path: ".", Template: "auto", Force: true})
|
||||
// if !result.OK { core.Print(nil, "%v", result.Value) }
|
||||
// result := service.Run(setup.Options{Path: ".", Template: "auto", Force: true})
|
||||
// if !result.OK { core.Print(nil, "%v", result.Value) }
|
||||
type Options struct {
|
||||
Path string // Target directory (default: cwd)
|
||||
DryRun bool // Preview only, don't write
|
||||
|
|
@ -18,10 +16,8 @@ type Options struct {
|
|||
Template string // Workspace template or compatibility alias (default, review, security, agent, go, php, gui, auto)
|
||||
}
|
||||
|
||||
// Run generates `.core/` files and optional workspace scaffolding for a repo.
|
||||
//
|
||||
// result := service.Run(setup.Options{Path: ".", Template: "auto"})
|
||||
// core.Println(result.OK)
|
||||
// result := service.Run(setup.Options{Path: ".", Template: "auto"})
|
||||
// core.Println(result.OK)
|
||||
func (s *Service) Run(options Options) core.Result {
|
||||
if options.Path == "" {
|
||||
options.Path = core.Env("DIR_CWD")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue