Update KDE README and remove D-Bus dependencies

- Rewrite README.md with accurate 9P-based architecture
- Remove all D-Bus dependencies:
  - Update dolphin/ollie-ask to use ollie-9p rdwr generate
  - Update kate/ollie_ghost.cpp to use ollie-9p rdwr complete
  - Remove QDBusInterface from ollie_ghost.h
  - Fix OllieGhostProvider constructor in ollie_kate.cpp
  - Remove D-Bus comments from gui/main.cpp and kate/ollie_kate.cpp
  - Remove QtDBus from CMakeLists.txt (KF5 and KF6)
  - Delete D-Bus test scripts test-e2e.sh and test-peers.sh
- Clarify no system tray component exists
- Update build/install instructions with just commands
This commit is contained in:
Levi Neely 2026-08-05 18:53:30 +02:00
parent 73e262ffaf
commit ce36dcde52
9 changed files with 154 additions and 241 deletions

View File

@ -39,7 +39,7 @@ endif()
if(OLLIE_KF5)
# --- Qt5 / KF5 ---
find_package(Qt5 REQUIRED COMPONENTS Core Concurrent DBus Widgets Quick Qml QuickControls2 Network)
find_package(Qt5 REQUIRED COMPONENTS Core Concurrent Widgets Quick Qml QuickControls2 Network)
# Override KDE_INSTALL_PLUGINDIR so kcoreaddons_add_plugin installs under
# CMAKE_INSTALL_PREFIX (e.g. ~/.local/lib/qt5/plugins), not the system dir.
@ -138,7 +138,6 @@ else()
target_link_libraries(krunner_ollie
KF6::Runner
Qt6::Core
Qt6::DBus
Qt6::Widgets
)
@ -159,7 +158,6 @@ else()
KF6::TextEditor
KF6::SyntaxHighlighting
Qt6::Core
Qt6::DBus
Qt6::Widgets
$ENV{HOME}/.local/lib/libollie9p.so
)

167
README.md
View File

