diff --git a/contract.go b/contract.go index c181c91..595c0fb 100644 --- a/contract.go +++ b/contract.go @@ -112,10 +112,6 @@ func New(opts ...CoreOption) Result { } } - // Post-construction: discover IPC handlers on all registered services. - // Services that implement HandleIPCEvents get auto-wired to the bus. - c.discoverHandlers() - return Result{c, true} } @@ -135,10 +131,8 @@ func WithOptions(opts Options) CoreOption { } // WithService registers a service via its factory function. -// The factory receives *Core so the service can wire IPC handlers -// and access other subsystems during construction. -// Service name is auto-discovered from the package path. -// If the service implements HandleIPCEvents, it is auto-registered. +// The factory receives *Core and is responsible for calling c.Service() +// to register itself, and c.RegisterAction() for IPC handlers. // // core.WithService(agentic.Register) // core.WithService(display.Register(nil)) diff --git a/core.go b/core.go index e89c835..fb9c5d9 100644 --- a/core.go +++ b/core.go @@ -7,7 +7,6 @@ package core import ( "context" - "reflect" "sync" "sync/atomic" ) @@ -81,28 +80,4 @@ func (c *Core) Must(err error, op, msg string) { c.log.Must(err, op, msg) } -// --- Post-Construction --- - -// discoverHandlers scans Config for service instances that implement HandleIPCEvents. -// Called once after all WithService options have run — services are fully registered. -func (c *Core) discoverHandlers() { - if c.config == nil || c.config.ConfigOptions == nil || c.config.Settings == nil { - return - } - c.config.mu.RLock() - defer c.config.mu.RUnlock() - for _, val := range c.config.Settings { - if val == nil { - continue - } - instanceValue := reflect.ValueOf(val) - handlerMethod := instanceValue.MethodByName("HandleIPCEvents") - if handlerMethod.IsValid() { - if handler, ok := handlerMethod.Interface().(func(*Core, Message) Result); ok { - c.RegisterAction(handler) - } - } - } -} - // --- Global Instance --- diff --git a/service.go b/service.go index 1e82dd6..3424769 100644 --- a/service.go +++ b/service.go @@ -2,9 +2,13 @@ // Service registry for the Core framework. // -// Register a service: +// Register a service (DTO with lifecycle hooks): // -// c.Service("auth", core.Service{}) +// c.Service("auth", core.Service{OnStart: startFn}) +// +// Register a service instance (auto-discovers Startable/Stoppable/HandleIPCEvents): +// +// c.RegisterService("display", displayInstance) // // Get a service: // @@ -13,11 +17,10 @@ package core -// No imports needed — uses package-level string helpers. - // Service is a managed component with optional lifecycle. type Service struct { Name string + Instance any // the raw service instance (for interface discovery) Options Options OnStart func() Result OnStop func() Result @@ -66,6 +69,77 @@ func (c *Core) Service(name string, service ...Service) Result { return Result{OK: true} } +// RegisterService registers a service instance by name. +// Auto-discovers Startable, Stoppable, and HandleIPCEvents interfaces +// on the instance and wires them into the lifecycle and IPC bus. +// +// c.RegisterService("display", displayInstance) +func (c *Core) RegisterService(name string, instance any) Result { + if name == "" { + return Result{E("core.RegisterService", "service name cannot be empty", nil), false} + } + + c.Lock("srv").Mutex.Lock() + defer c.Lock("srv").Mutex.Unlock() + + if c.services.locked { + return Result{E("core.RegisterService", Concat("service \"", name, "\" not permitted — registry locked"), nil), false} + } + if _, exists := c.services.services[name]; exists { + return Result{E("core.RegisterService", Join(" ", "service", name, "already registered"), nil), false} + } + + srv := &Service{Name: name, Instance: instance} + + // Auto-discover lifecycle interfaces + if s, ok := instance.(Startable); ok { + srv.OnStart = func() Result { + if err := s.OnStartup(c.context); err != nil { + return Result{err, false} + } + return Result{OK: true} + } + } + if s, ok := instance.(Stoppable); ok { + srv.OnStop = func() Result { + if err := s.OnShutdown(c.context); err != nil { + return Result{err, false} + } + return Result{OK: true} + } + } + + c.services.services[name] = srv + + // Auto-discover IPC handler + if handler, ok := instance.(interface { + HandleIPCEvents(*Core, Message) Result + }); ok { + c.ipc.ipcMu.Lock() + c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handler.HandleIPCEvents) + c.ipc.ipcMu.Unlock() + } + + return Result{OK: true} +} + +// ServiceFor retrieves a registered service by name and asserts its type. +// +// prep, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic") +func ServiceFor[T any](c *Core, name string) (T, bool) { + var zero T + r := c.Service(name) + if !r.OK { + return zero, false + } + svc := r.Value.(*Service) + if svc.Instance == nil { + return zero, false + } + typed, ok := svc.Instance.(T) + return typed, ok +} + // Services returns all registered service names. // // names := c.Services()