fix(ax): make core-agent startup explicit

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-30 00:00:33 +00:00
parent 52a339f125
commit 3d7ec7efce
8 changed files with 231 additions and 25 deletions

View file

@ -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.
//

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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()

View file

@ -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 {

View file

@ -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),

View file

@ -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())
}