fix(ax): make core-agent startup explicit
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
52a339f125
commit
3d7ec7efce
8 changed files with 231 additions and 25 deletions
|
|
@ -3,6 +3,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
|
||||
"dappco.re/go/core"
|
||||
)
|
||||
|
||||
|
|
@ -10,6 +13,59 @@ type appCommandSet struct {
|
|||
core *core.Core
|
||||
}
|
||||
|
||||
// startupArgs applies early log flags, then returns args for c.Cli().Run().
|
||||
//
|
||||
// args := startupArgs()
|
||||
// _ = c.Cli().Run(args...)
|
||||
func startupArgs() []string {
|
||||
previous := flag.CommandLine
|
||||
commandLine := flag.NewFlagSet("core-agent", flag.ContinueOnError)
|
||||
commandLine.SetOutput(&bytes.Buffer{})
|
||||
commandLine.BoolFunc("quiet", "", func(string) error {
|
||||
core.SetLevel(core.LevelError)
|
||||
return nil
|
||||
})
|
||||
commandLine.BoolFunc("q", "", func(string) error {
|
||||
core.SetLevel(core.LevelError)
|
||||
return nil
|
||||
})
|
||||
commandLine.BoolFunc("debug", "", func(string) error {
|
||||
core.SetLevel(core.LevelDebug)
|
||||
return nil
|
||||
})
|
||||
commandLine.BoolFunc("d", "", func(string) error {
|
||||
core.SetLevel(core.LevelDebug)
|
||||
return nil
|
||||
})
|
||||
|
||||
flag.CommandLine = commandLine
|
||||
defer func() {
|
||||
flag.CommandLine = previous
|
||||
}()
|
||||
|
||||
flag.Parse()
|
||||
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"})
|
||||
func applyLogLevel(args []string) []string {
|
||||
var cleaned []string
|
||||
for _, arg := range args {
|
||||
switch arg {
|
||||
case "--quiet", "-q":
|
||||
core.SetLevel(core.LevelError)
|
||||
case "--debug", "-d":
|
||||
core.SetLevel(core.LevelDebug)
|
||||
default:
|
||||
cleaned = append(cleaned, arg)
|
||||
}
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
// registerAppCommands adds app-level CLI commands (version, check, env).
|
||||
// These are not owned by any service — they're the binary's own commands.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -13,3 +13,10 @@ func Example_registerAppCommands() {
|
|||
core.Println(len(c.Commands()))
|
||||
// Output: 3
|
||||
}
|
||||
|
||||
func Example_applyLogLevel() {
|
||||
args := applyLogLevel([]string{"--debug", "status"})
|
||||
|
||||
core.Println(args[0])
|
||||
// Output: status
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core"
|
||||
|
|
@ -15,10 +17,63 @@ func newTestCore(t *testing.T) *core.Core {
|
|||
c := core.New(core.WithOption("name", "core-agent"))
|
||||
c.App().Version = "test"
|
||||
registerAppCommands(c)
|
||||
c.Cli().SetOutput(&bytes.Buffer{})
|
||||
return c
|
||||
}
|
||||
|
||||
// --- registerAppCommands ---
|
||||
func withArgs(t *testing.T, args ...string) {
|
||||
t.Helper()
|
||||
previous := os.Args
|
||||
os.Args = append([]string(nil), args...)
|
||||
t.Cleanup(func() {
|
||||
os.Args = previous
|
||||
})
|
||||
}
|
||||
|
||||
func TestCommands_ApplyLogLevel_Good(t *testing.T) {
|
||||
defer core.SetLevel(core.LevelInfo)
|
||||
|
||||
args := applyLogLevel([]string{"--quiet", "version"})
|
||||
assert.Equal(t, []string{"version"}, args)
|
||||
}
|
||||
|
||||
func TestCommands_ApplyLogLevel_Bad(t *testing.T) {
|
||||
defer core.SetLevel(core.LevelInfo)
|
||||
|
||||
args := applyLogLevel([]string{"status"})
|
||||
assert.Equal(t, []string{"status"}, args)
|
||||
}
|
||||
|
||||
func TestCommands_ApplyLogLevel_Ugly(t *testing.T) {
|
||||
defer core.SetLevel(core.LevelInfo)
|
||||
|
||||
args := applyLogLevel([]string{"version", "-q"})
|
||||
assert.Equal(t, []string{"version"}, args)
|
||||
}
|
||||
|
||||
func TestCommands_StartupArgs_Good(t *testing.T) {
|
||||
defer core.SetLevel(core.LevelInfo)
|
||||
|
||||
withArgs(t, "core-agent", "--debug", "check")
|
||||
args := startupArgs()
|
||||
assert.Equal(t, []string{"check"}, args)
|
||||
}
|
||||
|
||||
func TestCommands_StartupArgs_Bad(t *testing.T) {
|
||||
defer core.SetLevel(core.LevelInfo)
|
||||
|
||||
withArgs(t, "core-agent", "status")
|
||||
args := startupArgs()
|
||||
assert.Equal(t, []string{"status"}, args)
|
||||
}
|
||||
|
||||
func TestCommands_StartupArgs_Ugly(t *testing.T) {
|
||||
defer core.SetLevel(core.LevelInfo)
|
||||
|
||||
withArgs(t, "core-agent", "version", "-q")
|
||||
args := startupArgs()
|
||||
assert.Equal(t, []string{"version"}, args)
|
||||
}
|
||||
|
||||
func TestCommands_RegisterAppCommands_Good(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
|
|
@ -28,17 +83,18 @@ func TestCommands_RegisterAppCommands_Good(t *testing.T) {
|
|||
assert.Contains(t, cmds, "env")
|
||||
}
|
||||
|
||||
// --- version command ---
|
||||
|
||||
func TestCommands_Version_Good(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
version = "0.8.0"
|
||||
t.Cleanup(func() {
|
||||
version = ""
|
||||
})
|
||||
|
||||
r := c.Cli().Run("version")
|
||||
assert.True(t, r.OK)
|
||||
}
|
||||
|
||||
func TestCommands_Version_Bad_DevVersion(t *testing.T) {
|
||||
func TestCommands_VersionDev_Bad(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
version = ""
|
||||
c.App().Version = "dev"
|
||||
|
|
@ -47,8 +103,6 @@ func TestCommands_Version_Bad_DevVersion(t *testing.T) {
|
|||
assert.True(t, r.OK)
|
||||
}
|
||||
|
||||
// --- check command ---
|
||||
|
||||
func TestCommands_Check_Good(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
|
||||
|
|
@ -56,8 +110,6 @@ func TestCommands_Check_Good(t *testing.T) {
|
|||
assert.True(t, r.OK)
|
||||
}
|
||||
|
||||
// --- env command ---
|
||||
|
||||
func TestCommands_Env_Good(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
|
||||
|
|
@ -65,27 +117,23 @@ func TestCommands_Env_Good(t *testing.T) {
|
|||
assert.True(t, r.OK)
|
||||
}
|
||||
|
||||
// --- CLI resolution ---
|
||||
|
||||
func TestCommands_Cli_Bad_UnknownCommand(t *testing.T) {
|
||||
func TestCommands_CliUnknown_Bad(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
r := c.Cli().Run("nonexistent")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestCommands_Cli_Good_Banner(t *testing.T) {
|
||||
func TestCommands_CliBanner_Good(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
c.Cli().SetBanner(func(_ *core.Cli) string {
|
||||
return "core-agent test"
|
||||
})
|
||||
// No args — shows banner, returns empty Result
|
||||
r := c.Cli().Run()
|
||||
_ = r
|
||||
}
|
||||
|
||||
func TestCommands_Cli_Ugly_EmptyArgs(t *testing.T) {
|
||||
func TestCommands_CliEmptyArgs_Ugly(t *testing.T) {
|
||||
c := newTestCore(t)
|
||||
// Explicit empty slice
|
||||
r := c.Cli().Run()
|
||||
_ = r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"dappco.re/go/core"
|
||||
|
||||
"dappco.re/go/agent/pkg/agentic"
|
||||
|
|
@ -12,7 +15,10 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
newCoreAgent().Run()
|
||||
if err := runCoreAgent(); err != nil {
|
||||
core.Error(err.Error())
|
||||
syscall.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// newCoreAgent builds the Core app with services and CLI commands wired for startup.
|
||||
|
|
@ -51,3 +57,48 @@ func appVersion() string {
|
|||
}
|
||||
return "dev"
|
||||
}
|
||||
|
||||
// runCoreAgent builds the runtime and executes the CLI with startup flags applied.
|
||||
//
|
||||
// err := runCoreAgent()
|
||||
func runCoreAgent() error {
|
||||
return runApp(newCoreAgent(), startupArgs())
|
||||
}
|
||||
|
||||
// runApp starts services, runs the CLI with explicit args, then shuts down.
|
||||
//
|
||||
// err := runApp(c, []string{"version"})
|
||||
func runApp(c *core.Core, cliArgs []string) error {
|
||||
if c == nil {
|
||||
return core.E("main.runApp", "core is required", nil)
|
||||
}
|
||||
|
||||
defer c.ServiceShutdown(context.Background())
|
||||
|
||||
result := c.ServiceStartup(c.Context(), nil)
|
||||
if !result.OK {
|
||||
return resultError("main.runApp", "startup failed", result)
|
||||
}
|
||||
|
||||
if cli := c.Cli(); cli != nil {
|
||||
result = cli.Run(cliArgs...)
|
||||
if !result.OK {
|
||||
return resultError("main.runApp", "cli failed", result)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resultError extracts the error from a Result or wraps the failure in core.E().
|
||||
//
|
||||
// err := resultError("main.runApp", "startup failed", result)
|
||||
func resultError(op, msg string, result core.Result) error {
|
||||
if result.OK {
|
||||
return nil
|
||||
}
|
||||
if err, ok := result.Value.(error); ok && err != nil {
|
||||
return err
|
||||
}
|
||||
return core.E(op, msg, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func withVersion(t *testing.T, value string) {
|
|||
t.Cleanup(func() { version = oldVersion })
|
||||
}
|
||||
|
||||
func TestMain_NewCoreAgent_Good_RegistersRuntime(t *testing.T) {
|
||||
func TestMain_NewCoreAgent_Good(t *testing.T) {
|
||||
withVersion(t, "0.15.0")
|
||||
|
||||
c := newCoreAgent()
|
||||
|
|
@ -51,7 +51,7 @@ func TestMain_NewCoreAgent_Good_RegistersRuntime(t *testing.T) {
|
|||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestMain_NewCoreAgent_Good_BannerUsesVersion(t *testing.T) {
|
||||
func TestMain_NewCoreAgentBanner_Good(t *testing.T) {
|
||||
withVersion(t, "0.15.0")
|
||||
|
||||
c := newCoreAgent()
|
||||
|
|
@ -59,7 +59,22 @@ func TestMain_NewCoreAgent_Good_BannerUsesVersion(t *testing.T) {
|
|||
assert.Equal(t, "core-agent 0.15.0 — agentic orchestration for the Core ecosystem", c.Cli().Banner())
|
||||
}
|
||||
|
||||
func TestMain_NewCoreAgent_Ugly_DevVersionFallback(t *testing.T) {
|
||||
func TestMain_RunApp_Good(t *testing.T) {
|
||||
withVersion(t, "0.15.0")
|
||||
|
||||
assert.NoError(t, runApp(newTestCore(t), []string{"version"}))
|
||||
}
|
||||
|
||||
func TestMain_RunApp_Bad(t *testing.T) {
|
||||
assert.EqualError(t, runApp(nil, []string{"version"}), "main.runApp: core is required")
|
||||
}
|
||||
|
||||
func TestMain_ResultError_Ugly(t *testing.T) {
|
||||
err := resultError("main.runApp", "cli failed", core.Result{})
|
||||
assert.EqualError(t, err, "main.runApp: cli failed")
|
||||
}
|
||||
|
||||
func TestMain_NewCoreAgentFallback_Ugly(t *testing.T) {
|
||||
withVersion(t, "")
|
||||
|
||||
c := newCoreAgent()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ import (
|
|||
"forge.lthn.ai/core/mcp/pkg/mcp"
|
||||
)
|
||||
|
||||
// registerMCPService builds the MCP service from registered AX subsystems.
|
||||
//
|
||||
// r := registerMCPService(c)
|
||||
func registerMCPService(c *core.Core) core.Result {
|
||||
if c == nil {
|
||||
return core.Result{Value: core.E("main.registerMCPService", "core is required", nil), OK: false}
|
||||
}
|
||||
|
||||
var subsystems []mcp.Subsystem
|
||||
|
||||
if prep, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic"); ok {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,14 @@ func TestMCP_RegisterMCPService_Good(t *testing.T) {
|
|||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestMCP_RegisterMCPService_Good_WithRegisteredSubsystems(t *testing.T) {
|
||||
func TestMCP_RegisterMCPService_Bad(t *testing.T) {
|
||||
result := registerMCPService(nil)
|
||||
|
||||
require.False(t, result.OK)
|
||||
assert.EqualError(t, result.Value.(error), "main.registerMCPService: core is required")
|
||||
}
|
||||
|
||||
func TestMCP_RegisterMCPService_Ugly(t *testing.T) {
|
||||
c := core.New(
|
||||
core.WithOption("name", "core-agent"),
|
||||
core.WithService(agentic.ProcessRegister),
|
||||
|
|
|
|||
|
|
@ -10,36 +10,51 @@ import (
|
|||
|
||||
func TestUpdate_UpdateChannel_Good(t *testing.T) {
|
||||
version = "1.0.0"
|
||||
t.Cleanup(func() {
|
||||
version = ""
|
||||
})
|
||||
assert.Equal(t, "stable", updateChannel())
|
||||
}
|
||||
|
||||
func TestUpdate_UpdateChannel_Good_Dev(t *testing.T) {
|
||||
func TestUpdate_UpdateChannelDev_Good(t *testing.T) {
|
||||
version = "dev"
|
||||
t.Cleanup(func() {
|
||||
version = ""
|
||||
})
|
||||
assert.Equal(t, "dev", updateChannel())
|
||||
}
|
||||
|
||||
func TestUpdate_UpdateChannel_Good_Empty(t *testing.T) {
|
||||
func TestUpdate_UpdateChannelEmpty_Bad(t *testing.T) {
|
||||
version = ""
|
||||
assert.Equal(t, "dev", updateChannel())
|
||||
}
|
||||
|
||||
func TestUpdate_UpdateChannel_Good_Prerelease(t *testing.T) {
|
||||
func TestUpdate_UpdateChannelPrerelease_Ugly(t *testing.T) {
|
||||
version = "0.8.0-alpha"
|
||||
t.Cleanup(func() {
|
||||
version = ""
|
||||
})
|
||||
assert.Equal(t, "prerelease", updateChannel())
|
||||
}
|
||||
|
||||
func TestUpdate_UpdateChannel_Ugly(t *testing.T) {
|
||||
func TestUpdate_UpdateChannelNumericSuffix_Ugly(t *testing.T) {
|
||||
version = "0.8.0-beta.1"
|
||||
t.Cleanup(func() {
|
||||
version = ""
|
||||
})
|
||||
// Ends in '1' which is < 'a', so stable
|
||||
assert.Equal(t, "stable", updateChannel())
|
||||
}
|
||||
|
||||
func TestUpdate_AppVersion_Good(t *testing.T) {
|
||||
version = "1.2.3"
|
||||
t.Cleanup(func() {
|
||||
version = ""
|
||||
})
|
||||
assert.Equal(t, "1.2.3", appVersion())
|
||||
}
|
||||
|
||||
func TestUpdate_AppVersion_Good_Empty(t *testing.T) {
|
||||
func TestUpdate_AppVersion_Bad(t *testing.T) {
|
||||
version = ""
|
||||
assert.Equal(t, "dev", appVersion())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue