remove D-Bus adapter from olliesrv
Delete dbus/dbus.go — the entire D-Bus SessionManager service is gone. No more 150ms poll loop, no more signal emission, no more method handlers. - Remove ollie/dbus import, nodbus flag, dbusAdapter variable - Remove OnSessionCreated/Killed/Renamed hooks from Config - Remove EnableDBus from Config - Elevation notifications connect to session bus directly (godbus stays as a dependency solely for org.freedesktop.Notifications) All clients now use 9P exclusively. D-Bus is dead.
This commit is contained in:
parent
622ede8c21
commit
43d0db520a
|
|
@ -1 +0,0 @@
|
|||
# Placeholder — real config copied during build (just ollie-remote)
|
||||
|
|
@ -17,12 +17,13 @@ import (
|
|||
"9fans.net/go/plan9/client"
|
||||
"ollie/agent"
|
||||
"ollie/backend"
|
||||
oldbus "ollie/dbus"
|
||||
"ollie/elevate"
|
||||
"ollie/env"
|
||||
"ollie/fs/session"
|
||||
olog "ollie/log"
|
||||
"ollie/mount"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"ollie/paths"
|
||||
"ollie/skills"
|
||||
"ollie/toolsrv"
|
||||
|
|
@ -33,8 +34,7 @@ const serviceName = "ollie"
|
|||
var tcpAddr = flag.String("tcp", "", "also listen on TCP address (e.g. :564)")
|
||||
var strict = flag.Bool("strict", false, "only allow tool steps; reject inline code")
|
||||
var yolo = flag.Bool("yolo", false, "skip landrun sandbox for shell")
|
||||
var nodbus = flag.Bool("nodbus", false, "disable D-Bus adapter (for headless/container use)")
|
||||
var no9p = flag.Bool("no9p", false, "disable 9P listener (D-Bus only mode)")
|
||||
var no9p = flag.Bool("no9p", false, "disable 9P listener")
|
||||
|
||||
// ModelCache caches the list of models from all backends.
|
||||
type ModelCache struct {
|
||||
|
|
@ -141,12 +141,6 @@ func cmdMount() {
|
|||
func runServer(sockPath string) {
|
||||
env.EnsureDefaults()
|
||||
|
||||
// -no9p requires D-Bus (otherwise there's no interface at all)
|
||||
if *no9p && *nodbus {
|
||||
fmt.Fprintln(os.Stderr, "error: -no9p and -nodbus are mutually exclusive")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Remove stale socket
|
||||
if !*no9p {
|
||||
if _, err := os.Stat(sockPath); err == nil {
|
||||
|
|
@ -168,9 +162,6 @@ func runServer(sockPath string) {
|
|||
// Create the skills registry
|
||||
skillsRegistry := skills.NewRegistry()
|
||||
|
||||
// D-Bus adapter (initialized after manager so callbacks can reference it).
|
||||
var dbusAdapter *oldbus.DBusAdapter
|
||||
|
||||
// Elevate broker (initialized after manager; closures capture the pointer).
|
||||
var elevateBroker *elevate.Broker
|
||||
|
||||
|
|
@ -189,42 +180,12 @@ func runServer(sockPath string) {
|
|||
Yolo: *yolo,
|
||||
NoMount: *no9p || *tcpAddr != "",
|
||||
Enable9P: !*no9p,
|
||||
EnableDBus: !*nodbus && (*no9p || *tcpAddr == ""),
|
||||
InvalidateModels: modelCache.Invalidate,
|
||||
OnSessionCreated: func(id string, sess *fs.Session) {
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.OnSessionCreated(id, sess)
|
||||
}
|
||||
},
|
||||
OnSessionKilled: func(id string) {
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.OnSessionKilled(id)
|
||||
}
|
||||
},
|
||||
OnSessionRenamed: func(oldID, newID string) {
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.OnSessionRenamed(oldID, newID)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
var srv *Server
|
||||
// Server creation deferred until after elevate broker is ready (see below)
|
||||
|
||||
// Start D-Bus adapter.
|
||||
// -tcp implies -nodbus (remote/headless deployments have no session bus).
|
||||
// -no9p forces D-Bus on (it's the only interface).
|
||||
if !*nodbus && (*no9p || *tcpAddr == "") {
|
||||
dbusAdapter = oldbus.StartDBus(sessionTree)
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.WatchExisting()
|
||||
}
|
||||
if *no9p && dbusAdapter == nil {
|
||||
fmt.Fprintln(os.Stderr, "error: -no9p mode requires D-Bus but session bus is unavailable")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Start elevation broker
|
||||
elevateSocket := os.Getenv("OLLIE_ELEVATE_SOCKET")
|
||||
if elevateSocket == "" {
|
||||
|
|
@ -272,8 +233,9 @@ func runServer(sockPath string) {
|
|||
fmt.Fprintf(os.Stderr, "warning: %v\n", err)
|
||||
} else {
|
||||
defer elevateBroker.Close()
|
||||
if dbusAdapter != nil {
|
||||
initElevateNotifier(dbusAdapter.Conn(), elevateBroker)
|
||||
// Desktop notifications for elevation prompts (uses D-Bus notifications API directly)
|
||||
if conn, err := dbus.SessionBus(); err == nil {
|
||||
initElevateNotifier(conn, elevateBroker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -335,19 +297,12 @@ func runServer(sockPath string) {
|
|||
fmt.Printf("olliesrv listening on tcp %s\n", *tcpAddr)
|
||||
}
|
||||
|
||||
if *no9p {
|
||||
fmt.Println("olliesrv running in D-Bus only mode")
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
fmt.Println("shutting down")
|
||||
daemonCancel() // signal all sessions via context propagation
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.Close()
|
||||
}
|
||||
if srv != nil {
|
||||
srv.Shutdown()
|
||||
}
|
||||
|
|
|
|||
697
dbus/dbus.go
697
dbus/dbus.go
|
|
@ -1,697 +0,0 @@
|
|||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/godbus/dbus/v5/introspect"
|
||||
|
||||
"ollie/agent"
|
||||
"ollie/backend"
|
||||
"ollie/fs/session"
|
||||
)
|
||||
|
||||
const (
|
||||
busName = "org.ollie.SessionManager"
|
||||
busPath = "/org/ollie/SessionManager"
|
||||
busIface = "org.ollie.SessionManager"
|
||||
)
|
||||
|
||||
// DBusAdapter exposes sessions over D-Bus.
|
||||
type DBusAdapter struct {
|
||||
conn *dbus.Conn
|
||||
tree *fs.Tree
|
||||
|
||||
mu sync.RWMutex
|
||||
watchers map[string]context.CancelFunc // sessionID -> cancel for state watcher
|
||||
}
|
||||
|
||||
// StartDBus connects to the session bus, claims the well-known name, and
|
||||
// exports the adapter. Returns nil (no-op) if the bus is unavailable.
|
||||
func StartDBus(tree *fs.Tree) *DBusAdapter {
|
||||
conn, err := dbus.ConnectSessionBus()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "dbus: session bus unavailable: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
reply, err := conn.RequestName(busName, dbus.NameFlagDoNotQueue)
|
||||
if err != nil || reply != dbus.RequestNameReplyPrimaryOwner {
|
||||
fmt.Fprintf(os.Stderr, "dbus: cannot claim %s (ollied already running?)\n", busName)
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
a := &DBusAdapter{
|
||||
conn: conn,
|
||||
tree: tree,
|
||||
watchers: make(map[string]context.CancelFunc),
|
||||
}
|
||||
|
||||
conn.Export(a, busPath, busIface)
|
||||
conn.Export(introspect.Introspectable(introspectXML), busPath, "org.freedesktop.DBus.Introspectable")
|
||||
|
||||
fmt.Printf("dbus: exported %s\n", busName)
|
||||
return a
|
||||
}
|
||||
|
||||
// WatchExisting starts watchers for all sessions that already exist.
|
||||
// Called after the adapter is initialized to cover sessions restored at startup.
|
||||
func (a *DBusAdapter) WatchExisting() {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
for _, sess := range fs.All(a.tree) {
|
||||
id := sess.RunnableID()
|
||||
a.startWatcher(id, sess)
|
||||
// Emit SessionCreated so GUI clients that connected before restore
|
||||
// (via serviceRegistered) learn about pre-existing sessions.
|
||||
a.conn.Emit(busPath, busIface+".SessionCreated", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) Close() {
|
||||
if a == nil || a.conn == nil {
|
||||
return
|
||||
}
|
||||
a.mu.Lock()
|
||||
for _, cancel := range a.watchers {
|
||||
cancel()
|
||||
}
|
||||
a.mu.Unlock()
|
||||
a.conn.Close()
|
||||
}
|
||||
|
||||
// Conn returns the underlying D-Bus connection, or nil if the adapter is nil.
|
||||
func (a *DBusAdapter) Conn() *dbus.Conn {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
return a.conn
|
||||
}
|
||||
|
||||
// --- Lifecycle callbacks ---
|
||||
|
||||
func (a *DBusAdapter) OnSessionCreated(id string, sess *fs.Session) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
a.startWatcher(id, sess)
|
||||
go a.conn.Emit(busPath, busIface+".SessionCreated", id)
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) OnSessionKilled(id string) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
a.stopWatcher(id)
|
||||
go a.conn.Emit(busPath, busIface+".SessionKilled", id)
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) OnSessionRenamed(oldID, newID string) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
a.stopWatcher(oldID)
|
||||
// The session object is the same, just re-lookup
|
||||
sess := fs.Lookup(a.tree, newID)
|
||||
if sess != nil {
|
||||
a.startWatcher(newID, sess)
|
||||
}
|
||||
go a.conn.Emit(busPath, busIface+".SessionRenamed", oldID, newID)
|
||||
}
|
||||
|
||||
// startWatcher launches goroutines for StateChanged and ChatUpdated signals.
|
||||
func (a *DBusAdapter) startWatcher(id string, sess *fs.Session) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
a.mu.Lock()
|
||||
a.watchers[id] = cancel
|
||||
a.mu.Unlock()
|
||||
|
||||
// State watcher
|
||||
go func() {
|
||||
current := sess.Core.Agent().State()
|
||||
for {
|
||||
next, ok := sess.Core.WaitChange(ctx, agent.WatchState, current)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
current = next
|
||||
a.conn.Emit(busPath, busIface+".StateChanged", id, current)
|
||||
if current == "idle" {
|
||||
a.sendNotification("Ollie", "Session "+id+" finished")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Agent watcher — emits ConfigChanged when the agent name changes
|
||||
go func() {
|
||||
current := sess.Core.Agent().Name()
|
||||
for {
|
||||
next, ok := sess.Core.WaitChange(ctx, "agent", current)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
current = next
|
||||
a.conn.Emit(busPath, busIface+".ConfigChanged", id, "agent", current)
|
||||
}
|
||||
}()
|
||||
|
||||
// Chat watcher (polls log version)
|
||||
go func() {
|
||||
var lastLen int
|
||||
var lastVers uint32
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(150 * time.Millisecond):
|
||||
}
|
||||
l, v := sess.LogInfo()
|
||||
if v == lastVers {
|
||||
continue
|
||||
}
|
||||
if l > lastLen {
|
||||
// Read the new chunk
|
||||
sess.Mu().RLock()
|
||||
chunk := string(sess.Log()[lastLen:])
|
||||
sess.Mu().RUnlock()
|
||||
a.conn.Emit(busPath, busIface+".ChatUpdated", id, int64(lastLen), chunk)
|
||||
}
|
||||
lastLen = l
|
||||
lastVers = v
|
||||
}
|
||||
}()
|
||||
|
||||
// Detached process exit watcher
|
||||
go func() {
|
||||
exitedPIDs := make(map[int]bool) // already-reported PIDs
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
infos := sess.Core.Agent().ListDetached()
|
||||
// Clean up stale entries from exitedPIDs
|
||||
activePIDs := make(map[int]bool, len(infos))
|
||||
for _, info := range infos {
|
||||
activePIDs[info.PID] = true
|
||||
}
|
||||
for pid := range exitedPIDs {
|
||||
if !activePIDs[pid] {
|
||||
delete(exitedPIDs, pid)
|
||||
}
|
||||
}
|
||||
for _, info := range infos {
|
||||
if info.Exited && !exitedPIDs[info.PID] {
|
||||
exitedPIDs[info.PID] = true
|
||||
a.conn.Emit(busPath, busIface+".ProcessExited", id, int32(info.PID), int32(info.ExitCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) stopWatcher(id string) {
|
||||
a.mu.Lock()
|
||||
if cancel, ok := a.watchers[id]; ok {
|
||||
cancel()
|
||||
delete(a.watchers, id)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// --- D-Bus exported methods ---
|
||||
|
||||
func (a *DBusAdapter) CreateSession(cwd, backendName, modelName, agentName, systemPrompt, remote string) (string, *dbus.Error) {
|
||||
var args []string
|
||||
if cwd != "" {
|
||||
args = append(args, "cwd="+cwd)
|
||||
}
|
||||
if backendName != "" {
|
||||
args = append(args, "backend="+backendName)
|
||||
}
|
||||
if modelName != "" {
|
||||
args = append(args, "model="+modelName)
|
||||
}
|
||||
if agentName != "" {
|
||||
args = append(args, "agent="+agentName)
|
||||
}
|
||||
if systemPrompt != "" {
|
||||
args = append(args, "system_prompt="+systemPrompt)
|
||||
}
|
||||
if remote != "" {
|
||||
args = append(args, "remote="+remote)
|
||||
}
|
||||
if cwd == "" {
|
||||
dir, _ := os.Getwd()
|
||||
args = append(args, "cwd="+dir)
|
||||
}
|
||||
id, err := fs.CreateFromRoot(a.tree, args)
|
||||
if err != nil {
|
||||
return "", dbus.MakeFailedError(err)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) ListSessions() ([]string, *dbus.Error) {
|
||||
var result []string
|
||||
for _, sess := range fs.All(a.tree) {
|
||||
id := sess.RunnableID()
|
||||
result = append(result, fmt.Sprintf("%s\t%s\t%s\t%s",
|
||||
id, sess.Core.Agent().State(), sess.Core.Agent().ModelName(), sess.Core.Agent().Name()))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) KillSession(sessionID string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
fs.KillFromRoot(a.tree, sessionID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) RenameSession(sessionID, newName string) (bool, *dbus.Error) {
|
||||
if err := fs.RenameFromRoot(a.tree, sessionID, newName); err != nil {
|
||||
return false, dbus.MakeFailedError(err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) Submit(sessionID, prompt string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
go sess.Core.Agent().Submit(sess.SessionCtx, prompt)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) Interrupt(sessionID string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
sess.Core.Agent().Interrupt(agent.ErrInterrupted)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetState(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
return sess.Core.Agent().State(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetChat(sessionID string, offset int64) (string, int64, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", offset, nil
|
||||
}
|
||||
sess.Mu().RLock()
|
||||
log := sess.Log()
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if offset > int64(len(log)) {
|
||||
offset = int64(len(log))
|
||||
}
|
||||
text := string(log[offset:])
|
||||
newOffset := int64(len(log))
|
||||
sess.Mu().RUnlock()
|
||||
return text, newOffset, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetUsage(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
return sess.Core.Agent().UsageStr(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetCost(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
return sess.Core.Agent().CostStr(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetConfig(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
var sb strings.Builder
|
||||
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.Agent().State())
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) SetConfig(sessionID, key, value string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
switch key {
|
||||
case "model":
|
||||
sess.Core.Agent().Submit(sess.SessionCtx, "/model "+value)
|
||||
case "cwd":
|
||||
if err := sess.Core.SetCWD(value); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetContext(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
msgs := sess.Core.Agent().Context()
|
||||
var sb strings.Builder
|
||||
enc := json.NewEncoder(&sb)
|
||||
enc.SetEscapeHTML(false)
|
||||
for _, msg := range msgs {
|
||||
enc.Encode(msg)
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) ListBackends() ([]string, *dbus.Error) {
|
||||
return backend.Backends(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) ListModelsByBackend(backendName string) ([]string, *dbus.Error) {
|
||||
be, err := backend.NewWithName(backendName)
|
||||
if err != nil {
|
||||
return nil, dbus.MakeFailedError(err)
|
||||
}
|
||||
models := be.Models(context.Background())
|
||||
sort.Strings(models)
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) ListModels(sessionID string) ([]string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return nil, nil
|
||||
}
|
||||
raw := sess.CachedListModels()
|
||||
models := strings.Split(strings.TrimSpace(raw), "\n")
|
||||
if len(models) == 1 && models[0] == "" {
|
||||
return nil, nil
|
||||
}
|
||||
sort.Strings(models)
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) ListAgents() ([]string, *dbus.Error) {
|
||||
var result []string
|
||||
for _, dir := range agent.AgentsDirs() {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if strings.HasSuffix(name, ".json") {
|
||||
result = append(result, strings.TrimSuffix(name, ".json"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
// --- Detached processes ---
|
||||
|
||||
func (a *DBusAdapter) DetachProcess(sessionID string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
|
||||
}
|
||||
if !sess.Core.Agent().Detach() {
|
||||
return false, dbus.MakeFailedError(fmt.Errorf("no running process to detach"))
|
||||
}
|
||||
a.conn.Emit(busPath, busIface+".ProcessDetached", sessionID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) ListDetached(sessionID string) ([]string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return nil, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
|
||||
}
|
||||
infos := sess.Core.Agent().ListDetached()
|
||||
out := make([]string, len(infos))
|
||||
for i, info := range infos {
|
||||
status := "running"
|
||||
if info.Exited {
|
||||
status = fmt.Sprintf("exited(%d)", info.ExitCode)
|
||||
}
|
||||
out[i] = fmt.Sprintf("%d\t%s\t%d\t%s", info.PID, info.Command, info.Started, status)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) SignalDetached(sessionID string, pid int32, signal string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
|
||||
}
|
||||
var sig int
|
||||
switch strings.ToUpper(signal) {
|
||||
case "TERM", "SIGTERM", "15":
|
||||
sig = 15
|
||||
case "KILL", "SIGKILL", "9":
|
||||
sig = 9
|
||||
default:
|
||||
return false, dbus.MakeFailedError(fmt.Errorf("unsupported signal: %s", signal))
|
||||
}
|
||||
if err := sess.Core.Agent().SignalDetached(int(pid), sig); err != nil {
|
||||
return false, dbus.MakeFailedError(err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetDetachedOutput(sessionID string, pid int32) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
|
||||
}
|
||||
output, err := sess.Core.Agent().GetDetachedOutput(int(pid))
|
||||
if err != nil {
|
||||
return "", dbus.MakeFailedError(err)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) DismissDetached(sessionID string, pid int32) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
|
||||
}
|
||||
return sess.Core.Agent().DismissDetached(int(pid)), nil
|
||||
}
|
||||
|
||||
// --- Generate / Route ---
|
||||
|
||||
func (a *DBusAdapter) Generate(prompt, system, backendName, model string) (string, *dbus.Error) {
|
||||
result, err := backend.Generate(context.Background(), backend.GenerateRequest{
|
||||
Prompt: prompt,
|
||||
System: system,
|
||||
Backend: backendName,
|
||||
Model: model,
|
||||
})
|
||||
if err != nil {
|
||||
return "", dbus.MakeFailedError(err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) Route(task, backendName string) (string, *dbus.Error) {
|
||||
result, err := backend.Route(context.Background(), backend.RouteRequest{
|
||||
Task: task,
|
||||
Backend: backendName,
|
||||
})
|
||||
if err != nil {
|
||||
return "", dbus.MakeFailedError(err)
|
||||
}
|
||||
return fmt.Sprintf("backend=%s model=%s", result.Backend, result.Model), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) Complete(cwd, filePath, prefix, suffix, extraContext string) (string, *dbus.Error) {
|
||||
result, err := agent.Complete(cwd, filePath, prefix, suffix, extraContext)
|
||||
if err != nil {
|
||||
return "", dbus.MakeFailedError(err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetEnv(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "OLLIE_SESSION_ID=%s\n", sess.RunnableID())
|
||||
for _, e := range os.Environ() {
|
||||
if strings.HasPrefix(e, "OLLIE_") && !strings.HasPrefix(e, "OLLIE_SESSION_ID=") {
|
||||
sb.WriteString(e)
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetPlan(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
sess.Mu().RLock()
|
||||
plan := string(sess.Plan())
|
||||
sess.Mu().RUnlock()
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) SetPlan(sessionID, plan string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
sess.Mu().Lock()
|
||||
sess.SetPlan([]byte(plan))
|
||||
sess.Mu().Unlock()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetSystemPrompt(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
return sess.Core.Agent().SystemPrompt(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetContextSize(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
return sess.Core.Agent().CtxSz(), nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) GetPreviousPrompt(sessionID string) (string, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return "", nil
|
||||
}
|
||||
sess.Mu().RLock()
|
||||
p := string(sess.PrevPrompt())
|
||||
sess.Mu().RUnlock()
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) Compact(sessionID string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
sess.Core.Agent().Submit(sess.SessionCtx, "/compact")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DBusAdapter) ClearContext(sessionID string) (bool, *dbus.Error) {
|
||||
sess := fs.Lookup(a.tree, sessionID)
|
||||
if sess == nil {
|
||||
return false, nil
|
||||
}
|
||||
sess.Core.Agent().Submit(sess.SessionCtx, "/clear")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// --- Notifications ---
|
||||
|
||||
func (a *DBusAdapter) sendNotification(title, body string) {
|
||||
go func() {
|
||||
obj := a.conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
|
||||
obj.Call("org.freedesktop.Notifications.Notify", 0,
|
||||
"olliesrv", uint32(0), "system-run", title, body,
|
||||
[]string{}, map[string]dbus.Variant{}, int32(5000))
|
||||
}()
|
||||
}
|
||||
|
||||
// Introspection XML.
|
||||
var introspectXML = `<node>
|
||||
<interface name="` + busIface + `">
|
||||
<method name="CreateSession">
|
||||
<arg direction="in" type="s" name="cwd"/>
|
||||
<arg direction="in" type="s" name="backend"/>
|
||||
<arg direction="in" type="s" name="model"/>
|
||||
<arg direction="in" type="s" name="agent"/>
|
||||
<arg direction="in" type="s" name="systemPrompt"/>
|
||||
<arg direction="in" type="s" name="remote"/>
|
||||
<arg direction="out" type="s"/>
|
||||
</method>
|
||||
<method name="ListSessions"><arg direction="out" type="as"/></method>
|
||||
<method name="KillSession"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="b"/></method>
|
||||
<method name="RenameSession"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="newName"/><arg direction="out" type="b"/></method>
|
||||
<method name="Submit"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="prompt"/><arg direction="out" type="b"/></method>
|
||||
<method name="Interrupt"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="b"/></method>
|
||||
<method name="GetState"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="GetChat"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="x" name="offset"/><arg direction="out" type="s"/><arg direction="out" type="x"/></method>
|
||||
<method name="GetUsage"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="GetCost"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="GetConfig"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="SetConfig"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="key"/><arg direction="in" type="s" name="value"/><arg direction="out" type="b"/></method>
|
||||
<method name="GetContext"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="ListBackends"><arg direction="out" type="as"/></method>
|
||||
<method name="ListModelsByBackend"><arg direction="in" type="s" name="backend"/><arg direction="out" type="as"/></method>
|
||||
<method name="ListModels"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="as"/></method>
|
||||
<method name="ListAgents"><arg direction="out" type="as"/></method>
|
||||
<method name="DetachProcess"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="b"/></method>
|
||||
<method name="ListDetached"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="as"/></method>
|
||||
<method name="SignalDetached"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="i" name="pid"/><arg direction="in" type="s" name="signal"/><arg direction="out" type="b"/></method>
|
||||
<method name="GetDetachedOutput"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="i" name="pid"/><arg direction="out" type="s"/></method>
|
||||
<method name="DismissDetached"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="i" name="pid"/><arg direction="out" type="b"/></method>
|
||||
<method name="Generate"><arg direction="in" type="s" name="prompt"/><arg direction="in" type="s" name="system"/><arg direction="in" type="s" name="backend"/><arg direction="in" type="s" name="model"/><arg direction="out" type="s"/></method>
|
||||
<method name="Route"><arg direction="in" type="s" name="task"/><arg direction="in" type="s" name="backend"/><arg direction="out" type="s"/></method>
|
||||
<method name="Complete"><arg direction="in" type="s" name="cwd"/><arg direction="in" type="s" name="file"/><arg direction="in" type="s" name="prefix"/><arg direction="in" type="s" name="suffix"/><arg direction="in" type="s" name="context"/><arg direction="out" type="s"/></method>
|
||||
<method name="GetEnv"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="GetPlan"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="SetPlan"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="plan"/><arg direction="out" type="b"/></method>
|
||||
<method name="GetSystemPrompt"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="GetContextSize"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="GetPreviousPrompt"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
|
||||
<method name="Compact"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="b"/></method>
|
||||
<method name="ClearContext"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="b"/></method>
|
||||
<signal name="SessionCreated"><arg type="s" name="sessionID"/></signal>
|
||||
<signal name="SessionKilled"><arg type="s" name="sessionID"/></signal>
|
||||
<signal name="SessionRenamed"><arg type="s" name="oldID"/><arg type="s" name="newID"/></signal>
|
||||
<signal name="StateChanged"><arg type="s" name="sessionID"/><arg type="s" name="state"/></signal>
|
||||
<signal name="ChatUpdated"><arg type="s" name="sessionID"/><arg type="x" name="offset"/><arg type="s" name="text"/></signal>
|
||||
<signal name="ProcessDetached"><arg type="s" name="sessionID"/></signal>
|
||||
<signal name="ProcessExited"><arg type="s" name="sessionID"/><arg type="i" name="pid"/><arg type="i" name="exitCode"/></signal>
|
||||
<signal name="ConfigChanged"><arg type="s" name="sessionID"/><arg type="s" name="key"/><arg type="s" name="value"/></signal>
|
||||
</interface>
|
||||
</node>`
|
||||
|
|
@ -194,7 +194,7 @@ func Create(rs *rootState, args []string) (string, error) {
|
|||
envMap[k] = v
|
||||
}
|
||||
}
|
||||
opModel := prompts.OperationalModel(rs.cfg.Enable9P, rs.cfg.EnableDBus, envMap)
|
||||
opModel := prompts.OperationalModel(rs.cfg.Enable9P, false, envMap)
|
||||
|
||||
platform := "linux"
|
||||
isGitRepo := false
|
||||
|
|
@ -256,9 +256,6 @@ func Create(rs *rootState, args []string) (string, error) {
|
|||
|
||||
rs.cfg.Log.Info("new session %s (backend=%s model=%s agent=%s)",
|
||||
sessID, core.Agent().BackendName(), core.Agent().ModelName(), core.Agent().Name())
|
||||
if rs.cfg.OnSessionCreated != nil {
|
||||
rs.cfg.OnSessionCreated(sessID, sess)
|
||||
}
|
||||
return sessID, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ func Kill(rs *rootState, id string) {
|
|||
}
|
||||
removePersistedAgent(rs, id)
|
||||
rs.cfg.Log.Info("killed session %s", id)
|
||||
if rs.cfg.OnSessionKilled != nil {
|
||||
rs.cfg.OnSessionKilled(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,9 +67,6 @@ func Rename(rs *rootState, old, new string) error {
|
|||
|
||||
sess.AppendLog([]byte(fmt.Sprintf(":: session renamed: %s -> %s\n", old, new)))
|
||||
rs.cfg.Log.Info("renamed session %s -> %s", old, new)
|
||||
if rs.cfg.OnSessionRenamed != nil {
|
||||
rs.cfg.OnSessionRenamed(old, new)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +290,7 @@ func restoreSession(rs *rootState, ps *agent.PersistedAgent) error {
|
|||
envMap[k] = v
|
||||
}
|
||||
}
|
||||
opModel := prompts.OperationalModel(rs.cfg.Enable9P, rs.cfg.EnableDBus, envMap)
|
||||
opModel := prompts.OperationalModel(rs.cfg.Enable9P, false, envMap)
|
||||
platform := "linux"
|
||||
isGitRepo := false
|
||||
for _, e := range promptEnv {
|
||||
|
|
@ -358,8 +352,5 @@ func restoreSession(rs *rootState, ps *agent.PersistedAgent) error {
|
|||
rs.mu.Unlock()
|
||||
|
||||
rs.cfg.Log.Info("restored session %s (backend=%s model=%s agent=%s)", sessID, backendName, modelName, agentName)
|
||||
if rs.cfg.OnSessionCreated != nil {
|
||||
rs.cfg.OnSessionCreated(sessID, sess)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,20 +47,12 @@ type Config struct {
|
|||
NoMount bool
|
||||
// Enable9P indicates the 9P listener is active (for operational model injection).
|
||||
Enable9P bool
|
||||
// EnableDBus indicates the D-Bus adapter is active (for operational model injection).
|
||||
EnableDBus bool
|
||||
// InvalidateModels clears the model cache, forcing a refresh.
|
||||
InvalidateModels func()
|
||||
// ToolRegistry is the shared tool registry for lazy tool promotion.
|
||||
ToolRegistry *toolsrv.Registry
|
||||
// SkillsRegistry is the shared skills registry for skill loading.
|
||||
SkillsRegistry *skills.Registry
|
||||
// OnSessionCreated is called after a new session is added.
|
||||
OnSessionCreated func(id string, sess *Session)
|
||||
// OnSessionKilled is called after a session is removed.
|
||||
OnSessionKilled func(id string)
|
||||
// OnSessionRenamed is called after a session is renamed.
|
||||
OnSessionRenamed func(oldID, newID string)
|
||||
}
|
||||
|
||||
// rootState is the package-level state stored in root.Data.
|
||||
|
|
|
|||
Loading…
Reference in New Issue