@ -1,78 +1,97 @@
# ollie-kde
KDE-native integration for [ollie](../README.md) — thin KDE clients (system
tray, Plasmoid, KRunner, Kate plugin, Dolphin menus) that talk to the
`org.ollie.SessionManager` D-Bus interface.
KDE-native integration for [ollie](../README.md) — thin KDE clients (GUI application, KRunner plugin, Kate plugin, Dolphin menus, Plasmoid) that connect directly to the Ollie 9P filesystem.
## Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ Plasma Desktop │
│ System Tray │ KRunner │ Plasmoid │ Kate Plugin │ Dolphin │
│ GUI App │ KRunner │ Kate Plugin │ Dolphin │ Plasmoid │
└──────────────────────────┬───────────────────────────────────┘
│ D-Bus (org.ollie.SessionManager)
│ 9P filesystem (ollie-9p transport)
│
┌────────────────┴────────────────┐
│ │
┌─────────┴──────────┐ ┌───────────┴──────────────────┐
│ ollied (../dbus) │ │ olliesrv -nodbus=false │
│ Pure D-Bus daemon│ │ (../9p, embedded adapter) │
│ Go │ godbus │ │ Go │ 9P + D-Bus │
│ olliesrv (../9p) │ │ ollie-9p (../cmd/ollie-9p) │
│ 9P server │ │ 9P client + transport │
│ Go │ lib9p │ │ Go │ lib9p │
└────────────────────┘ └──────────────────────────────┘
```
Two backends provide the D-Bus interface — pick one:
All KDE components use the 9P filesystem directly via `ollie-9p` subprocess calls. **No D-Bus dependency** — the architecture is fully 9P-based.
- **ollied** (`../dbus/`) — standalone D-Bus daemon, pure Go, no 9P. Lighter weight.
- **olliesrv** (`../9p/`) — the full 9P server with an embedded D-Bus adapter (enabled by default; disable with `-nodbus`).
## Components
Both register the same `org.ollie.SessionManager` bus name. They cannot run
side by side — choose one. KDE components here are pure D-Bus clients,
lightweight, stateless, and independently restartable.
- **ollie-gui** — Main Qt/QML GUI application with session management and chat interface
- **krunner_ollie.so** — KRunner plugin for quick prompts and session management
- **ollie_kate.so** — Kate plugin with integrated chat panel and editor integration
- **Plasmoid** — Desktop widget for session monitoring and quick access
- **Dolphin service menus** — Right-click actions for files and directories
**Note**: No system tray component exists (old documentation was incorrect).
## Dependencies
- 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)
- Qt 6 (Core, Widgets, Quick, QuickControls2) or Qt 5 (Core, Widgets, Quick, Qml, QuickControls2)
- KDE Frameworks 6 (Runner, CoreAddons, Config, TextEditor, SyntaxHighlighting) or KF5 equivalents
- Plasma 6 or Plasma 5 (for Plasmoid)
- ECM (Extra CMake Modules)
- libollie9p.so (from `../lib9p/`)
KF6/Qt6 is the default. To build against KF5/Qt5:
```sh
make kf5
just kde-kf5
```
## Build
```sh
make # builds tray + KRunner + Kate + Plasmoid via CMake
just kde # builds all components (KF6)
just kde-kf5 # builds all components (KF5)
```
Outputs:
- `build-cmake/ollie-tray` — system tray icon
- `build-cmake/ollie-gui` — main GUI application
- `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
- `build-cmake/lib/libollie-plasmoid-plugin.so` — Plasmoid QML plugin
- `build-cmake/org/ollie/private/ollie-plasmoid-pluginplugin.so` — Plasmoid plugin loader
## Install
```sh
sudo make install # all KDE components (KF6)
sudo make kf5-install # all KDE components (KF5)
# Individual:
make krunner-install
make dolphin-install
make plasmoid-install
just install-kde # installs all KDE components (KF6)
just install-kde-kf5 # installs all KDE components (KF5)
```
The D-Bus daemon is installed separately — either `ollied` from `../dbus/` or use `olliesrv` from `../9p/` (which includes D-Bus by default).
The installation:
- Installs binaries to `~/.local/bin/`
- Installs plugins to `~/.local/lib64/qt6/plugins/` (KF6) or `~/.local/lib/qt5/plugins/` (KF5)
- Installs Dolphin service menus to `~/.local/share/kio/servicemenus/`
- Installs desktop entry to `~/.local/share/applications/`
- Installs icon to `~/.local/share/icons/hicolor/scalable/apps/`
- Sets up environment script in `~/.config/plasma-workspace/env/`
**Note**: Restart Plasma (`plasmashell --replace` or log out/in) to pick up new plugins.
## Usage
### GUI Application (`ollie-gui`)
Launch from application menu or terminal:
```sh
ollie-gui
```
Features:
- Session list with state indicators (idle, thinking, tool call)
- Live chat view with auto-scroll
- Inline prompt input
- Real-time updates via 9P polling
- Theme support (light/dark/system)
### KRunner
- `ollie <prompt>` — one-shot generation via `Generate` (no session created); shows result in a popup
@ -87,52 +106,86 @@ Right-click any file or directory:
### Kate
Enable the "Ollie" plugin in Settings → Configure Kate → Plugins. Panel appears
on the right with:
Enable the "Ollie" plugin in Settings → Configure Kate → Plugins. Panel appears on the right with:
- Session indicator + CWD label
- **New** — create session in project/file directory
- **Ask File** — send current file path for description
- **Explain** — send selected text for explanation
- Prompt input for free-form messages
- Live-streaming chat view
- Editor integration (inline annotations, code block application, ghost text completion)
### Plasmoid
Add the "Ollie" widget to your panel or desktop:
- Session list with state indicators (○ idle, ● thinking, ⚡ tool call)
- Session list with state indicators
- Live chat view with auto-scroll
- Inline prompt input
- Real-time updates via D-Bus signals
### System Tray
- Tooltip: session count
- Status: passive (no sessions), active (sessions idle), attention (agent working)
- Right-click: New Session, Sessions submenu (kill each), Stop All, Quit
- Real-time updates via 9P polling
## File Layout
```
ollie-kde/
├── Makefile # CMake orchestration
├── CMakeLists.txt
├── tray/
│ ├── main.cpp
│ └── ollie_tray.h/cpp # KStatusNotifierItem
├── justfile # Build orchestration
├── CMakeLists.txt # Main CMake configuration
├── CMakeLists-kf5.txt # KF5-specific overrides
├── gui/
│ ├── main.cpp # GUI application entry
│ ├── main.qml # QML interface
│ ├── ollie9pclient.h/cpp # 9P client for GUI
│ ├── lib9pclient.h/cpp # Shared 9P client library
│ ├── chatblockmodel.h/cpp # Chat data model
│ ├── thememanager.h/cpp # Theme management
│ ├── streamfsm.h/cpp # Streaming state machine
│ ├── ninepconnection.h/cpp # 9P connection management
│ └── org.ollie.gui.desktop # Desktop entry
├── krunner/
│ ├── metadata.json
│ └── ollie_runner.h/cpp # KRunner plugin
│ ├── metadata.json # Plugin metadata
│ └── ollie_runner.h/cpp # KRunner plugin (9P-based)
├── kate/
│ ├── metadata.json
│ └── ollie_kate.h/cpp # KTextEditor plugin
│ ├── metadata.json # Plugin metadata
│ ├── ollie_kate.h/cpp # Kate plugin (9P-based)
│ ├── ollie_ghost.h/cpp # Ghost text provider (9P-based)
│ ├── chat_renderer.h/cpp # Chat rendering engine
│ ├── ktexteditor_utils.h # Editor utilities
│ └── diffparams.h # Diff viewer parameters
├── plasmoid/
│ ├── metadata.json
│ ├── contents/ui/main.qml
│ └── plugin/
│ └── olliedbusclient.h/cpp # QML singleton (D-Bus client)
│ ├── plugin/
│ │ └── olliedbusclient.h/cpp # QML singleton (9P client - misnamed)
│ └── contents/ui/main.qml # Plasmoid QML interface
├── dolphin/
│ ├── ollie-actions.desktop # Service menu entries
│ ├── ollie-ask # One-shot helper script
│ └── ollie-session-here # Session creation helper
└── test-e2e.sh # End-to-end test script
│ ├── ollie-actions.desktop # Service menu entries (KF6)
│ ├── ollie-actions-kf5.desktop # Service menu entries (KF5)
│ ├── ollie-ask # One-shot helper script (9P-based)
│ └── ollie-session-here # Session creation helper (9P-based)
├── 99-ollie.sh # Environment script (KF6)
├── 99-ollie-kf5.sh # Environment script (KF5)
└── test-peers.sh # Peer connection tests
```
## Development Notes
- **All components use `ollie-9p` for 9P transport** — no D-Bus dependencies
- The Kate plugin includes sophisticated editor integration with ghost text, inline annotations, and diff viewing
- The GUI application uses Qt Quick for a modern, responsive interface
- Build system supports both KF6/Qt6 and KF5/Qt5 via CMake option `OLLIE_KF5`
- Installation is user-local (`~/.local/`) by default, no system-wide installation needed
## Troubleshooting
**Plugins not appearing after installation:**
- Run `kbuildsycoca6` (KF6) or `kbuildsycoca5` (KF5) to rebuild system cache
- Restart Plasma: `plasmashell --replace` or log out/in
**"ollie-9p not found" errors:**
- Ensure `ollie-9p` is in your PATH or build/install the main Ollie project first
**Kate plugin crashes:**
- Check that `libollie9p.so` is installed in `~/.local/lib/`
- Verify Kate is using the same Qt version as the plugin (Qt5 vs Qt6)
**Dolphin actions not working:**
- Ensure `ollie-ask` and `ollie-session-here` are installed in `~/.local/bin/`
- Check that the service menu desktop files are in `~/.local/share/kio/servicemenus/`

