diff --git a/internal/tui/signals.go b/internal/tui/signals.go deleted file mode 100644 index 33b803c..0000000 --- a/internal/tui/signals.go +++ /dev/null @@ -1,40 +0,0 @@ -package tui - -import ( - "context" - "fmt" - "io" - "os" - "os/signal" - "time" - - "ollie/pkg/core" -) - -const ctrlCExitWindow = 750 * time.Millisecond - -func startSignalWatcher(appCancel context.CancelCauseFunc, c core.Core, errStream io.Writer) { - ch := make(chan os.Signal, 16) - signals := []os.Signal{os.Interrupt} - if haveSIGTERM { - signals = append(signals, SIGTERM) - } - signal.Notify(ch, signals...) - - go func() { - for sig := range ch { - switch sig { - case SIGTERM: - signal.Stop(ch) - close(ch) - fmt.Fprint(errStream, "\n(terminated)\n") - appCancel(context.Canceled) - continue - case os.Interrupt: - if c.Interrupt(core.ErrInterrupted) { - fmt.Fprint(errStream, "\n^C\n") - } - } - } - }() -} diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 40aa36c..d504fc7 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -80,7 +80,7 @@ func (t *TUI) Run(ctx context.Context) { t.split = newSplitInput(tt, tt.Output(), t.core.Prompt(), nil) appCtx, appCancel := context.WithCancelCause(ctx) - startSignalWatcher(appCancel, t.core, os.Stderr) + core.WatchSignals(appCancel, t.core, os.Stderr) var lastCtrlC time.Time firstRead := true @@ -101,7 +101,7 @@ func (t *TUI) Run(ctx context.Context) { errs := err.Error() if errs == "interrupted" || errs == "^C" { now := time.Now() - if !lastCtrlC.IsZero() && now.Sub(lastCtrlC) <= ctrlCExitWindow { + if !lastCtrlC.IsZero() && now.Sub(lastCtrlC) <= core.CtrlCExitWindow { break } lastCtrlC = now diff --git a/pkg/core/signals.go b/pkg/core/signals.go new file mode 100644 index 0000000..69aaf99 --- /dev/null +++ b/pkg/core/signals.go @@ -0,0 +1,41 @@ +package core + +import ( + "context" + "fmt" + "io" + "os" + "os/signal" + "time" +) + +const CtrlCExitWindow = 750 * time.Millisecond + +// WatchSignals installs OS signal handlers for the lifetime of the program. +// SIGTERM cancels the app context; SIGINT interrupts the current agent turn. +// It is safe to call from any frontend (TUI, HTTP, CLI one-shot, etc.). +func WatchSignals(appCancel context.CancelCauseFunc, c Core, errStream io.Writer) { + ch := make(chan os.Signal, 16) + signals := []os.Signal{os.Interrupt} + if haveSIGTERM { + signals = append(signals, sigTERM) + } + signal.Notify(ch, signals...) + + go func() { + for sig := range ch { + switch sig { + case sigTERM: + signal.Stop(ch) + close(ch) + fmt.Fprint(errStream, "\n(terminated)\n") + appCancel(context.Canceled) + continue + case os.Interrupt: + if c.Interrupt(ErrInterrupted) { + fmt.Fprint(errStream, "\n^C\n") + } + } + } + }() +} diff --git a/internal/tui/signals_unix.go b/pkg/core/signals_unix.go similarity index 61% rename from internal/tui/signals_unix.go rename to pkg/core/signals_unix.go index 9d47a98..7870403 100644 --- a/internal/tui/signals_unix.go +++ b/pkg/core/signals_unix.go @@ -1,10 +1,10 @@ //go:build !windows -package tui +package core import "syscall" const ( haveSIGTERM = true - SIGTERM = syscall.SIGTERM + sigTERM = syscall.SIGTERM ) diff --git a/internal/tui/signals_windows.go b/pkg/core/signals_windows.go similarity index 59% rename from internal/tui/signals_windows.go rename to pkg/core/signals_windows.go index e55558f..9a67a30 100644 --- a/internal/tui/signals_windows.go +++ b/pkg/core/signals_windows.go @@ -1,9 +1,9 @@ //go:build windows -package tui +package core import "os" const haveSIGTERM = false -var SIGTERM = os.Signal(nil) +var sigTERM = os.Signal(nil)