kate: add connection state tracking and auto-recovery

Adds a heartbeat timer (5s interval) that pings the 9P server with a
cheap read. If the server goes down, the status bar turns red showing
'disconnected'. When it comes back, the plugin reconnects and
re-selects the agent for the current project.

Status bar now shows connection state:
- Red '✗ disconnected' — server unreachable
- Gray '○ no agent' — connected but no agent selected
- Green '● kate/ID' — connected with active agent
This commit is contained in:
Levi Neely 2026-08-06 14:09:33 +02:00
parent 0ff1025bb9
commit 2a6441cd53
2 changed files with 50 additions and 2 deletions

View File

@ -60,12 +60,19 @@ OllieKateView::OllieKateView(OllieKatePlugin *plugin, KTextEditor::MainWindow *m
, m_plugin(plugin)
, m_mainWindow(mainWindow)
, m_9p(new Lib9pClient(this))
, m_heartbeatTimer(new QTimer(this))
{
// Connect to 9P server
if (!m_9p->connectDefault()) {
m_connected = m_9p->connectDefault();
if (!m_connected) {
qWarning() << "Ollie: failed to connect to 9P server:" << m_9p->lastError();
}
// Heartbeat timer — detect server death and reconnect
m_heartbeatTimer->setInterval(5000);
connect(m_heartbeatTimer, &QTimer::timeout, this, &OllieKateView::heartbeat);
m_heartbeatTimer->start();
// Status bar indicator
if (auto *win = qobject_cast<QMainWindow *>(m_mainWindow->window())) {
m_statusLabel = new QLabel();
@ -120,6 +127,7 @@ OllieKateView::OllieKateView(OllieKatePlugin *plugin, KTextEditor::MainWindow *m
OllieKateView::~OllieKateView()
{
m_heartbeatTimer->stop();
delete m_ghost;
if (m_statusLabel) {
delete m_statusLabel;
@ -229,10 +237,46 @@ QString OllieKateView::agentPath() const
void OllieKateView::updateStatusLabel()
{
if (!m_statusLabel) return;
if (m_agentId.isEmpty()) {
if (!m_connected) {
m_statusLabel->setText(QStringLiteral("✗ ollie: disconnected"));
m_statusLabel->setStyleSheet(QStringLiteral("color: red; padding: 0px 4px;"));
} else if (m_agentId.isEmpty()) {
m_statusLabel->setText(QStringLiteral("○ ollie: no agent"));
m_statusLabel->setStyleSheet(QStringLiteral("color: gray; padding: 0px 4px;"));
} else {
m_statusLabel->setText(QStringLiteral("● ollie: kate/") + m_agentId);
m_statusLabel->setStyleSheet(QStringLiteral("color: green; padding: 0px 4px;"));
}
}
// --- Connection Recovery ---
void OllieKateView::heartbeat()
{
// Try a cheap read to verify daemon is alive
if (!m_9p->isConnected()) {
if (!m_9p->connectDefault()) {
if (m_connected) {
m_connected = false;
updateStatusLabel();
}
return;
}
}
QByteArray result = m_9p->read(QStringLiteral("help"));
bool alive = !result.isEmpty();
if (alive && !m_connected) {
// Recovered — re-establish state
m_connected = true;
selectAgentForProject();
updateStatusLabel();
} else if (!alive && m_connected) {
// Lost connection
m_connected = false;
m_9p->disconnect();
updateStatusLabel();
}
}

View File

@ -12,6 +12,7 @@
#include <QLabel>
#include <QAction>
#include <QEvent>
#include <QTimer>
#include "lib9pclient.h"
@ -60,6 +61,7 @@ protected:
private slots:
void onProjectChanged();
void heartbeat();
private:
// Actions
@ -89,7 +91,9 @@ private:
QString m_sessionId;
QString m_agentId;
bool m_connected = false;
QTimer *m_heartbeatTimer;
QLabel *m_statusLabel = nullptr;
QPointer<OllieGhostProvider> m_ghost;
bool m_ghostPrefix = false;