diff --git a/dbus.go b/dbus.go index 37c4677..9fb004e 100644 --- a/dbus.go +++ b/dbus.go @@ -137,7 +137,7 @@ func (a *DBusAdapter) startWatcher(id string, sess *mgr.Session) { // State watcher go func() { - current := sess.Core.State() + current := sess.Core.Agent().State() for { next, ok := sess.Core.WaitChange(ctx, agent.WatchState, current) if !ok { @@ -153,7 +153,7 @@ func (a *DBusAdapter) startWatcher(id string, sess *mgr.Session) { // Agent watcher — emits ConfigChanged when the agent name changes go func() { - current := sess.Core.AgentName() + current := sess.Core.Agent().Name() for { next, ok := sess.Core.WaitChange(ctx, "agent", current) if !ok { @@ -275,7 +275,7 @@ func (a *DBusAdapter) ListSessions() ([]string, *dbus.Error) { continue } result = append(result, fmt.Sprintf("%s\t%s\t%s\t%s", - id, sess.Core.State(), sess.Core.ModelName(), sess.Core.AgentName())) + id, sess.Core.Agent().State(), sess.Core.Agent().ModelName(), sess.Core.Agent().Name())) } return result, nil } @@ -301,7 +301,7 @@ func (a *DBusAdapter) Submit(sessionID, prompt string) (bool, *dbus.Error) { if sess == nil { return false, nil } - go sess.Core.Submit(sess.Ctx, prompt) + go sess.Core.Agent().Submit(sess.Ctx, prompt) return true, nil } @@ -314,7 +314,7 @@ func (a *DBusAdapter) ReactTo(sessionID, responseID, emoji string) (bool, *dbus. if sess == nil { return false, nil } - if err := sess.Core.ReactTo(responseID, emoji); err != nil { + if err := sess.Core.Agent().React(responseID, emoji); err != nil { return false, dbus.MakeFailedError(err) } return true, nil @@ -334,7 +334,7 @@ func (a *DBusAdapter) GetState(sessionID string) (string, *dbus.Error) { if sess == nil { return "", nil } - return sess.Core.State(), nil + return sess.Core.Agent().State(), nil } func (a *DBusAdapter) GetChat(sessionID string, offset int64) (string, int64, *dbus.Error) { @@ -361,7 +361,7 @@ func (a *DBusAdapter) GetUsage(sessionID string) (string, *dbus.Error) { if sess == nil { return "", nil } - return sess.Core.Usage(), nil + return sess.Core.Agent().UsageStr(), nil } func (a *DBusAdapter) GetCost(sessionID string) (string, *dbus.Error) { @@ -369,7 +369,7 @@ func (a *DBusAdapter) GetCost(sessionID string) (string, *dbus.Error) { if sess == nil { return "", nil } - return sess.Core.Cost(), nil + return sess.Core.Agent().CostStr(), nil } func (a *DBusAdapter) GetConfig(sessionID string) (string, *dbus.Error) { @@ -378,11 +378,11 @@ func (a *DBusAdapter) GetConfig(sessionID string) (string, *dbus.Error) { return "", nil } var sb strings.Builder - fmt.Fprintf(&sb, "model=%s\n", sess.Core.ModelName()) - fmt.Fprintf(&sb, "backend=%s\n", sess.Core.BackendName()) - fmt.Fprintf(&sb, "agent=%s\n", sess.Core.AgentName()) + fmt.Fprintf(&sb, "model=%s\n", sess.Core.Agent().ModelName()) + fmt.Fprintf(&sb, "backend=%s\n", sess.Core.Agent().BackendName()) + fmt.Fprintf(&sb, "agent=%s\n", sess.Core.Agent().Name()) fmt.Fprintf(&sb, "cwd=%s\n", sess.Core.CWD()) - fmt.Fprintf(&sb, "state=%s\n", sess.Core.State()) + fmt.Fprintf(&sb, "state=%s\n", sess.Core.Agent().State()) return sb.String(), nil } @@ -393,7 +393,7 @@ func (a *DBusAdapter) SetConfig(sessionID, key, value string) (bool, *dbus.Error } switch key { case "model": - sess.Core.Submit(sess.Ctx, "/model "+value) + sess.Core.Agent().Submit(sess.Ctx, "/model "+value) case "cwd": if err := sess.Core.SetCWD(value); err != nil { return false, nil @@ -409,7 +409,7 @@ func (a *DBusAdapter) GetReactions(sessionID string) (string, *dbus.Error) { if sess == nil { return "{}", nil } - data, err := json.Marshal(sess.Core.Reactions()) + data, err := json.Marshal(sess.Core.Agent().Reactions()) if err != nil { return "", dbus.MakeFailedError(err) } @@ -421,7 +421,7 @@ func (a *DBusAdapter) GetContext(sessionID string) (string, *dbus.Error) { if sess == nil { return "", nil } - msgs := sess.Core.Context() + msgs := sess.Core.Agent().Context() var sb strings.Builder enc := json.NewEncoder(&sb) enc.SetEscapeHTML(false) @@ -655,7 +655,7 @@ func (a *DBusAdapter) GetSystemPrompt(sessionID string) (string, *dbus.Error) { if sess == nil { return "", nil } - return sess.Core.SystemPrompt(), nil + return sess.Core.Agent().SystemPrompt(), nil } func (a *DBusAdapter) GetContextSize(sessionID string) (string, *dbus.Error) { @@ -663,7 +663,7 @@ func (a *DBusAdapter) GetContextSize(sessionID string) (string, *dbus.Error) { if sess == nil { return "", nil } - return sess.Core.CtxSz(), nil + return sess.Core.Agent().CtxSz(), nil } func (a *DBusAdapter) GetPreviousPrompt(sessionID string) (string, *dbus.Error) { @@ -682,7 +682,7 @@ func (a *DBusAdapter) Compact(sessionID string) (bool, *dbus.Error) { if sess == nil { return false, nil } - sess.Core.Submit(sess.Ctx, "/compact") + sess.Core.Agent().Submit(sess.Ctx, "/compact") return true, nil } @@ -691,7 +691,7 @@ func (a *DBusAdapter) ClearContext(sessionID string) (bool, *dbus.Error) { if sess == nil { return false, nil } - sess.Core.Submit(sess.Ctx, "/clear") + sess.Core.Agent().Submit(sess.Ctx, "/clear") return true, nil } diff --git a/main_test.go b/main_test.go index b19a9c5..9183243 100644 --- a/main_test.go +++ b/main_test.go @@ -326,7 +326,7 @@ func TestSessionManagerInterruptAll(t *testing.T) { defer sess.Cancel() // Submit in background to make it "running" - go sess.Core.Submit(sess.Ctx, "hello") + go sess.Core.Agent().Submit(sess.Ctx, "hello") time.Sleep(50 * time.Millisecond) // let it enter thinking state s.AddSession(sess) @@ -381,7 +381,7 @@ func TestSessionManagerRenameErrors(t *testing.T) { } // running sess := testBlockingSession("r") - go sess.Core.Submit(sess.Ctx, "hello") + go sess.Core.Agent().Submit(sess.Ctx, "hello") time.Sleep(50 * time.Millisecond) s.AddSession(sess) if err := s.Tree().Rename("r", "r2"); err == nil { @@ -566,8 +566,8 @@ func TestSessionFileStoreWritePrompt(t *testing.T) { testStoreWrite(t, sf, "prompt", []byte("hello agent")) // Verify state is back to idle (Submit completed) time.Sleep(50 * time.Millisecond) - if sess.Core.State() != "idle" { - t.Errorf("state = %q; want idle after submit", sess.Core.State()) + if sess.Core.Agent().State() != "idle" { + t.Errorf("state = %q; want idle after submit", sess.Core.Agent().State()) } } @@ -577,7 +577,7 @@ func TestSessionFileStoreWriteFifoIn(t *testing.T) { sf := newTestSessionFileStore(t, sess) testStoreWrite(t, sf, "fifo.in", []byte("inject this")) - item, ok := sess.Core.PopQueue() + item, ok := sess.Core.Agent().PopQueue() if !ok || item != "inject this" { t.Errorf("PopQueue() = %q, %v; want inject this, true", item, ok) } @@ -606,14 +606,14 @@ func TestSessionFileStoreWriteBackendModelAgent(t *testing.T) { // With real session + NewBackend func, it switches backend testStoreWrite(t, sf, "cfg", []byte("backend=openai")) time.Sleep(50 * time.Millisecond) - if sess.Core.BackendName() != "openai" { - t.Errorf("BackendName() = %q; want openai", sess.Core.BackendName()) + if sess.Core.Agent().BackendName() != "openai" { + t.Errorf("BackendName() = %q; want openai", sess.Core.Agent().BackendName()) } testStoreWrite(t, sf, "cfg", []byte("model=gpt-4")) time.Sleep(50 * time.Millisecond) - if sess.Core.ModelName() != "gpt-4" { - t.Errorf("ModelName() = %q; want gpt-4", sess.Core.ModelName()) + if sess.Core.Agent().ModelName() != "gpt-4" { + t.Errorf("ModelName() = %q; want gpt-4", sess.Core.Agent().ModelName()) } } @@ -622,7 +622,7 @@ func TestSessionFileStoreWriteBackendWhileRunning(t *testing.T) { defer sess.Cancel() // Make session running - go sess.Core.Submit(sess.Ctx, "hello") + go sess.Core.Agent().Submit(sess.Ctx, "hello") time.Sleep(50 * time.Millisecond) sf := newTestSessionFileStore(t, sess) @@ -647,7 +647,7 @@ func TestSessionFileStoreWriteParams(t *testing.T) { sf := newTestSessionFileStore(t, sess) testStoreWrite(t, sf, "cfg", []byte("maxTokens=2048")) - p := sess.Core.GenerationParams() + p := sess.Core.Agent().GenParams() if p.MaxTokens != 2048 { t.Errorf("MaxTokens = %d; want 2048", p.MaxTokens) } @@ -657,7 +657,7 @@ func TestSessionFileStoreWriteParamsWhileRunning(t *testing.T) { sess := testBlockingSession("s1") defer sess.Cancel() - go sess.Core.Submit(sess.Ctx, "hello") + go sess.Core.Agent().Submit(sess.Ctx, "hello") time.Sleep(50 * time.Millisecond) sf := newTestSessionFileStore(t, sess) @@ -673,7 +673,7 @@ func TestSessionFileStoreHandleCtl(t *testing.T) { defer sess.Cancel() // Make session running for interrupt test - go sess.Core.Submit(sess.Ctx, "hello") + go sess.Core.Agent().Submit(sess.Ctx, "hello") time.Sleep(50 * time.Millisecond) sf := newTestSessionFileStore(t, sess) @@ -681,7 +681,7 @@ func TestSessionFileStoreHandleCtl(t *testing.T) { // stop — interrupts the running session testStoreWrite(t, sf, "ctl", []byte("stop")) time.Sleep(50 * time.Millisecond) - if sess.Core.IsRunning() { + if sess.Core.Agent().IsRunning() { t.Error("ctl stop should interrupt (session should not be running)") } @@ -730,7 +730,7 @@ func TestSessionFileStoreBlockingRead(t *testing.T) { time.Sleep(100 * time.Millisecond) // Submit triggers state change: idle → thinking - go sess.Core.Submit(sess.Ctx, "trigger") + go sess.Core.Agent().Submit(sess.Ctx, "trigger") select { case v := <-done: @@ -1067,12 +1067,12 @@ func TestSessionInterrupt(t *testing.T) { sess := testBlockingSession("s1") defer sess.Cancel() - go sess.Core.Submit(sess.Ctx, "hello") + go sess.Core.Agent().Submit(sess.Ctx, "hello") time.Sleep(50 * time.Millisecond) sess.Interrupt() time.Sleep(50 * time.Millisecond) - if sess.Core.IsRunning() { + if sess.Core.Agent().IsRunning() { t.Error("session should not be running after interrupt") } } diff --git a/mgr/create.go b/mgr/create.go index 0e927ac..f989f45 100644 --- a/mgr/create.go +++ b/mgr/create.go @@ -241,10 +241,10 @@ func (s *Manager) CreateSession(args []string) (string, error) { s.mu.Unlock() // HACK: Load the agent now that the session mount is active. - core.Submit(ctx, "/agent "+agentName) + core.Agent().Submit(ctx, "/agent "+agentName) s.cfg.Log.Info("new session %s (backend=%s model=%s agent=%s)", - sessID, core.BackendName(), core.ModelName(), core.AgentName()) + sessID, core.Agent().BackendName(), core.Agent().ModelName(), core.Agent().Name()) if s.cfg.OnSessionCreated != nil { s.cfg.OnSessionCreated(sessID, sess) } @@ -271,7 +271,7 @@ func (s *Manager) renameSession(old, new string) error { s.mu.Unlock() return fmt.Errorf("session already exists: %s", newID) } - if sess.Core.IsRunning() { + if sess.Core.Agent().IsRunning() { s.mu.Unlock() return fmt.Errorf("cannot rename while agent is running") } diff --git a/mgr/manager.go b/mgr/manager.go index bf2db80..a177810 100644 --- a/mgr/manager.go +++ b/mgr/manager.go @@ -286,7 +286,7 @@ func (s *Manager) PeerSubmit(sessID, peerID, prompt string) error { if !isPeer { return fmt.Errorf("%s is not a peer of %s", peerID, sessID) } - go peer.Core.Submit(peer.Ctx, prompt) + go peer.Core.Agent().Submit(peer.Ctx, prompt) return nil } diff --git a/mgr/persist.go b/mgr/persist.go index c57e1ea..58b0ee4 100644 --- a/mgr/persist.go +++ b/mgr/persist.go @@ -291,7 +291,7 @@ func (s *Manager) waitIdle(poll, timeout time.Duration) { allIdle := true s.mu.RLock() for _, sess := range s.sessions { - if sess.Core.State() != "idle" { + if sess.Core.Agent().State() != "idle" { allIdle = false break } @@ -332,11 +332,11 @@ func (s *Manager) index() []byte { for _, id := range ids { sess := s.sessions[id] sess.mu.RLock() - state := sess.Core.State() + state := sess.Core.Agent().State() cwd := sess.Core.CWD() - be := sess.Core.BackendName() - model := sess.Core.ModelName() - agent := sess.Core.AgentName() + be := sess.Core.Agent().BackendName() + model := sess.Core.Agent().ModelName() + agent := sess.Core.Agent().Name() sess.mu.RUnlock() fmt.Fprintf(&sb, "%s\t%s\t%s\t%s\t%s\t%s\n", id, state, cwd, be, model, agent) } diff --git a/mgr/session.go b/mgr/session.go index 0b71f63..16083b8 100644 --- a/mgr/session.go +++ b/mgr/session.go @@ -3,6 +3,7 @@ package mgr import ( "context" "sync" + "strings" "time" "ollie/session" @@ -53,7 +54,8 @@ func (sess *Session) CachedListModels() string { sess.modelsMu.Unlock() // Cache miss — fetch and store. - result := sess.Core.ListModels() + models := sess.Core.Agent().ListModels() + result := strings.Join(models, "\n") sess.modelsMu.Lock() sess.modelsCache = result sess.modelsCacheAt = time.Now() diff --git a/mgr/sessionfile.go b/mgr/sessionfile.go index 179f64f..9d0bfc0 100644 --- a/mgr/sessionfile.go +++ b/mgr/sessionfile.go @@ -129,7 +129,7 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) fs.FileSpec { } case "reactions": fs.Read = func() ([]byte, error) { - data, err := json.Marshal(h.sess.Core.Reactions()) + data, err := json.Marshal(h.sess.Core.Agent().Reactions()) if err != nil { return nil, err } @@ -151,7 +151,7 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) fs.FileSpec { } case "fifo.out": fs.Read = func() ([]byte, error) { - item, ok := h.sess.Core.PopQueue() + item, ok := h.sess.Core.Agent().PopQueue() if !ok { return nil, nil } @@ -203,7 +203,7 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) fs.FileSpec { h.sess.prevPrompt = []byte(input) h.sess.mu.Unlock() go func() { - h.sess.Core.Submit(h.sess.Ctx, input) + h.sess.Core.Agent().Submit(h.sess.Ctx, input) h.sess.EnsureTrailingNewline() }() return nil @@ -264,7 +264,7 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) fs.FileSpec { if req.Emoji == "" { return fmt.Errorf("react: emoji is required") } - return h.sess.Core.ReactTo(req.ResponseID, req.Emoji) + return h.sess.Core.Agent().React(req.ResponseID, req.Emoji) } } @@ -275,11 +275,11 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) fs.FileSpec { defer cancel() context.AfterFunc(h.sess.Ctx, cancel) if base == "" { - base = h.sess.Core.State() + base = h.sess.Core.Agent().State() } v, ok := h.sess.Core.WaitChange(ctx, agent.WatchState, base) if !ok { - s := h.sess.Core.State() + s := h.sess.Core.Agent().State() return []byte(s + "\n"), s, nil } return []byte(v + "\n"), v, nil @@ -294,18 +294,18 @@ func (h *sessionHelper) content(name string) string { defer h.sess.mu.RUnlock() switch name { case "usage": - return h.sess.Core.Usage() + "\n" + return h.sess.Core.Agent().UsageStr() + "\n" case "cost": - return h.sess.Core.Cost() + return h.sess.Core.Agent().CostStr() case "ctxsz": - return h.sess.Core.CtxSz() + "\n" + return h.sess.Core.Agent().CtxSz() + "\n" case "models": h.log.Debug("content(models) called") return h.sess.CachedListModels() + "\n" case "systemprompt": - return h.sess.Core.SystemPrompt() + return h.sess.Core.Agent().SystemPrompt() case "state": - return h.sess.Core.State() + "\n" + return h.sess.Core.Agent().State() + "\n" case "offset": return fmt.Sprintf("%d\n", h.sess.ChatOffset) case "env": @@ -316,7 +316,7 @@ func (h *sessionHelper) content(name string) string { var sb strings.Builder enc := json.NewEncoder(&sb) enc.SetEscapeHTML(false) - for _, m := range h.sess.Core.Context() { + for _, m := range h.sess.Core.Agent().Context() { enc.Encode(m) } return sb.String() @@ -374,12 +374,12 @@ func (h *sessionHelper) envContent() string { func (h *sessionHelper) cfgContent() string { h.sess.mu.RLock() defer h.sess.mu.RUnlock() - p := h.sess.Core.GenerationParams() + p := h.sess.Core.Agent().GenParams() var sb strings.Builder fmt.Fprintf(&sb, "name=%s\n", h.sess.id) - fmt.Fprintf(&sb, "backend=%s\n", h.sess.Core.BackendName()) - fmt.Fprintf(&sb, "model=%s\n", h.sess.Core.ModelName()) - fmt.Fprintf(&sb, "agent=%s\n", h.sess.Core.AgentName()) + fmt.Fprintf(&sb, "backend=%s\n", h.sess.Core.Agent().BackendName()) + fmt.Fprintf(&sb, "model=%s\n", h.sess.Core.Agent().ModelName()) + fmt.Fprintf(&sb, "agent=%s\n", h.sess.Core.Agent().Name()) fmt.Fprintf(&sb, "cwd=%s\n", h.sess.Core.CWD()) fmt.Fprintf(&sb, "remote=%s\n", h.sess.remote) fmt.Fprintf(&sb, "maxTokens=%d\n", p.MaxTokens) @@ -450,12 +450,12 @@ func (h *sessionHelper) cfgContent() string { } else { sb.WriteString("verbosity=\n") } - fmt.Fprintf(&sb, "compactionModel=%s\n", h.sess.Core.CompactionModel()) + fmt.Fprintf(&sb, "compactionModel=%s\n", h.sess.Core.Agent().CompactionModel()) return sb.String() } func (h *sessionHelper) handleCfg(input string) error { - p := h.sess.Core.GenerationParams() + p := h.sess.Core.Agent().GenParams() hasParams := false var deferredBackend, deferredModel string for _, line := range strings.Split(input, "\n") { @@ -473,7 +473,7 @@ func (h *sessionHelper) handleCfg(input string) error { if v == "" { continue } - if h.sess.Core.IsRunning() { + if h.sess.Core.Agent().IsRunning() { return fmt.Errorf("cannot rename while agent is running") } if err := h.rename(v); err != nil { @@ -483,15 +483,15 @@ func (h *sessionHelper) handleCfg(input string) error { if v == "" { continue } - if h.sess.Core.IsRunning() { + if h.sess.Core.Agent().IsRunning() { return fmt.Errorf("cannot switch %s while agent is running", k) } - h.sess.Core.Submit(h.sess.Ctx, "/agent "+v) + h.sess.Core.Agent().Submit(h.sess.Ctx, "/agent "+v) case "backend", "model": if v == "" { continue } - if h.sess.Core.IsRunning() { + if h.sess.Core.Agent().IsRunning() { return fmt.Errorf("cannot switch %s while agent is running", k) } // Defer backend/model until after the loop so they override @@ -610,22 +610,22 @@ func (h *sessionHelper) handleCfg(input string) error { hasParams = true p.Verbosity = v case "compactionModel": - h.sess.Core.SetCompactionModel(v) + h.sess.Core.Agent().SetCompactionModel(v) // state → read-only, silently ignored } } // Apply backend/model after agent= so manual overrides win. if deferredBackend != "" { - h.sess.Core.Submit(h.sess.Ctx, "/backend "+deferredBackend) + h.sess.Core.Agent().Submit(h.sess.Ctx, "/backend "+deferredBackend) } if deferredModel != "" { - h.sess.Core.Submit(h.sess.Ctx, "/model "+deferredModel) + h.sess.Core.Agent().Submit(h.sess.Ctx, "/model "+deferredModel) } if hasParams { - if h.sess.Core.IsRunning() { + if h.sess.Core.Agent().IsRunning() { return fmt.Errorf("cannot change params while agent is running") } - return h.sess.Core.SetGenerationParams(p) + h.sess.Core.Agent().SetGenParams(p) } return nil } @@ -680,7 +680,7 @@ func (h *sessionHelper) handleCtl(input string) error { "agents", "agent", "sessions", "cwd", "skills", "tools", "context", "usage", "cost", "history", "irw", "help": - h.sess.Core.Submit(h.sess.Ctx, "/"+input) + h.sess.Core.Agent().Submit(h.sess.Ctx, "/"+input) default: return fmt.Errorf("unknown ctl command: %s", cmd[0]) } diff --git a/olliesrv.test b/olliesrv.test new file mode 100755 index 0000000..bee777d Binary files /dev/null and b/olliesrv.test differ