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.
|
// 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 {
|
if open == nil {
|
||||||
open = os.Open
|
open = os.Open
|
||||||
}
|
}
|
||||||
path := AgentConfigPath(agentsDir, name)
|
path := AgentConfigPath(agentsDir, name)
|
||||||
f, err := open(path)
|
f, err := open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
cfg, _ := Load(f)
|
return Load(f)
|
||||||
return cfg
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,9 +170,10 @@ func Rename(oldName, newName string) error {
|
||||||
return nil
|
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() {
|
func Shutdown() {
|
||||||
InterruptAll()
|
// First, pause all sessions to persist their state.
|
||||||
|
PauseAll()
|
||||||
waitIdle(100*time.Millisecond, 5*time.Second)
|
waitIdle(100*time.Millisecond, 5*time.Second)
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
|
|
@ -188,6 +189,8 @@ func Shutdown() {
|
||||||
delete(sessions, name)
|
delete(sessions, name)
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
if sess != nil {
|
if sess != nil {
|
||||||
|
// Session is already paused, so Cancel/Keeper/Proc should be nil.
|
||||||
|
// But close anything still open defensively.
|
||||||
if sess.Cancel != nil {
|
if sess.Cancel != nil {
|
||||||
sess.Cancel()
|
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.
|
// InterruptAll interrupts every active session.
|
||||||
func InterruptAll() {
|
func InterruptAll() {
|
||||||
mu.RLock()
|
mu.RLock()
|
||||||
|
|
@ -380,7 +402,10 @@ func buildAgent(sess *Session, p AgentParams) (*agent.Agent, error) {
|
||||||
return nil, fmt.Errorf("session cwd is required")
|
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
|
sessID := sess.ID
|
||||||
|
|
||||||
// Set up tool server — reuse existing session infrastructure
|
// Set up tool server — reuse existing session infrastructure
|
||||||
|
|
@ -431,7 +456,7 @@ func buildAgent(sess *Session, p AgentParams) (*agent.Agent, error) {
|
||||||
|
|
||||||
// Create backend
|
// Create backend
|
||||||
beName := p.Backend
|
beName := p.Backend
|
||||||
if beName == "" {
|
if beName == "" && cfg != nil {
|
||||||
beName = cfg.Backend
|
beName = cfg.Backend
|
||||||
}
|
}
|
||||||
be, err := backend.NewWithName(beName)
|
be, err := backend.NewWithName(beName)
|
||||||
|
|
@ -442,7 +467,7 @@ func buildAgent(sess *Session, p AgentParams) (*agent.Agent, error) {
|
||||||
|
|
||||||
if p.Model != "" {
|
if p.Model != "" {
|
||||||
rt.Backend.SetModel(p.Model)
|
rt.Backend.SetModel(p.Model)
|
||||||
} else if cfg.Model != "" {
|
} else if cfg != nil && cfg.Model != "" {
|
||||||
rt.Backend.SetModel(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).
|
// rebuildAgentRuntime rebuilds the full runtime for an agent (used on resume).
|
||||||
func rebuildAgentRuntime(ag *agent.Agent, sessID string) error {
|
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, "")
|
layers := BuildPromptLayers(cfg, ag.Cwd(), sessID, ag.ID(), "linux", false, "")
|
||||||
env := []string{"OLLIE_SESSION_ID=" + sessID, "OLLIE_UNAME=" + ag.ID()}
|
env := []string{"OLLIE_SESSION_ID=" + sessID, "OLLIE_UNAME=" + ag.ID()}
|
||||||
rt := agent.BuildRuntime(cfg, ag.ToolServer(), ag.Cwd(), env, layers.SystemPrompt, layers.EnvBlock)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("backend: %w", err)
|
return fmt.Errorf("backend: %w", err)
|
||||||
}
|
}
|
||||||
if cfg.Model != "" {
|
if cfg != nil && cfg.Model != "" {
|
||||||
be.SetModel(cfg.Model)
|
be.SetModel(cfg.Model)
|
||||||
}
|
}
|
||||||
rt.Backend = be
|
rt.Backend = be
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue