session: pause all sessions on shutdown, fix agent config loading
Shutdown changes: - Shutdown() now calls PauseAll() before closing sessions - PauseAll() interrupts agents then pauses each session - Sessions persist their state, allowing resume on next startup Agent loading fix: - LoadConfig returns (*AgentConfig, error) instead of *AgentConfig - Parse errors are no longer silently discarded - Callers handle nil config gracefully (no nil pointer dereferences)
This commit is contained in:
parent
c1888d31fe
commit
f85612a4da
|
|
@ -17,16 +17,20 @@ func AgentConfigPath(agentsDir, name string) string {
|
|||
}
|
||||
|
||||
// LoadConfig loads an agent configuration file by profile name.
|
||||
func LoadConfig(agentsDir, name string, open func(string) (*os.File, error)) *AgentConfig {
|
||||
// Returns (nil, nil) if the file does not exist.
|
||||
// Returns (nil, err) for parse errors.
|
||||
func LoadConfig(agentsDir, name string, open func(string) (*os.File, error)) (*AgentConfig, error) {
|
||||
if open == nil {
|
||||
open = os.Open
|
||||
}
|
||||
path := AgentConfigPath(agentsDir, name)
|
||||
f, err := open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
cfg, _ := Load(f)
|
||||
return cfg
|
||||
return Load(f)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,9 +170,10 @@ func Rename(oldName, newName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Shutdown interrupts all sessions, waits for idle, and closes them.
|
||||
// Shutdown pauses all sessions (to allow later resume), waits for idle, then closes them.
|
||||
func Shutdown() {
|
||||
InterruptAll()
|
||||
// First, pause all sessions to persist their state.
|
||||
PauseAll()
|
||||
waitIdle(100*time.Millisecond, 5*time.Second)
|
||||
|
||||
mu.Lock()
|
||||
|
|
@ -188,6 +189,8 @@ func Shutdown() {
|
|||
delete(sessions, name)
|
||||
mu.Unlock()
|
||||
if sess != nil {
|
||||
// Session is already paused, so Cancel/Keeper/Proc should be nil.
|
||||
// But close anything still open defensively.
|
||||
if sess.Cancel != nil {
|
||||
sess.Cancel()
|
||||
}
|
||||
|
|
@ -202,6 +205,25 @@ func Shutdown() {
|
|||
}
|
||||
}
|
||||
|
||||
// PauseAll pauses every active session. Agents are interrupted first.
|
||||
func PauseAll() {
|
||||
InterruptAll()
|
||||
mu.RLock()
|
||||
var toProcess []*Session
|
||||
for _, sess := range sessions {
|
||||
if !sess.IsPaused() {
|
||||
toProcess = append(toProcess, sess)
|
||||
}
|
||||
}
|
||||
mu.RUnlock()
|
||||
|
||||
for _, sess := range toProcess {
|
||||
if err := sess.Pause(); err != nil {
|
||||
pkgLog.Warn("pause session %s: %v", sess.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InterruptAll interrupts every active session.
|
||||
func InterruptAll() {
|
||||
mu.RLock()
|
||||
|
|
@ -380,7 +402,10 @@ func buildAgent(sess *Session, p AgentParams) (*agent.Agent, error) {
|
|||
return nil, fmt.Errorf("session cwd is required")
|
||||
}
|
||||
|
||||
cfg := agent.LoadConfig(pkgAgentsDir, p.Profile, nil)
|
||||
cfg, err := agent.LoadConfig(pkgAgentsDir, p.Profile, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load agent config %q: %w", p.Profile, err)
|
||||
}
|
||||
sessID := sess.ID
|
||||
|
||||
// Set up tool server — reuse existing session infrastructure
|
||||
|
|
@ -431,7 +456,7 @@ func buildAgent(sess *Session, p AgentParams) (*agent.Agent, error) {
|
|||
|
||||
// Create backend
|
||||
beName := p.Backend
|
||||
if beName == "" {
|
||||
if beName == "" && cfg != nil {
|
||||
beName = cfg.Backend
|
||||
}
|
||||
be, err := backend.NewWithName(beName)
|
||||
|
|
@ -442,7 +467,7 @@ func buildAgent(sess *Session, p AgentParams) (*agent.Agent, error) {
|
|||
|
||||
if p.Model != "" {
|
||||
rt.Backend.SetModel(p.Model)
|
||||
} else if cfg.Model != "" {
|
||||
} else if cfg != nil && cfg.Model != "" {
|
||||
rt.Backend.SetModel(cfg.Model)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -496,15 +496,23 @@ func (s *Session) Resume() error {
|
|||
|
||||
// rebuildAgentRuntime rebuilds the full runtime for an agent (used on resume).
|
||||
func rebuildAgentRuntime(ag *agent.Agent, sessID string) error {
|
||||
cfg := agent.LoadConfig(pkgAgentsDir, ag.Profile(), os.Open)
|
||||
cfg, err := agent.LoadConfig(pkgAgentsDir, ag.Profile(), os.Open)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load agent config %q: %w", ag.Profile(), err)
|
||||
}
|
||||
layers := BuildPromptLayers(cfg, ag.Cwd(), sessID, ag.ID(), "linux", false, "")
|
||||
env := []string{"OLLIE_SESSION_ID=" + sessID, "OLLIE_UNAME=" + ag.ID()}
|
||||
rt := agent.BuildRuntime(cfg, ag.ToolServer(), ag.Cwd(), env, layers.SystemPrompt, layers.EnvBlock)
|
||||
be, err := backend.NewWithName(cfg.Backend)
|
||||
|
||||
var beName string
|
||||
if cfg != nil {
|
||||
beName = cfg.Backend
|
||||
}
|
||||
be, err := backend.NewWithName(beName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("backend: %w", err)
|
||||
}
|
||||
if cfg.Model != "" {
|
||||
if cfg != nil && cfg.Model != "" {
|
||||
be.SetModel(cfg.Model)
|
||||
}
|
||||
rt.Backend = be
|
||||
|
|
|
|||
Loading…
Reference in New Issue