Move signal handling from internal/tui to pkg/core
WatchSignals and CtrlCExitWindow are generic — any frontend can use them. Removing them from the TUI package keeps tui focused on terminal I/O only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c5b16e739e
commit
dffce36dd4
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
//go:build !windows
|
||||
|
||||
package tui
|
||||
package core
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
haveSIGTERM = true
|
||||
SIGTERM = syscall.SIGTERM
|
||||
sigTERM = syscall.SIGTERM
|
||||
)
|
||||
|
|
@ -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)
|
||||
Reference in New Issue