736 lines
23 KiB
Go
736 lines
23 KiB
Go
package main
|
|
|
|
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"
|
|
"olliesrv/mgr"
|
|
)
|
|
|
|
const (
|
|
busName = "org.ollie.SessionManager"
|
|
busPath = "/org/ollie/SessionManager"
|
|
busIface = "org.ollie.SessionManager"
|
|
)
|
|
|
|
// DBusAdapter exposes a mgr.Manager over D-Bus.
|
|
type DBusAdapter struct {
|
|
conn *dbus.Conn
|
|
mgr *mgr.Manager
|
|
|
|
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(mgr *mgr.Manager) *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,
|
|
mgr: mgr,
|
|
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
|
|
}
|
|
entries, _ := a.mgr.List()
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
id := e.Name()
|
|
sess := a.mgr.Session(id)
|
|
if sess == nil {
|
|
continue
|
|
}
|
|
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()
|
|
}
|
|
|
|
// --- Lifecycle callbacks (called by mgr.Manager) ---
|
|
|
|
func (a *DBusAdapter) OnSessionCreated(id string, sess *mgr.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 := a.mgr.Session(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 *mgr.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 := a.mgr.CreateSession(args)
|
|
if err != nil {
|
|
return "", dbus.MakeFailedError(err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
func (a *DBusAdapter) ListSessions() ([]string, *dbus.Error) {
|
|
entries, _ := a.mgr.List()
|
|
var result []string
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
id := e.Name()
|
|
sess := a.mgr.Session(id)
|
|
if sess == nil {
|
|
continue
|
|
}
|
|
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 := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return false, nil
|
|
}
|
|
a.mgr.KillSession(sessionID)
|
|
return true, nil
|
|
}
|
|
|
|
func (a *DBusAdapter) RenameSession(sessionID, newName string) (bool, *dbus.Error) {
|
|
if err := a.mgr.RenameSession(sessionID, newName); err != nil {
|
|
return false, dbus.MakeFailedError(err)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (a *DBusAdapter) Submit(sessionID, prompt string) (bool, *dbus.Error) {
|
|
sess := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return false, nil
|
|
}
|
|
go sess.Core.Agent().Submit(sess.Ctx, prompt)
|
|
return true, nil
|
|
}
|
|
|
|
func (a *DBusAdapter) React(sessionID, emoji string) (bool, *dbus.Error) {
|
|
return a.ReactTo(sessionID, "", emoji)
|
|
}
|
|
|
|
func (a *DBusAdapter) ReactTo(sessionID, responseID, emoji string) (bool, *dbus.Error) {
|
|
sess := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return false, nil
|
|
}
|
|
if err := sess.Core.Agent().React(responseID, emoji); err != nil {
|
|
return false, dbus.MakeFailedError(err)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (a *DBusAdapter) Interrupt(sessionID string) (bool, *dbus.Error) {
|
|
sess := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return "", nil
|
|
}
|
|
return sess.Core.Agent().UsageStr(), nil
|
|
}
|
|
|
|
func (a *DBusAdapter) GetCost(sessionID string) (string, *dbus.Error) {
|
|
sess := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return "", nil
|
|
}
|
|
return sess.Core.Agent().CostStr(), nil
|
|
}
|
|
|
|
func (a *DBusAdapter) GetConfig(sessionID string) (string, *dbus.Error) {
|
|
sess := a.mgr.Session(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 := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return false, nil
|
|
}
|
|
switch key {
|
|
case "model":
|
|
sess.Core.Agent().Submit(sess.Ctx, "/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) GetReactions(sessionID string) (string, *dbus.Error) {
|
|
sess := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return "{}", nil
|
|
}
|
|
data, err := json.Marshal(sess.Core.Agent().Reactions())
|
|
if err != nil {
|
|
return "", dbus.MakeFailedError(err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func (a *DBusAdapter) GetContext(sessionID string) (string, *dbus.Error) {
|
|
sess := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(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 := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return "", nil
|
|
}
|
|
return sess.Core.Agent().SystemPrompt(), nil
|
|
}
|
|
|
|
func (a *DBusAdapter) GetContextSize(sessionID string) (string, *dbus.Error) {
|
|
sess := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return "", nil
|
|
}
|
|
return sess.Core.Agent().CtxSz(), nil
|
|
}
|
|
|
|
func (a *DBusAdapter) GetPreviousPrompt(sessionID string) (string, *dbus.Error) {
|
|
sess := a.mgr.Session(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 := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return false, nil
|
|
}
|
|
sess.Core.Agent().Submit(sess.Ctx, "/compact")
|
|
return true, nil
|
|
}
|
|
|
|
func (a *DBusAdapter) ClearContext(sessionID string) (bool, *dbus.Error) {
|
|
sess := a.mgr.Session(sessionID)
|
|
if sess == nil {
|
|
return false, nil
|
|
}
|
|
sess.Core.Agent().Submit(sess.Ctx, "/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="React"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="emoji"/><arg direction="out" type="b"/></method>
|
|
<method name="ReactTo"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="responseID"/><arg direction="in" type="s" name="emoji"/><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="GetReactions"><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>`
|