View File

@ -6,17 +6,11 @@ TARGET="$1"
PREFIX="${2:-Describe and explain this file:}"
PROMPT="$PREFIX $TARGET"
SERVICE="org.ollie.SessionManager"
OBJ="/org/ollie/SessionManager"
IFACE="org.ollie.SessionManager"
# Use Generate one-shot — no session needed
RESULT=$(gdbus call --session --dest $SERVICE --object-path $OBJ \
--method $IFACE.Generate "$PROMPT" "" "" "" 2>/dev/null | \
sed "s/^('//; s/')$//; s/\\\\n/\n/g")
# Use Generate one-shot via 9P
RESULT=$(echo "$PROMPT" | ollie-9p rdwr generate 2>/dev/null)
if [ -z "$RESULT" ]; then
notify-send "Ollie" "No response (is olliesrv or ollied running?)"
notify-send "Ollie" "No response (is olliesrv running?)"
exit 1
fi

View File

@ -1,5 +1,5 @@
// ollie-gui-9p: minimal Qt GUI using 9P for all communication.
// No D-Bus dependency. Uses ollie-9p subprocess for 9P transport.
// Uses ollie-9p subprocess for 9P transport.
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

View File

@ -8,18 +8,13 @@
#include <KTextEditor/ConfigInterface>
#endif
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingReply>
#include <QDBusPendingCallWatcher>
#include <QApplication>
#include <QFileInfo>
#include <QPaintEvent>
#include <QScrollBar>
static const char *DBUS_SERVICE = "org.ollie.SessionManager";
static const char *DBUS_PATH = "/org/ollie/SessionManager";
static const char *DBUS_IFACE = "org.ollie.SessionManager";
#include <QProcess>
#include <QJsonDocument>
#include <QJsonObject>
// === GhostOverlay ===
@ -85,10 +80,9 @@ void GhostOverlay::paintEvent(QPaintEvent *event)
// === OllieGhostProvider ===
OllieGhostProvider::OllieGhostProvider(KTextEditor::View *view, QDBusInterface *iface, OllieKatePlugin *plugin, QObject *parent)
OllieGhostProvider::OllieGhostProvider(KTextEditor::View *view, OllieKatePlugin *plugin, QObject *parent)
: KTextEditor::InlineNoteProvider() // note: KDE's ctor takes no parent arg
, m_view(view)
, m_iface(iface)
, m_plugin(plugin)
, m_debounce(new QTimer(this))
{
@ -317,22 +311,39 @@ void OllieGhostProvider::requestCompletion()
context += QStringLiteral("model=") + model + QStringLiteral("\n");
}
QDBusMessage msg = QDBusMessage::createMethodCall(
DBUS_SERVICE, DBUS_PATH, DBUS_IFACE, "Complete");
msg << cwd << filePath << prefix << suffix << context;
// Build JSON request for ollie-9p rdwr complete
QJsonObject request;
request["file"] = filePath;
request["prefix"] = prefix;
request["suffix"] = suffix;
if (!context.isEmpty()) {
request["context"] = context;
}
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg, 15000);
auto *watcher = new QDBusPendingCallWatcher(pending, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) {
w->deleteLater();
QJsonDocument docJson(request);
QString jsonStr = QString::fromUtf8(docJson.toJson(QJsonDocument::Compact));
// Run ollie-9p rdwr complete
QProcess *process = new QProcess(this);
process->setProgram("ollie-9p");
process->setArguments({"rdwr", "complete"});
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) {
process->deleteLater();
m_requesting = false;
QDBusPendingReply<QString> reply = *w;
if (reply.isError()) {
if (exitStatus != QProcess::NormalExit || exitCode != 0) {
return;
}
onCompletionReply(reply.value());
QString result = QString::fromUtf8(process->readAllStandardOutput());
onCompletionReply(result);
});
process->start();
process->write(jsonStr.toUtf8());
process->closeWriteChannel();
}
void OllieGhostProvider::onCompletionReply(const QString &result)

View File

@ -7,7 +7,6 @@
#include <KTextEditor/Cursor>
#include <ktexteditor_version.h>
#include <QDBusInterface>
#include <QTimer>
#include <QPainter>
#include <QFont>
@ -28,7 +27,7 @@ class GhostOverlay;
*
* Flow:
* 1. Cursor moves or text changes → debounce timer starts (500ms)
* 2. Timer fires → gather prefix/suffix, call D-Bus Complete asynchronously
* 2. Timer fires → gather prefix/suffix, call 9P Complete asynchronously
* 3. Result arrives → store suggestion lines, emit inlineNotesChanged
* 4. paintInlineNote renders first line; GhostOverlay renders the rest
* 5. Tab → accept (insert text), any other input → dismiss
@ -38,7 +37,7 @@ class OllieGhostProvider : public KTextEditor::InlineNoteProvider
Q_OBJECT
public:
explicit OllieGhostProvider(KTextEditor::View *view, QDBusInterface *iface, class OllieKatePlugin *plugin, QObject *parent = nullptr);
explicit OllieGhostProvider(KTextEditor::View *view, class OllieKatePlugin *plugin, QObject *parent = nullptr);
~OllieGhostProvider() override;
// InlineNoteProvider interface
@ -90,7 +89,6 @@ private:
QString gatherSuffix() const;
KTextEditor::View *m_view;
QDBusInterface *m_iface;
OllieKatePlugin *m_plugin;
QTimer *m_debounce;
GhostOverlay *m_overlay = nullptr;
@ -106,7 +104,7 @@ private:
int m_historyIndex = -1; // current index into m_history
bool m_enabled = true; // ghost completions active
bool m_requesting = false; // true while D-Bus call is in flight
bool m_requesting = false; // true while 9P call is in flight
};
/**

View File

@ -476,7 +476,7 @@ OllieKateView::~OllieKateView()
delete m_toolView;
}
// --- D-Bus signal handlers ---
// --- Signal handlers ---
void OllieKateView::onChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text)
{
@ -529,7 +529,7 @@ void OllieKateView::onSessionKilled(const QString &session_id)
m_chatView->clear();
m_sessionLabel->setText("No session");
m_cwdLabel->setText("");
// Defer session switch to let Qt finish processing the D-Bus signal.
// Defer session switch to let Qt finish processing the signal.
// Calling render() synchronously here can crash because the QTextDocument
// is still tearing down resources from the clear() above.
QTimer::singleShot(0, this, &OllieKateView::refreshSessions);
@ -992,8 +992,8 @@ void OllieKateView::setupGhostProvider()
m_ghost = nullptr;
}
// Ghost provider disabled until 9P completion is implemented
// TODO: create ghost provider with 9P-based completion
// Create ghost provider with 9P-based completion
m_ghost = new OllieGhostProvider(view, m_plugin, this);
// Install event filter on the view widget for Tab interception
if (QWidget *proxy = view->focusProxy()) {

View File

@ -1,85 +0,0 @@
#!/bin/sh
# End-to-end test for ollie-kde Phase 2
# Run this while the daemon is running: make run
set -e
SERVICE="org.ollie.SessionManager"
PATH_OBJ="/org/ollie/SessionManager"
IFACE="org.ollie.SessionManager"
call() {
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$PATH_OBJ "$IFACE.$1" "${@:2}"
}
echo "=== ListBackends ==="
call ListBackends
echo ""
echo "=== ListAgents ==="
call ListAgents
echo ""
echo "=== CreateSession ==="
RESULT=$(dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$PATH_OBJ $IFACE.CreateSession \
string:"$HOME" string:"" string:"" string:"default" string:"" string:"")
echo "$RESULT"
SESSION_ID=$(echo "$RESULT" | grep 'string "' | head -1 | sed 's/.*string "//;s/"//')
echo "Session ID: $SESSION_ID"
if [ -z "$SESSION_ID" ]; then
echo "FAIL: no session created"
exit 1
fi
echo ""
echo "=== GetState ==="
call GetState string:"$SESSION_ID"
echo ""
echo "=== GetConfig ==="
call GetConfig string:"$SESSION_ID"
echo ""
echo "=== Submit prompt ==="
call Submit string:"$SESSION_ID" string:"Say hello in exactly 5 words."
echo ""
echo "=== Waiting 5s for response... ==="
sleep 5
echo ""
echo "=== GetState (should be idle) ==="
call GetState string:"$SESSION_ID"
echo ""
echo "=== GetChat (offset 0) ==="
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$PATH_OBJ $IFACE.GetChat \
string:"$SESSION_ID" int64:0
echo ""
echo "=== GetUsage ==="
call GetUsage string:"$SESSION_ID"
echo ""
echo "=== GetCost ==="
call GetCost string:"$SESSION_ID"
echo ""
echo "=== GetContext (first 200 chars) ==="
CONTEXT=$(dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$PATH_OBJ $IFACE.GetContext string:"$SESSION_ID" 2>&1)
echo "$CONTEXT" | head -5
echo ""
echo "=== KillSession ==="
call KillSession string:"$SESSION_ID"
echo ""
echo "=== ListSessions (should be empty) ==="
call ListSessions
echo ""
echo "=== DONE ==="

View File

@ -1,56 +0,0 @@
#!/bin/sh
# Test peer-to-peer agent communication
# Run while the daemon is running: make run
set -e
SERVICE="org.ollie.SessionManager"
OBJ="/org/ollie/SessionManager"
IFACE="org.ollie.SessionManager"
echo "=== Create session A ==="
A=$(dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.CreateSession string:"$HOME" string:"" string:"" string:"default" string:"" string:"" | grep 'string "' | head -1 | sed 's/.*string "//;s/"//')
echo "A: $A"
echo "=== Create session B ==="
B=$(dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.CreateSession string:"$HOME" string:"" string:"" string:"default" string:"" string:"" | grep 'string "' | head -1 | sed 's/.*string "//;s/"//')
echo "B: $B"
echo ""
echo "=== PeerAdd: A registers B ==="
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.PeerAdd string:"$A" string:"$B"
echo ""
echo "=== PeerList ==="
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.PeerList string:"$A"
echo ""
echo "=== PeerSubmit: A sends to B ==="
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.PeerSubmit string:"$A" string:"$B" \
string:"Reply with exactly: PEER_OK"
echo ""
echo "=== Waiting 8s for B to respond... ==="
sleep 8
echo ""
echo "=== B's chat ==="
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.GetChat string:"$B" int64:0
echo ""
echo "=== PeerRemove ==="
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.PeerRemove string:"$A" string:"$B"
echo ""
echo "=== Cleanup ==="
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.KillSession string:"$A" > /dev/null
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
$OBJ $IFACE.KillSession string:"$B" > /dev/null
echo "=== DONE ==="