remove daemon (moved to ollie-dbus submodule)
This commit is contained in:
parent
e53ed3b918
commit
d457be9524
|
|
@ -21,10 +21,6 @@ include(KDECompilerSettings NO_POLICY_SCOPE)
|
|||
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
|
||||
# libollie.so built by Go (expected in build/ after `make lib`)
|
||||
set(OLLIE_LIB "${CMAKE_SOURCE_DIR}/build/libollie.so")
|
||||
set(OLLIE_HEADER "${CMAKE_SOURCE_DIR}/build/libollie.h")
|
||||
|
||||
if(OLLIE_KF5)
|
||||
# --- Qt5 / KF5 ---
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core DBus Widgets Quick Qml)
|
||||
|
|
@ -37,27 +33,6 @@ if(OLLIE_KF5)
|
|||
TextEditor
|
||||
)
|
||||
|
||||
# --- Daemon ---
|
||||
qt5_add_dbus_adaptor(DBUS_ADAPTOR_SRCS
|
||||
daemon/org.ollie.SessionManager.xml
|
||||
daemon/session_manager.h SessionManager)
|
||||
|
||||
add_executable(ollie-kde
|
||||
daemon/main.cpp
|
||||
daemon/session_manager.cpp
|
||||
${DBUS_ADAPTOR_SRCS}
|
||||
)
|
||||
target_include_directories(ollie-kde PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/build
|
||||
${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
target_link_libraries(ollie-kde
|
||||
Qt5::Core
|
||||
Qt5::DBus
|
||||
${OLLIE_LIB}
|
||||
)
|
||||
|
||||
# --- System Tray ---
|
||||
add_executable(ollie-tray
|
||||
tray/main.cpp
|
||||
|
|
@ -113,7 +88,7 @@ if(OLLIE_KF5)
|
|||
plasma_install_package(plasmoid org.ollie.plasmoid)
|
||||
|
||||
# --- Install ---
|
||||
install(TARGETS ollie-kde ollie-tray DESTINATION ${KDE_INSTALL_BINDIR})
|
||||
install(TARGETS ollie-tray DESTINATION ${KDE_INSTALL_BINDIR})
|
||||
install(TARGETS ollie-plasmoid-plugin
|
||||
DESTINATION ${KDE_INSTALL_QMLDIR}/org/ollie/private)
|
||||
install(FILES plasmoid/plugin/qmldir
|
||||
|
|
@ -126,27 +101,6 @@ else()
|
|||
find_package(KF6 REQUIRED COMPONENTS Runner Notifications CoreAddons Config)
|
||||
find_package(Plasma REQUIRED)
|
||||
|
||||
# --- Daemon ---
|
||||
qt6_add_dbus_adaptor(DBUS_ADAPTOR_SRCS
|
||||
daemon/org.ollie.SessionManager.xml
|
||||
daemon/session_manager.h SessionManager)
|
||||
|
||||
add_executable(ollie-kde
|
||||
daemon/main.cpp
|
||||
daemon/session_manager.cpp
|
||||
${DBUS_ADAPTOR_SRCS}
|
||||
)
|
||||
target_include_directories(ollie-kde PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/build
|
||||
${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
target_link_libraries(ollie-kde
|
||||
Qt6::Core
|
||||
Qt6::DBus
|
||||
${OLLIE_LIB}
|
||||
)
|
||||
|
||||
# --- System Tray ---
|
||||
add_executable(ollie-tray
|
||||
tray/main.cpp
|
||||
|
|
@ -203,7 +157,7 @@ else()
|
|||
plasma_install_package(plasmoid org.ollie.plasmoid)
|
||||
|
||||
# --- Install ---
|
||||
install(TARGETS ollie-kde ollie-tray DESTINATION ${KDE_INSTALL_BINDIR})
|
||||
install(TARGETS ollie-tray DESTINATION ${KDE_INSTALL_BINDIR})
|
||||
install(TARGETS ollie-plasmoid-plugin
|
||||
DESTINATION ${KDE_INSTALL_QMLDIR}/org/ollie/private)
|
||||
endif()
|
||||
|
|
|
|||
62
Makefile
62
Makefile
|
|
@ -1,43 +1,33 @@
|
|||
# kde-ollie top-level Makefile
|
||||
# Build order: libollie.so (Go) → ollie-kde daemon (C++/CMake)
|
||||
# kde-ollie Makefile
|
||||
# Build order: cmake (KDE tools only — daemon is now in ../dbus)
|
||||
|
||||
BUILD_DIR := build-cmake
|
||||
LIB_DIR := build
|
||||
|
||||
.PHONY: all lib daemon clean run install uninstall \
|
||||
.PHONY: all clean run install uninstall \
|
||||
kf5 kf5-install kf5-uninstall \
|
||||
krunner-install dolphin-install plasmoid-install plasmoid-uninstall \
|
||||
tray data
|
||||
|
||||
all: daemon data
|
||||
all: build data
|
||||
|
||||
# Step 1: Build libollie.so from Go
|
||||
lib: $(LIB_DIR)/libollie.so
|
||||
|
||||
$(LIB_DIR)/libollie.so: lib/capi.go lib/go.mod
|
||||
cd lib && go build -buildmode=c-shared -o ../$(LIB_DIR)/libollie.so
|
||||
|
||||
# Step 2: Build the C++ daemon against libollie.so
|
||||
daemon: lib
|
||||
# Build KDE tools (tray, krunner, kate, plasmoid)
|
||||
build:
|
||||
cmake -B $(BUILD_DIR) -S .
|
||||
cmake --build $(BUILD_DIR)
|
||||
|
||||
# Step 2b: Build using KF5/Qt5 (for Plasma 5.27)
|
||||
# Build using KF5/Qt5 (for Plasma 5.27)
|
||||
KF5_BUILD_DIR := build-kf5
|
||||
|
||||
kf5: lib
|
||||
kf5:
|
||||
cmake -B $(KF5_BUILD_DIR) -S . -DOLLIE_KF5=ON
|
||||
cmake --build $(KF5_BUILD_DIR)
|
||||
|
||||
# --- KF6 Install (default) ---
|
||||
|
||||
install: daemon
|
||||
install: build
|
||||
@echo "=== Installing ollie-kde (KF6) ==="
|
||||
# Daemon + tray binaries
|
||||
install -m755 $(BUILD_DIR)/ollie-kde /usr/bin/
|
||||
# Tray binary
|
||||
install -m755 $(BUILD_DIR)/ollie-tray /usr/bin/
|
||||
# Go shared library
|
||||
install -m755 $(LIB_DIR)/libollie.so /usr/lib64/
|
||||
# KRunner plugin
|
||||
mkdir -p /usr/lib64/qt6/plugins/kf6/krunner
|
||||
install -m755 $(BUILD_DIR)/lib/kf6/krunner/krunner_ollie.so \
|
||||
|
|
@ -64,8 +54,6 @@ install: daemon
|
|||
install -m755 dolphin/ollie-actions.desktop ~/.local/share/kio/servicemenus/
|
||||
install -m755 dolphin/ollie-ask /usr/local/bin/
|
||||
install -m755 dolphin/ollie-session-here /usr/local/bin/
|
||||
# XDG autostart
|
||||
install -m644 org.ollie.SessionManager.desktop /etc/xdg/autostart/
|
||||
# Finalize
|
||||
ldconfig
|
||||
kquitapp6 krunner 2>/dev/null; true
|
||||
|
|
@ -75,23 +63,21 @@ install: daemon
|
|||
|
||||
uninstall:
|
||||
@echo "=== Uninstalling ollie-kde ==="
|
||||
pkill -u "$$(id -u)" -x ollie-kde 2>/dev/null; true
|
||||
rm -f /usr/bin/ollie-kde /usr/bin/ollie-tray
|
||||
rm -f /usr/lib64/libollie.so /usr/lib64/libollie-plasmoid-plugin.so
|
||||
rm -f /usr/bin/ollie-tray
|
||||
rm -f /usr/lib64/libollie-plasmoid-plugin.so
|
||||
rm -f /usr/lib64/qt6/plugins/kf6/krunner/krunner_ollie.so
|
||||
rm -f /usr/lib64/qt6/plugins/kf6/ktexteditor/ollie_kate.so
|
||||
rm -rf /usr/lib64/qt6/qml/org/ollie
|
||||
kpackagetool6 -t Plasma/Applet -r org.ollie.plasmoid 2>/dev/null; true
|
||||
rm -f ~/.local/share/kio/servicemenus/ollie-actions.desktop
|
||||
rm -f /usr/local/bin/ollie-ask /usr/local/bin/ollie-session-here
|
||||
rm -f /etc/xdg/autostart/org.ollie.SessionManager.desktop
|
||||
ldconfig
|
||||
kquitapp6 krunner 2>/dev/null; true
|
||||
@echo "=== Uninstall complete ==="
|
||||
|
||||
# --- Individual component installs ---
|
||||
|
||||
krunner-install: daemon
|
||||
krunner-install: build
|
||||
mkdir -p /usr/lib64/qt6/plugins/kf6/krunner
|
||||
install -m755 $(BUILD_DIR)/lib/kf6/krunner/krunner_ollie.so \
|
||||
/usr/lib64/qt6/plugins/kf6/krunner/
|
||||
|
|
@ -106,7 +92,7 @@ dolphin-install:
|
|||
install -m755 dolphin/ollie-session-here /usr/local/bin/
|
||||
@echo "Dolphin menus installed. Restart Dolphin or re-open context menu."
|
||||
|
||||
plasmoid-install: daemon
|
||||
plasmoid-install: build
|
||||
kpackagetool6 -t Plasma/Applet -r org.ollie.plasmoid 2>/dev/null; true
|
||||
kpackagetool6 -t Plasma/Applet -i plasmoid/
|
||||
mkdir -p /usr/lib64/qt6/qml/org/ollie/private
|
||||
|
|
@ -131,11 +117,8 @@ plasmoid-uninstall:
|
|||
|
||||
kf5-install: kf5
|
||||
@echo "=== Installing ollie-kde (KF5) ==="
|
||||
# Daemon + tray binaries
|
||||
install -m755 $(KF5_BUILD_DIR)/ollie-kde /usr/bin/
|
||||
# Tray binary
|
||||
install -m755 $(KF5_BUILD_DIR)/ollie-tray /usr/bin/
|
||||
# Go shared library
|
||||
install -m755 $(LIB_DIR)/libollie.so /usr/lib/x86_64-linux-gnu/
|
||||
# KRunner plugin
|
||||
mkdir -p /usr/lib/x86_64-linux-gnu/qt5/plugins/kf5/krunner
|
||||
install -m755 $(KF5_BUILD_DIR)/lib/krunner_ollie.so \
|
||||
|
|
@ -163,8 +146,6 @@ kf5-install: kf5
|
|||
install -m755 dolphin/ollie-actions-kf5.desktop ~/.local/share/kio/servicemenus/ollie-actions.desktop
|
||||
install -m755 dolphin/ollie-ask /usr/local/bin/
|
||||
install -m755 dolphin/ollie-session-here /usr/local/bin/
|
||||
# XDG autostart
|
||||
install -m644 org.ollie.SessionManager.desktop /etc/xdg/autostart/
|
||||
# Finalize
|
||||
ldconfig
|
||||
kquitapp5 krunner 2>/dev/null; true
|
||||
|
|
@ -174,16 +155,13 @@ kf5-install: kf5
|
|||
|
||||
kf5-uninstall:
|
||||
@echo "=== Uninstalling ollie-kde (KF5) ==="
|
||||
pkill -u "$$(id -u)" -x ollie-kde 2>/dev/null; true
|
||||
rm -f /usr/bin/ollie-kde /usr/bin/ollie-tray
|
||||
rm -f /usr/lib/x86_64-linux-gnu/libollie.so
|
||||
rm -f /usr/bin/ollie-tray
|
||||
rm -f /usr/lib/x86_64-linux-gnu/qt5/plugins/kf5/krunner/krunner_ollie.so
|
||||
rm -f /usr/lib/x86_64-linux-gnu/qt5/plugins/ktexteditor/ollie_kate.so
|
||||
rm -rf /usr/lib/x86_64-linux-gnu/qt5/qml/org/ollie
|
||||
kpackagetool5 -t Plasma/Applet -r org.ollie.plasmoid 2>/dev/null; true
|
||||
rm -f ~/.local/share/kio/servicemenus/ollie-actions.desktop
|
||||
rm -f /usr/local/bin/ollie-ask /usr/local/bin/ollie-session-here
|
||||
rm -f /etc/xdg/autostart/org.ollie.SessionManager.desktop
|
||||
ldconfig
|
||||
kquitapp5 krunner 2>/dev/null; true
|
||||
@echo "=== Uninstall complete (KF5) ==="
|
||||
|
|
@ -203,12 +181,8 @@ data:
|
|||
|
||||
# --- Development helpers ---
|
||||
|
||||
run: daemon
|
||||
LD_LIBRARY_PATH=$(LIB_DIR):$$LD_LIBRARY_PATH ./$(BUILD_DIR)/ollie-kde
|
||||
|
||||
tray: daemon
|
||||
tray: build
|
||||
./$(BUILD_DIR)/ollie-tray
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
rm -f $(LIB_DIR)/libollie.so $(LIB_DIR)/libollie.h
|
||||
rm -rf $(BUILD_DIR) $(KF5_BUILD_DIR)
|
||||
|
|
|
|||
132
README.md
132
README.md
|
|
@ -1,8 +1,8 @@
|
|||
# ollie-kde
|
||||
|
||||
KDE-native integration for [ollie](https://github.com/lkn/ollie) — a long-lived
|
||||
D-Bus daemon that manages AI agent sessions, with thin KDE clients (system tray,
|
||||
Plasmoid, KRunner, Kate plugin, Dolphin menus).
|
||||
KDE-native integration for [ollie](https://github.com/lkn/ollie) — thin KDE
|
||||
clients (system tray, Plasmoid, KRunner, Kate plugin, Dolphin menus) that talk
|
||||
to the `ollied` D-Bus daemon.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
@ -13,114 +13,57 @@ Plasmoid, KRunner, Kate plugin, Dolphin menus).
|
|||
└──────────────────────────┬───────────────────────────────────┘
|
||||
│ D-Bus (org.ollie.SessionManager)
|
||||
┌──────────────────────────┴───────────────────────────────────┐
|
||||
│ ollie-kde daemon │
|
||||
│ C++ Qt6 │ D-Bus adaptor │ libollie.so (Go) │
|
||||
│ ollied (../dbus) │
|
||||
│ Pure Go │ godbus │ ollie/core │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The daemon owns all sessions and the Go runtime (libollie.so). KDE components
|
||||
are pure D-Bus clients — lightweight, stateless, and independently restartable.
|
||||
The daemon lives in `../dbus/` (the `ollie-dbus` submodule). KDE components
|
||||
here are pure D-Bus clients — lightweight, stateless, and independently
|
||||
restartable.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Go 1.25+
|
||||
- Qt 6 (Core, DBus, Widgets, Quick) or Qt 5 (Core, DBus, Widgets, Quick, Qml)
|
||||
- KDE Frameworks 6 (StatusNotifierItem, Runner, TextEditor, CoreAddons, Config)
|
||||
or KDE Frameworks 5 (Notifications, Runner, TextEditor, CoreAddons, Config, Plasma)
|
||||
- Plasma 6 or Plasma 5 (for Plasmoid)
|
||||
- ECM (Extra CMake Modules)
|
||||
- ollie/core (sibling directory at `../ollie/core`)
|
||||
|
||||
KF6/Qt6 is the default (`CMakeLists.txt`). To build against KF5/Qt5, point CMake
|
||||
at the alternate file:
|
||||
KF6/Qt6 is the default. To build against KF5/Qt5:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build-cmake -DCMAKE_PROJECT_INCLUDE=CMakeLists-kf5.txt
|
||||
# or copy/symlink it:
|
||||
ln -sf CMakeLists-kf5.txt CMakeLists.txt
|
||||
make kf5
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
make # builds libollie.so (Go) then ollie-kde + ollie-tray (CMake)
|
||||
make # builds tray + KRunner + Kate + Plasmoid via CMake
|
||||
```
|
||||
|
||||
Outputs:
|
||||
- `build/libollie.so` — Go shared library (cgo c-shared)
|
||||
- `build-cmake/ollie-kde` — D-Bus daemon
|
||||
- `build-cmake/ollie-tray` — system tray icon
|
||||
- `build-cmake/lib/kf6/krunner/krunner_ollie.so` — KRunner plugin (kf5/ on KF5 builds)
|
||||
- `build-cmake/lib/kf6/ktexteditor/ollie_kate.so` — Kate plugin (kf5/ on KF5 builds)
|
||||
- `build-cmake/lib/kf6/krunner/krunner_ollie.so` — KRunner plugin
|
||||
- `build-cmake/lib/kf6/ktexteditor/ollie_kate.so` — Kate plugin
|
||||
- `build-cmake/org/ollie/private/` — Plasmoid QML plugin
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
# Daemon + tray
|
||||
sudo install -m755 build-cmake/ollie-kde /usr/bin/
|
||||
sudo install -m755 build/libollie.so /usr/lib64/
|
||||
sudo make install # all KDE components (KF6)
|
||||
sudo make kf5-install # all KDE components (KF5)
|
||||
|
||||
# KRunner plugin (use kf5/ and qt5 paths on KF5 builds)
|
||||
sudo cp build-cmake/lib/kf6/krunner/krunner_ollie.so /usr/lib64/qt6/plugins/kf6/krunner/
|
||||
|
||||
# Kate plugin
|
||||
sudo cp build-cmake/lib/kf6/ktexteditor/ollie_kate.so /usr/lib64/qt6/plugins/kf6/ktexteditor/
|
||||
|
||||
# Plasmoid
|
||||
make plasmoid-install
|
||||
|
||||
# KRunner (restart needed)
|
||||
# Individual:
|
||||
make krunner-install
|
||||
|
||||
# Dolphin service menus
|
||||
make dolphin-install
|
||||
|
||||
# System tray (optional standalone)
|
||||
sudo install -m755 build-cmake/ollie-tray /usr/bin/
|
||||
make plasmoid-install
|
||||
```
|
||||
|
||||
## Service Setup
|
||||
|
||||
The daemon starts automatically via XDG autostart when you log into a KDE Plasma
|
||||
session. No manual service configuration is needed after `make install`.
|
||||
|
||||
To disable autostart without uninstalling:
|
||||
|
||||
```sh
|
||||
# Per-user override
|
||||
cp /etc/xdg/autostart/org.ollie.SessionManager.desktop ~/.config/autostart/
|
||||
echo "Hidden=true" >> ~/.config/autostart/org.ollie.SessionManager.desktop
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
```sh
|
||||
make run # foreground, for development
|
||||
```
|
||||
The D-Bus daemon (`ollied`) is installed separately from the `../dbus/` submodule.
|
||||
|
||||
## Usage
|
||||
|
||||
### D-Bus API
|
||||
|
||||
The daemon registers as `org.ollie.SessionManager` on the session bus.
|
||||
|
||||
```sh
|
||||
# Create a session
|
||||
dbus-send --session --dest=org.ollie.SessionManager --type=method_call --print-reply \
|
||||
/org/ollie/SessionManager org.ollie.SessionManager.CreateSession \
|
||||
string:"$HOME" string:"" string:"" string:"default"
|
||||
|
||||
# Submit a prompt
|
||||
dbus-send --session --dest=org.ollie.SessionManager --type=method_call --print-reply \
|
||||
/org/ollie/SessionManager org.ollie.SessionManager.Submit \
|
||||
string:"<session_id>" string:"your prompt here"
|
||||
|
||||
# Get chat output
|
||||
gdbus call --session --dest org.ollie.SessionManager --object-path /org/ollie/SessionManager \
|
||||
--method org.ollie.SessionManager.GetChat "<session_id>" 0
|
||||
```
|
||||
|
||||
### KRunner
|
||||
|
||||
- `ollie <prompt>` — one-shot: creates session, submits, shows popup with result
|
||||
|
|
@ -158,48 +101,12 @@ Add the "Ollie" widget to your panel or desktop:
|
|||
- Status: passive (no sessions), active (sessions idle), attention (agent working)
|
||||
- Right-click: New Session, Sessions submenu (kill each), Stop All, Quit
|
||||
|
||||
## D-Bus Interface
|
||||
|
||||
Methods:
|
||||
- `CreateSession(cwd, backend, model, agent) → session_id`
|
||||
- `ListSessions() → [id\tstate\tmodel\tagent, ...]`
|
||||
- `KillSession(session_id) → success`
|
||||
- `RenameSession(session_id, new_name) → success`
|
||||
- `Submit(session_id, prompt) → success`
|
||||
- `Interrupt(session_id) → success`
|
||||
- `GetState(session_id) → state`
|
||||
- `GetChat(session_id, offset) → text, new_offset`
|
||||
- `GetUsage(session_id) → usage`
|
||||
- `GetCost(session_id) → cost`
|
||||
- `GetConfig(session_id) → key=value pairs`
|
||||
- `SetConfig(session_id, key, value) → success`
|
||||
- `GetContext(session_id) → JSONL messages`
|
||||
- `ListBackends() → [names]`
|
||||
- `ListModels(session_id) → [names]`
|
||||
- `ListAgents() → [names]`
|
||||
- `PeerAdd(session_id, peer_id) → success`
|
||||
- `PeerRemove(session_id, peer_id) → success`
|
||||
- `PeerList(session_id) → [peer_ids]`
|
||||
- `PeerSubmit(session_id, peer_id, prompt) → success`
|
||||
|
||||
Signals:
|
||||
- `SessionCreated(session_id)`
|
||||
- `SessionKilled(session_id)`
|
||||
- `StateChanged(session_id, new_state)`
|
||||
- `ChatUpdated(session_id, offset, new_text)`
|
||||
|
||||
## File Layout
|
||||
|
||||
```
|
||||
ollie-kde/
|
||||
├── Makefile # Go + CMake orchestration
|
||||
├── Makefile # CMake orchestration
|
||||
├── CMakeLists.txt
|
||||
├── lib/
|
||||
│ └── capi.go # cgo exports (libollie.so)
|
||||
├── daemon/
|
||||
│ ├── main.cpp # D-Bus registration, event loop
|
||||
│ ├── session_manager.h/cpp # D-Bus adaptee, bridges C API
|
||||
│ └── org.ollie.SessionManager.xml
|
||||
├── tray/
|
||||
│ ├── main.cpp
|
||||
│ └── ollie_tray.h/cpp # KStatusNotifierItem
|
||||
|
|
@ -218,6 +125,5 @@ ollie-kde/
|
|||
│ ├── ollie-actions.desktop # Service menu entries
|
||||
│ ├── ollie-ask # One-shot helper script
|
||||
│ └── ollie-session-here # Session creation helper
|
||||
├── org.ollie.SessionManager.desktop # XDG autostart entry
|
||||
└── test-e2e.sh # End-to-end test script
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDBusConnection>
|
||||
#include <QDebug>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include <csignal>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "session_manager.h"
|
||||
#include "sessionmanageradaptor.h" // generated by qt6_add_dbus_adaptor
|
||||
|
||||
// Async-signal-safe shutdown via socketpair
|
||||
static int sigFd[2];
|
||||
|
||||
static void signalHandler(int)
|
||||
{
|
||||
char c = 1;
|
||||
write(sigFd[0], &c, 1);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
app.setApplicationName("ollie-kde");
|
||||
app.setOrganizationName("ollie");
|
||||
|
||||
// Set up signal handling via socketpair (async-signal-safe)
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sigFd) != 0) {
|
||||
qCritical() << "Cannot create signal socketpair";
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto *sigNotifier = new QSocketNotifier(sigFd[1], QSocketNotifier::Read, &app);
|
||||
QObject::connect(sigNotifier, &QSocketNotifier::activated, &app, [&]() {
|
||||
char c;
|
||||
read(sigFd[1], &c, 1);
|
||||
qDebug() << "Received shutdown signal, exiting gracefully...";
|
||||
app.quit();
|
||||
});
|
||||
|
||||
signal(SIGTERM, signalHandler);
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
// Create the session manager (initializes ollie core)
|
||||
SessionManager manager;
|
||||
|
||||
// Create the D-Bus adaptor (generated from XML)
|
||||
new SessionManagerAdaptor(&manager);
|
||||
|
||||
// Register on the session bus
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
if (!bus.isConnected()) {
|
||||
qCritical() << "Cannot connect to D-Bus session bus";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!bus.registerService("org.ollie.SessionManager")) {
|
||||
qCritical() << "Cannot register D-Bus service org.ollie.SessionManager:"
|
||||
<< bus.lastError().message();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!bus.registerObject("/org/ollie/SessionManager", &manager)) {
|
||||
qCritical() << "Cannot register D-Bus object /org/ollie/SessionManager:"
|
||||
<< bus.lastError().message();
|
||||
return 1;
|
||||
}
|
||||
|
||||
qDebug() << "ollie-kde daemon started on D-Bus: org.ollie.SessionManager";
|
||||
|
||||
int rc = app.exec();
|
||||
|
||||
qDebug() << "ollie-kde daemon exiting";
|
||||
return rc;
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.ollie.SessionManager">
|
||||
<!-- Session lifecycle -->
|
||||
<method name="CreateSession">
|
||||
<arg name="cwd" type="s" direction="in"/>
|
||||
<arg name="backend" type="s" direction="in"/>
|
||||
<arg name="model" type="s" direction="in"/>
|
||||
<arg name="agent" type="s" direction="in"/>
|
||||
<arg name="session_id" type="s" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="ListSessions">
|
||||
<arg name="sessions" type="as" direction="out"/>
|
||||
<!-- each entry: "id\tstate\tmodel\tagent" -->
|
||||
</method>
|
||||
|
||||
<method name="KillSession">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="RenameSession">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="new_name" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Interaction -->
|
||||
<method name="Submit">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="prompt" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="Interrupt">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- State queries -->
|
||||
<method name="GetState">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="state" type="s" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="GetChat">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="offset" type="x" direction="in"/>
|
||||
<arg name="text" type="s" direction="out"/>
|
||||
<arg name="new_offset" type="x" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="GetUsage">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="usage" type="s" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="GetCost">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="cost" type="s" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Config -->
|
||||
<method name="GetConfig">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="config" type="s" direction="out"/>
|
||||
<!-- key=value\n pairs -->
|
||||
</method>
|
||||
|
||||
<method name="SetConfig">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="key" type="s" direction="in"/>
|
||||
<arg name="value" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Context -->
|
||||
<method name="GetContext">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="context" type="s" direction="out"/>
|
||||
<!-- JSONL: one JSON message per line -->
|
||||
</method>
|
||||
|
||||
<!-- Model/backend management -->
|
||||
<method name="ListBackends">
|
||||
<arg name="backends" type="as" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="ListModels">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="models" type="as" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Agents -->
|
||||
<method name="ListAgents">
|
||||
<arg name="agents" type="as" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Peers -->
|
||||
<method name="PeerAdd">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peer_id" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="PeerRemove">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peer_id" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="PeerList">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peers" type="as" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="PeerSubmit">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peer_id" type="s" direction="in"/>
|
||||
<arg name="prompt" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Signals -->
|
||||
<signal name="SessionCreated">
|
||||
<arg name="session_id" type="s"/>
|
||||
</signal>
|
||||
|
||||
<signal name="SessionKilled">
|
||||
<arg name="session_id" type="s"/>
|
||||
</signal>
|
||||
|
||||
<signal name="StateChanged">
|
||||
<arg name="session_id" type="s"/>
|
||||
<arg name="new_state" type="s"/>
|
||||
</signal>
|
||||
|
||||
<signal name="ChatUpdated">
|
||||
<arg name="session_id" type="s"/>
|
||||
<arg name="offset" type="x"/>
|
||||
<arg name="new_text" type="s"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
|
|
@ -1,341 +0,0 @@
|
|||
#include "session_manager.h"
|
||||
#include "libollie.h"
|
||||
|
||||
#include <QDBusInterface>
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
|
||||
// Global pointer to the singleton — used by C callbacks to route events.
|
||||
static SessionManager *g_instance = nullptr;
|
||||
|
||||
// --- C callback handlers (called from Go goroutines, must be thread-safe) ---
|
||||
|
||||
static void onStateChange(const char *session_id, const char *state)
|
||||
{
|
||||
if (!g_instance) return;
|
||||
QString sid = QString::fromUtf8(session_id);
|
||||
QString st = QString::fromUtf8(state);
|
||||
QMetaObject::invokeMethod(g_instance, "handleStateChange",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, sid), Q_ARG(QString, st));
|
||||
}
|
||||
|
||||
static void onChatUpdate(const char *session_id, int64_t offset, const char *text, int len)
|
||||
{
|
||||
if (!g_instance) return;
|
||||
QString sid = QString::fromUtf8(session_id);
|
||||
QString txt = QString::fromUtf8(text, len);
|
||||
qlonglong off = static_cast<qlonglong>(offset);
|
||||
QMetaObject::invokeMethod(g_instance, [sid, off, txt]() {
|
||||
emit g_instance->ChatUpdated(sid, off, txt);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
// --- SessionManager implementation ---
|
||||
|
||||
SessionManager::SessionManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
g_instance = this;
|
||||
ollie_init(nullptr);
|
||||
installCallbacks();
|
||||
qDebug() << "SessionManager: initialized ollie core";
|
||||
}
|
||||
|
||||
SessionManager::~SessionManager()
|
||||
{
|
||||
ollie_shutdown();
|
||||
g_instance = nullptr;
|
||||
qDebug() << "SessionManager: shut down ollie core";
|
||||
}
|
||||
|
||||
void SessionManager::installCallbacks()
|
||||
{
|
||||
ollie_on_state_change(onStateChange);
|
||||
ollie_on_chat_update(onChatUpdate);
|
||||
}
|
||||
|
||||
// --- Session lifecycle ---
|
||||
|
||||
QString SessionManager::CreateSession(const QString &cwd, const QString &backend,
|
||||
const QString &model, const QString &agent)
|
||||
{
|
||||
QByteArray cwdUtf8 = cwd.toUtf8();
|
||||
QByteArray backendUtf8 = backend.toUtf8();
|
||||
QByteArray modelUtf8 = model.toUtf8();
|
||||
QByteArray agentUtf8 = agent.toUtf8();
|
||||
|
||||
char *outID = nullptr;
|
||||
int rc = ollie_create_session(
|
||||
const_cast<char*>(cwdUtf8.constData()),
|
||||
const_cast<char*>(backendUtf8.constData()),
|
||||
const_cast<char*>(modelUtf8.constData()),
|
||||
const_cast<char*>(agentUtf8.constData()),
|
||||
&outID);
|
||||
if (rc != 0 || !outID) {
|
||||
qWarning() << "CreateSession failed, rc=" << rc;
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString sessionID = QString::fromUtf8(outID);
|
||||
ollie_free(outID);
|
||||
|
||||
emit SessionCreated(sessionID);
|
||||
return sessionID;
|
||||
}
|
||||
|
||||
QStringList SessionManager::ListSessions()
|
||||
{
|
||||
char *buf = nullptr;
|
||||
ollie_list_sessions(&buf);
|
||||
QStringList result;
|
||||
|
||||
if (buf) {
|
||||
QString raw = QString::fromUtf8(buf);
|
||||
ollie_free(buf);
|
||||
result = raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SessionManager::KillSession(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
int rc = ollie_kill_session(const_cast<char*>(idUtf8.constData()));
|
||||
if (rc == 0) {
|
||||
m_sessionStates.remove(session_id);
|
||||
emit SessionKilled(session_id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SessionManager::RenameSession(const QString &session_id, const QString &new_name)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray nameUtf8 = new_name.toUtf8();
|
||||
int rc = ollie_rename_session(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(nameUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
// --- Interaction ---
|
||||
|
||||
bool SessionManager::Submit(const QString &session_id, const QString &prompt)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray promptUtf8 = prompt.toUtf8();
|
||||
int rc = ollie_submit(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(promptUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
bool SessionManager::Interrupt(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
int rc = ollie_interrupt(const_cast<char*>(idUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
// --- State queries ---
|
||||
|
||||
QString SessionManager::GetState(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_get_state(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return QString();
|
||||
QString state = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return state;
|
||||
}
|
||||
|
||||
QString SessionManager::GetChat(const QString &session_id, qlonglong offset, qlonglong &new_offset)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int64_t newOff = 0;
|
||||
int rc = ollie_get_chat(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
static_cast<int64_t>(offset), &out, &newOff);
|
||||
if (rc != 0 || !out) {
|
||||
new_offset = offset;
|
||||
return QString();
|
||||
}
|
||||
QString text = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
new_offset = static_cast<qlonglong>(newOff);
|
||||
return text;
|
||||
}
|
||||
|
||||
QString SessionManager::GetUsage(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_get_usage(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return QString();
|
||||
QString usage = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return usage;
|
||||
}
|
||||
|
||||
QString SessionManager::GetCost(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_get_cost(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return QString();
|
||||
QString cost = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return cost;
|
||||
}
|
||||
|
||||
// --- Config ---
|
||||
|
||||
QString SessionManager::GetConfig(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_get_config(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return QString();
|
||||
QString config = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return config;
|
||||
}
|
||||
|
||||
bool SessionManager::SetConfig(const QString &session_id, const QString &key, const QString &value)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray keyUtf8 = key.toUtf8();
|
||||
QByteArray valueUtf8 = value.toUtf8();
|
||||
int rc = ollie_set_config(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(keyUtf8.constData()),
|
||||
const_cast<char*>(valueUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
// --- Context ---
|
||||
|
||||
QString SessionManager::GetContext(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_get_context(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return QString();
|
||||
QString context = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return context;
|
||||
}
|
||||
|
||||
// --- Backends/models/agents ---
|
||||
|
||||
QStringList SessionManager::ListBackends()
|
||||
{
|
||||
char *out = nullptr;
|
||||
ollie_list_backends(&out);
|
||||
if (!out) return {};
|
||||
QString raw = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
QStringList SessionManager::ListModels(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_list_models(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return {};
|
||||
QString raw = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
QStringList SessionManager::ListAgents()
|
||||
{
|
||||
char *out = nullptr;
|
||||
ollie_list_agents(&out);
|
||||
if (!out) return {};
|
||||
QString raw = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
// --- Peers ---
|
||||
|
||||
bool SessionManager::PeerAdd(const QString &session_id, const QString &peer_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray peerUtf8 = peer_id.toUtf8();
|
||||
int rc = ollie_peer_add(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(peerUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
bool SessionManager::PeerRemove(const QString &session_id, const QString &peer_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray peerUtf8 = peer_id.toUtf8();
|
||||
int rc = ollie_peer_remove(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(peerUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
QStringList SessionManager::PeerList(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
ollie_peer_list(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (!out) return {};
|
||||
QString raw = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
bool SessionManager::PeerSubmit(const QString &session_id, const QString &peer_id, const QString &prompt)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray peerUtf8 = peer_id.toUtf8();
|
||||
QByteArray promptUtf8 = prompt.toUtf8();
|
||||
int rc = ollie_peer_submit(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(peerUtf8.constData()),
|
||||
const_cast<char*>(promptUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
// --- Notifications ---
|
||||
|
||||
void SessionManager::handleStateChange(const QString &session_id, const QString &new_state)
|
||||
{
|
||||
QString prev = m_sessionStates.value(session_id, "idle");
|
||||
m_sessionStates[session_id] = new_state;
|
||||
|
||||
if (new_state == "idle" && prev != "idle") {
|
||||
sendNotification("Ollie", "Session " + session_id + " finished");
|
||||
}
|
||||
|
||||
emit StateChanged(session_id, new_state);
|
||||
}
|
||||
|
||||
void SessionManager::sendNotification(const QString &title, const QString &body)
|
||||
{
|
||||
QDBusInterface notifyIface(
|
||||
"org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
QDBusConnection::sessionBus());
|
||||
notifyIface.call("Notify",
|
||||
"ollie-kde", // app_name
|
||||
(uint)0, // replaces_id
|
||||
"system-run", // app_icon
|
||||
title, // summary
|
||||
body, // body
|
||||
QStringList(), // actions
|
||||
QVariantMap(), // hints
|
||||
(int)5000); // timeout ms
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
#ifndef SESSION_MANAGER_H
|
||||
#define SESSION_MANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QHash>
|
||||
|
||||
// SessionManager is the D-Bus adaptee. The generated SessionManagerAdaptor
|
||||
// (from qt6_add_dbus_adaptor) forwards D-Bus method calls to slots here.
|
||||
// Signals defined here are relayed by the adaptor onto the bus.
|
||||
|
||||
class SessionManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SessionManager(QObject *parent = nullptr);
|
||||
~SessionManager() override;
|
||||
|
||||
public slots:
|
||||
// Session lifecycle
|
||||
QString CreateSession(const QString &cwd, const QString &backend,
|
||||
const QString &model, const QString &agent);
|
||||
QStringList ListSessions();
|
||||
bool KillSession(const QString &session_id);
|
||||
bool RenameSession(const QString &session_id, const QString &new_name);
|
||||
|
||||
// Interaction
|
||||
bool Submit(const QString &session_id, const QString &prompt);
|
||||
bool Interrupt(const QString &session_id);
|
||||
|
||||
// State queries
|
||||
QString GetState(const QString &session_id);
|
||||
QString GetChat(const QString &session_id, qlonglong offset, qlonglong &new_offset);
|
||||
QString GetUsage(const QString &session_id);
|
||||
QString GetCost(const QString &session_id);
|
||||
|
||||
// Config
|
||||
QString GetConfig(const QString &session_id);
|
||||
bool SetConfig(const QString &session_id, const QString &key, const QString &value);
|
||||
|
||||
// Context
|
||||
QString GetContext(const QString &session_id);
|
||||
|
||||
// Backends/models/agents
|
||||
QStringList ListBackends();
|
||||
QStringList ListModels(const QString &session_id);
|
||||
QStringList ListAgents();
|
||||
|
||||
// Peers
|
||||
bool PeerAdd(const QString &session_id, const QString &peer_id);
|
||||
bool PeerRemove(const QString &session_id, const QString &peer_id);
|
||||
QStringList PeerList(const QString &session_id);
|
||||
bool PeerSubmit(const QString &session_id, const QString &peer_id, const QString &prompt);
|
||||
|
||||
signals:
|
||||
void SessionCreated(const QString &session_id);
|
||||
void SessionKilled(const QString &session_id);
|
||||
void StateChanged(const QString &session_id, const QString &new_state);
|
||||
void ChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text);
|
||||
|
||||
private:
|
||||
void installCallbacks();
|
||||
void sendNotification(const QString &title, const QString &body);
|
||||
|
||||
QHash<QString, QString> m_sessionStates;
|
||||
|
||||
private slots:
|
||||
void handleStateChange(const QString &session_id, const QString &new_state);
|
||||
};
|
||||
|
||||
#endif // SESSION_MANAGER_H
|
||||
635
lib/capi.go
635
lib/capi.go
|
|
@ -1,635 +0,0 @@
|
|||
package main
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Callback typedefs — set by the C++ daemon to receive push events.
|
||||
typedef void (*ollie_state_cb)(const char *session_id, const char *state);
|
||||
typedef void (*ollie_chat_cb)(const char *session_id, int64_t offset, const char *text, int len);
|
||||
|
||||
// Trampolines: cgo cannot call function pointers directly from Go.
|
||||
static inline void ollie_state_cb_invoke(ollie_state_cb cb, const char *sid, const char *state) {
|
||||
if (cb) cb(sid, state);
|
||||
}
|
||||
static inline void ollie_chat_cb_invoke(ollie_chat_cb cb, const char *sid, int64_t offset, const char *text, int len) {
|
||||
if (cb) cb(sid, offset, text, len);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"ollie/pkg/agent"
|
||||
"ollie/pkg/backend"
|
||||
"ollie/pkg/config"
|
||||
"ollie/pkg/env"
|
||||
"ollie/pkg/paths"
|
||||
"ollie/pkg/tools"
|
||||
"ollie/pkg/tools/execute"
|
||||
)
|
||||
|
||||
// --- Global state ---
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
sessions map[string]*managedSession
|
||||
stateCB C.ollie_state_cb
|
||||
chatCB C.ollie_chat_cb
|
||||
)
|
||||
|
||||
// managedSession wraps a Core with its context and metadata.
|
||||
type managedSession struct {
|
||||
core agent.Core
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
id string
|
||||
agent string
|
||||
log []byte
|
||||
logMu sync.Mutex
|
||||
peers map[string]bool
|
||||
}
|
||||
|
||||
// --- Lifecycle ---
|
||||
|
||||
//export ollie_init
|
||||
func ollie_init(configPath *C.char) C.int {
|
||||
env.EnsureDefaults()
|
||||
mu.Lock()
|
||||
sessions = make(map[string]*managedSession)
|
||||
mu.Unlock()
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_shutdown
|
||||
func ollie_shutdown() {
|
||||
mu.Lock()
|
||||
for id, sess := range sessions {
|
||||
sess.core.Interrupt(agent.ErrInterrupted)
|
||||
sess.core.Close()
|
||||
sess.cancel()
|
||||
delete(sessions, id)
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// --- Sessions ---
|
||||
|
||||
//export ollie_create_session
|
||||
func ollie_create_session(cwdC *C.char, backendC *C.char, modelC *C.char, agentC *C.char, outID **C.char) C.int {
|
||||
cwd := C.GoString(cwdC)
|
||||
backendName := C.GoString(backendC)
|
||||
modelName := C.GoString(modelC)
|
||||
agentName := C.GoString(agentC)
|
||||
|
||||
if cwd == "" {
|
||||
cwd, _ = os.Getwd()
|
||||
}
|
||||
cwd = paths.ExpandHome(os.ExpandEnv(cwd))
|
||||
|
||||
if agentName == "" {
|
||||
agentName = "default"
|
||||
if v := os.Getenv("OLLIE_DEFAULT_AGENT"); v != "" {
|
||||
agentName = v
|
||||
}
|
||||
}
|
||||
|
||||
sessID := agent.NewSessionID()
|
||||
|
||||
// Load agent config
|
||||
agentsDir := paths.CfgDir() + "/agents"
|
||||
var cfg *config.Config
|
||||
cfgPath := agent.AgentConfigPath(agentsDir, agentName)
|
||||
if f, err := os.Open(cfgPath); err == nil {
|
||||
cfg, _ = config.Load(f)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// Resolve backend
|
||||
if backendName == "" && cfg != nil && cfg.Backend != "" {
|
||||
backendName = cfg.Backend
|
||||
}
|
||||
be, err := backend.NewWithName(backendName)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Resolve model
|
||||
if modelName == "" && cfg != nil && cfg.Model != "" {
|
||||
modelName = cfg.Model
|
||||
}
|
||||
if modelName == "" {
|
||||
modelName = os.Getenv("OLLIE_MODEL")
|
||||
}
|
||||
if modelName != "" {
|
||||
be.SetModel(modelName)
|
||||
}
|
||||
|
||||
// Sessions dir
|
||||
sessionsDir := paths.DataDir() + "/sessions"
|
||||
os.MkdirAll(sessionsDir, 0700)
|
||||
|
||||
// Build dispatcher + runtime
|
||||
newDisp := tools.NewDispatcherFunc(map[string]func() tools.Server{
|
||||
"execute": execute.Decl(cwd),
|
||||
})
|
||||
rt := agent.BuildRuntime(cfg, newDisp(), cwd, []string{"OLLIE_SESSION_ID=" + sessID})
|
||||
|
||||
// Create the agent core
|
||||
core := agent.NewAgentCore(agent.AgentCoreConfig{
|
||||
Backend: be,
|
||||
AgentName: agentName,
|
||||
AgentsDir: agentsDir,
|
||||
SessionsDir: sessionsDir,
|
||||
SessionID: sessID,
|
||||
CWD: cwd,
|
||||
Runtime: rt,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sess := &managedSession{
|
||||
core: core,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
id: sessID,
|
||||
agent: agentName,
|
||||
}
|
||||
|
||||
// Subscribe to events for the chat log + callback dispatch
|
||||
streamingRole := "" // tracks current streaming role ("assistant" or "reasoning")
|
||||
core.Bus().Subscribe("event", func(ev agent.Event) {
|
||||
var text string
|
||||
switch ev.Role {
|
||||
case "assistant", "reasoning":
|
||||
if streamingRole != ev.Role {
|
||||
if streamingRole != "" {
|
||||
text += "\n\n"
|
||||
}
|
||||
text += "[" + ev.Role + "]\n"
|
||||
streamingRole = ev.Role
|
||||
}
|
||||
text += ev.Content
|
||||
default:
|
||||
if ev.Role == "usage" {
|
||||
return
|
||||
}
|
||||
if streamingRole != "" {
|
||||
text += "\n\n"
|
||||
streamingRole = ""
|
||||
}
|
||||
switch ev.Role {
|
||||
case "user":
|
||||
text += "[user]\n" + ev.Content + "\n\n"
|
||||
case "call":
|
||||
text += "[call:" + ev.Name + "]\n" + ev.Content + "\n\n"
|
||||
case "tool":
|
||||
text += "[tool:" + ev.Name + "]\n" + ev.Content + "\n\n"
|
||||
case "error":
|
||||
text += "[error]\n" + ev.Content + "\n\n"
|
||||
case "info":
|
||||
text += "[info] " + ev.Content + "\n\n"
|
||||
default:
|
||||
text += "[" + ev.Role + "]\n" + ev.Content + "\n\n"
|
||||
}
|
||||
}
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
sess.logMu.Lock()
|
||||
offset := int64(len(sess.log))
|
||||
sess.log = append(sess.log, []byte(text)...)
|
||||
sess.logMu.Unlock()
|
||||
|
||||
if chatCB != nil {
|
||||
cID := C.CString(sess.id)
|
||||
cText := C.CString(text)
|
||||
C.ollie_chat_cb_invoke(chatCB, cID, C.int64_t(offset), cText, C.int(len(text)))
|
||||
C.free(unsafe.Pointer(cID))
|
||||
C.free(unsafe.Pointer(cText))
|
||||
}
|
||||
})
|
||||
|
||||
// Watch state changes in a goroutine
|
||||
go func() {
|
||||
current := "idle"
|
||||
for {
|
||||
next, ok := core.WaitChange(ctx, agent.WatchState, current)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
current = next
|
||||
if stateCB != nil {
|
||||
cID := C.CString(sess.id)
|
||||
cState := C.CString(current)
|
||||
C.ollie_state_cb_invoke(stateCB, cID, cState)
|
||||
C.free(unsafe.Pointer(cID))
|
||||
C.free(unsafe.Pointer(cState))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mu.Lock()
|
||||
sessions[sessID] = sess
|
||||
mu.Unlock()
|
||||
|
||||
*outID = C.CString(sessID)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_kill_session
|
||||
func ollie_kill_session(sessionIDC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
mu.Lock()
|
||||
sess, ok := sessions[id]
|
||||
if !ok {
|
||||
mu.Unlock()
|
||||
return -1
|
||||
}
|
||||
delete(sessions, id)
|
||||
mu.Unlock()
|
||||
|
||||
sess.core.Interrupt(agent.ErrInterrupted)
|
||||
sess.core.Close()
|
||||
sess.cancel()
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_rename_session
|
||||
func ollie_rename_session(sessionIDC *C.char, newNameC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
newName := C.GoString(newNameC)
|
||||
|
||||
mu.Lock()
|
||||
sess, ok := sessions[id]
|
||||
if !ok {
|
||||
mu.Unlock()
|
||||
return -1
|
||||
}
|
||||
delete(sessions, id)
|
||||
sess.id = newName
|
||||
sessions[newName] = sess
|
||||
mu.Unlock()
|
||||
|
||||
if err := sess.core.SetSessionID(newName); err != nil {
|
||||
mu.Lock()
|
||||
delete(sessions, newName)
|
||||
sess.id = id
|
||||
sessions[id] = sess
|
||||
mu.Unlock()
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_list_sessions
|
||||
func ollie_list_sessions(outBuf **C.char) C.int {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
|
||||
var result string
|
||||
for id, sess := range sessions {
|
||||
result += fmt.Sprintf("%s\t%s\t%s\t%s\n",
|
||||
id, sess.core.State(), sess.core.ModelName(), sess.agent)
|
||||
}
|
||||
*outBuf = C.CString(result)
|
||||
return C.int(len(sessions))
|
||||
}
|
||||
|
||||
// --- Interaction ---
|
||||
|
||||
//export ollie_submit
|
||||
func ollie_submit(sessionIDC *C.char, promptC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
prompt := C.GoString(promptC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
go sess.core.Submit(sess.ctx, prompt)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_interrupt
|
||||
func ollie_interrupt(sessionIDC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
sess.core.Interrupt(agent.ErrInterrupted)
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- State queries ---
|
||||
|
||||
//export ollie_get_state
|
||||
func ollie_get_state(sessionIDC *C.char, outState **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
*outState = C.CString(sess.core.State())
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_get_chat
|
||||
func ollie_get_chat(sessionIDC *C.char, offset C.int64_t, outText **C.char, outNewOffset *C.int64_t) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
sess.logMu.Lock()
|
||||
off := int64(offset)
|
||||
if off < 0 {
|
||||
off = 0
|
||||
}
|
||||
if off > int64(len(sess.log)) {
|
||||
off = int64(len(sess.log))
|
||||
}
|
||||
text := string(sess.log[off:])
|
||||
newOff := int64(len(sess.log))
|
||||
sess.logMu.Unlock()
|
||||
|
||||
*outText = C.CString(text)
|
||||
*outNewOffset = C.int64_t(newOff)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_get_usage
|
||||
func ollie_get_usage(sessionIDC *C.char, out **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
*out = C.CString(sess.core.Usage())
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_get_cost
|
||||
func ollie_get_cost(sessionIDC *C.char, out **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
*out = C.CString(sess.core.Cost())
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_get_config
|
||||
func ollie_get_config(sessionIDC *C.char, outBuf **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Expose key=value pairs for session config
|
||||
var result string
|
||||
result += "model=" + sess.core.ModelName() + "\n"
|
||||
result += "backend=" + sess.core.BackendName() + "\n"
|
||||
result += "agent=" + sess.agent + "\n"
|
||||
result += "cwd=" + sess.core.CWD() + "\n"
|
||||
result += "state=" + sess.core.State() + "\n"
|
||||
*outBuf = C.CString(result)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_set_config
|
||||
func ollie_set_config(sessionIDC *C.char, keyC *C.char, valueC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
key := C.GoString(keyC)
|
||||
value := C.GoString(valueC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "model":
|
||||
// Submit a /model command to change model
|
||||
sess.core.Submit(sess.ctx, "/model "+value)
|
||||
case "cwd":
|
||||
if err := sess.core.SetCWD(value); err != nil {
|
||||
return -1
|
||||
}
|
||||
default:
|
||||
return -1 // unknown key
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_get_context
|
||||
func ollie_get_context(sessionIDC *C.char, outBuf **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Return context as JSONL (one JSON message per line)
|
||||
msgs := sess.core.Context()
|
||||
var result string
|
||||
for _, msg := range msgs {
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
result += string(data) + "\n"
|
||||
}
|
||||
*outBuf = C.CString(result)
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- Peers ---
|
||||
|
||||
//export ollie_peer_add
|
||||
func ollie_peer_add(sessionIDC *C.char, peerIDC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
peerID := C.GoString(peerIDC)
|
||||
|
||||
mu.RLock()
|
||||
_, ok := sessions[id]
|
||||
_, peerOk := sessions[peerID]
|
||||
mu.RUnlock()
|
||||
if !ok || !peerOk {
|
||||
return -1
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
sess := sessions[id]
|
||||
if sess.peers == nil {
|
||||
sess.peers = make(map[string]bool)
|
||||
}
|
||||
sess.peers[peerID] = true
|
||||
mu.Unlock()
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_peer_remove
|
||||
func ollie_peer_remove(sessionIDC *C.char, peerIDC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
peerID := C.GoString(peerIDC)
|
||||
|
||||
mu.Lock()
|
||||
sess, ok := sessions[id]
|
||||
if !ok {
|
||||
mu.Unlock()
|
||||
return -1
|
||||
}
|
||||
delete(sess.peers, peerID)
|
||||
mu.Unlock()
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_peer_list
|
||||
func ollie_peer_list(sessionIDC *C.char, outBuf **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
var result string
|
||||
for peerID := range sess.peers {
|
||||
result += peerID + "\n"
|
||||
}
|
||||
*outBuf = C.CString(result)
|
||||
return C.int(len(sess.peers))
|
||||
}
|
||||
|
||||
//export ollie_peer_submit
|
||||
func ollie_peer_submit(sessionIDC *C.char, peerIDC *C.char, promptC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
peerID := C.GoString(peerIDC)
|
||||
prompt := C.GoString(promptC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
peer, peerOk := sessions[peerID]
|
||||
mu.RUnlock()
|
||||
if !ok || !peerOk {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Verify peer is registered
|
||||
if !sess.peers[peerID] {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Submit to the peer session
|
||||
go peer.core.Submit(peer.ctx, prompt)
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- Backends/models/agents ---
|
||||
|
||||
//export ollie_list_backends
|
||||
func ollie_list_backends(out **C.char) C.int {
|
||||
backends := "ollama\nopenai\nopenrouter\nanthropic\ncopilot\nkiro\ngemini\n"
|
||||
*out = C.CString(backends)
|
||||
return 7
|
||||
}
|
||||
|
||||
//export ollie_list_models
|
||||
func ollie_list_models(sessionIDC *C.char, out **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
*out = C.CString(sess.core.ListModels())
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_list_agents
|
||||
func ollie_list_agents(out **C.char) C.int {
|
||||
var result string
|
||||
var count int
|
||||
for _, dir := range agent.AgentsDirs() {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if len(name) > 5 && name[len(name)-5:] == ".json" {
|
||||
result += name[:len(name)-5] + "\n"
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
*out = C.CString(result)
|
||||
return C.int(count)
|
||||
}
|
||||
|
||||
// --- Callbacks ---
|
||||
|
||||
//export ollie_on_state_change
|
||||
func ollie_on_state_change(cb C.ollie_state_cb) {
|
||||
stateCB = cb
|
||||
}
|
||||
|
||||
//export ollie_on_chat_update
|
||||
func ollie_on_chat_update(cb C.ollie_chat_cb) {
|
||||
chatCB = cb
|
||||
}
|
||||
|
||||
// --- Memory ---
|
||||
|
||||
//export ollie_free
|
||||
func ollie_free(ptr *C.char) {
|
||||
C.free(unsafe.Pointer(ptr))
|
||||
}
|
||||
|
||||
// main is required for c-shared but never called.
|
||||
func main() {}
|
||||
12
lib/go.mod
12
lib/go.mod
|
|
@ -1,12 +0,0 @@
|
|||
module ollie-kde
|
||||
|
||||
go 1.25.6
|
||||
|
||||
require ollie v0.0.0-00010101000000-000000000000
|
||||
|
||||
require (
|
||||
github.com/simonfxr/pubsub v0.0.5 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace ollie => ../../core
|
||||
14
lib/go.sum
14
lib/go.sum
|
|
@ -1,14 +0,0 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/simonfxr/pubsub v0.0.5 h1:DJfvFoglqGvwJriIOC5NI5um34n2YX8KAU5+7jv768w=
|
||||
github.com/simonfxr/pubsub v0.0.5/go.mod h1:bQ+B2NEEHZ08VY/0xVFGaGL1KMJ7cl3yLaXe8xvyC24=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Ollie Session Manager
|
||||
GenericName=AI Agent D-Bus Daemon
|
||||
Comment=D-Bus session manager for Ollie AI agent sessions
|
||||
Exec=/usr/bin/ollie-kde
|
||||
Icon=ollie
|
||||
Terminal=false
|
||||
Categories=Utility;
|
||||
OnlyShowIn=KDE;
|
||||
X-KDE-autostart-phase=1
|
||||
X-DBUS-ServiceName=org.ollie.SessionManager
|
||||
Reference in New Issue