fix connection dot indicators

This commit is contained in:
Levi Neely 2026-08-02 23:59:28 +02:00
parent 41f95e84c2
commit 4dabab1f71
5 changed files with 53 additions and 15 deletions

View File

@ -228,6 +228,10 @@ ApplicationWindow {
} }
scrollTimer.start() scrollTimer.start()
} }
function onSessionConnected() {
chatModel.appendData("\n[[[info]]]\nsession connected\n[[[end]]]\n")
scrollTimer.start()
}
function onStreamingDone() { function onStreamingDone() {
chatModel.appendData("\n[[[info]]]\nsession ended\n[[[end]]]\n") chatModel.appendData("\n[[[info]]]\nsession ended\n[[[end]]]\n")
} }

View File

@ -19,6 +19,7 @@ void NinePConnection::start(const QString &program, const QStringList &args, int
m_retryMs = retryMs; m_retryMs = retryMs;
m_probe = probe; m_probe = probe;
m_stopRequested = false; m_stopRequested = false;
m_openMarkerSeen = false;
m_retryTimer.stop(); m_retryTimer.stop();
if (!m_process) if (!m_process)
startProcess(); startProcess();
@ -43,6 +44,7 @@ void NinePConnection::startProcess()
if (m_stopRequested || m_process) if (m_stopRequested || m_process)
return; return;
m_stderr.clear(); m_stderr.clear();
m_openMarkerSeen = false;
m_process = new QProcess(this); m_process = new QProcess(this);
connect(m_process, &QProcess::started, this, &NinePConnection::onStarted); connect(m_process, &QProcess::started, this, &NinePConnection::onStarted);
connect(m_process, &QProcess::readyReadStandardOutput, this, &NinePConnection::onStdout); connect(m_process, &QProcess::readyReadStandardOutput, this, &NinePConnection::onStdout);
@ -77,8 +79,10 @@ void NinePConnection::onStderr()
if (end < 0) break; if (end < 0) break;
const QByteArray line = m_stderr.left(end).trimmed(); const QByteArray line = m_stderr.left(end).trimmed();
m_stderr.remove(0, end + 1); m_stderr.remove(0, end + 1);
if (line == "OLLIE_9P_OPEN") if (line == "OLLIE_9P_OPEN") {
m_openMarkerSeen = true;
setState(Connected); setState(Connected);
}
} }
} }
@ -87,7 +91,7 @@ void NinePConnection::onFinished(int exitCode, QProcess::ExitStatus)
if (!m_process) return; if (!m_process) return;
m_process->deleteLater(); m_process->deleteLater();
m_process = nullptr; m_process = nullptr;
if (m_probe && m_state == Connected && exitCode == 0 && !m_stopRequested) { if (m_probe && m_state == Connected && m_openMarkerSeen && exitCode == 0 && !m_stopRequested) {
m_retryTimer.start(m_retryMs); m_retryTimer.start(m_retryMs);
return; return;
} }

View File

@ -46,6 +46,7 @@ private:
bool m_stopRequested = false; bool m_stopRequested = false;
int m_retryMs = 4000; int m_retryMs = 4000;
bool m_probe = false; bool m_probe = false;
bool m_openMarkerSeen = false;
}; };
#endif // NINEPCONNECTION_H #endif // NINEPCONNECTION_H

View File

@ -46,11 +46,18 @@ Ollie9pClient::Ollie9pClient(QObject *parent)
emit chatReceived(QString::fromUtf8(data)); emit chatReceived(QString::fromUtf8(data));
}); });
connect(m_chat, &StreamFsm::died, this, [this]() { connect(m_chat, &StreamFsm::died, this, [this]() {
// The chat stream closes when the selected session ends. Treat that if (!m_activeSessionId.isEmpty()) {
// existing signal as an immediate disconnect for connection tracking; for (const QVariant &value : std::as_const(m_sessions)) {
// the periodic connection probe will confirm or recover it later. const QVariantMap session = value.toMap();
if (!m_activeSessionId.isEmpty() && !m_agentId.isEmpty()) if (session.value("id").toString() != m_activeSessionId) continue;
setAgentConnected(agentKey(m_activeSessionId, m_agentId), false); for (const QVariant &agentValue : session.value("agents").toList()) {
const QString key = agentKey(m_activeSessionId,
agentValue.toMap().value("id").toString());
setAgentConnected(key, false);
}
break;
}
}
emit streamingDone(); emit streamingDone();
}); });
@ -204,22 +211,27 @@ QString Ollie9pClient::sessionConnectionColor(const QString &sessionId) const
void Ollie9pClient::setAgentConnected(const QString &key, bool connected) void Ollie9pClient::setAgentConnected(const QString &key, bool connected)
{ {
if (m_agentConnected.value(key, false) == connected) return; const bool cached = m_agentConnected.value(key, false);
m_agentConnected[key] = connected; m_agentConnected[key] = connected;
bool modelChanged = false;
// Update the in-memory model without starting another round of probes. // Update the in-memory model without starting another round of probes.
for (QVariant &value : m_sessions) { for (QVariant &value : m_sessions) {
QVariantMap session = value.toMap(); QVariantMap session = value.toMap();
QVariantList agents = session.value("agents").toList(); QVariantList agents = session.value("agents").toList();
for (QVariant &agentValue : agents) { for (QVariant &agentValue : agents) {
QVariantMap agent = agentValue.toMap(); QVariantMap agent = agentValue.toMap();
if (agentKey(session["id"].toString(), agent["id"].toString()) == key) if (agentKey(session["id"].toString(), agent["id"].toString()) == key) {
if (agent.value("connected").toBool() != connected)
modelChanged = true;
agent["connected"] = connected; agent["connected"] = connected;
}
agentValue = agent; agentValue = agent;
} }
session["agents"] = agents; session["agents"] = agents;
value = session; value = session;
} }
emit sessionsChanged(); if (cached != connected || modelChanged)
emit sessionsChanged();
} }
void Ollie9pClient::startAgentConnections() void Ollie9pClient::startAgentConnections()
@ -244,7 +256,14 @@ void Ollie9pClient::reconcileAgentConnections()
auto *connection = new NinePConnection(this); auto *connection = new NinePConnection(this);
m_agentConnections.insert(key, connection); m_agentConnections.insert(key, connection);
connect(connection, &NinePConnection::connected, this, [this, key]() { connect(connection, &NinePConnection::connected, this, [this, key]() {
const bool reconnected = m_seenAgentConnections.contains(key);
m_seenAgentConnections.insert(key);
setAgentConnected(key, true); setAgentConnected(key, true);
if (key == agentKey(m_activeSessionId, m_agentId)) {
if (reconnected)
emit sessionConnected();
startActiveAgentStreams();
}
}); });
connect(connection, &NinePConnection::disconnected, this, [this, key]() { connect(connection, &NinePConnection::disconnected, this, [this, key]() {
setAgentConnected(key, false); setAgentConnected(key, false);
@ -457,11 +476,7 @@ void Ollie9pClient::switchAgent(const QString &sessionId, const QString &agentId
emit activeAgentIdChanged(); emit activeAgentIdChanged();
emit activeStateChanged(); emit activeStateChanged();
// switchAgent is the ONLY path that starts per-agent streams. startActiveAgentStreams();
QString srv = serverAddr();
QString bin = ninepBin();
m_chat->start(bin, {"-a", srv, "read", agentPath() + "/chat"});
m_state->start(bin, {"-a", srv, "read", agentPath() + "/statewait"});
} }
void Ollie9pClient::loadRootBackends() void Ollie9pClient::loadRootBackends()
@ -691,6 +706,16 @@ void Ollie9pClient::ensureRootDataLoaded()
// --- Streaming --- // --- Streaming ---
void Ollie9pClient::startActiveAgentStreams()
{
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return;
const QString srv = serverAddr();
const QString bin = ninepBin();
m_chat->start(bin, {"-a", srv, "read", agentPath() + "/chat"});
m_state->start(bin, {"-a", srv, "read", agentPath() + "/statewait"});
}
void Ollie9pClient::stopAgentStreams() void Ollie9pClient::stopAgentStreams()
{ {
if (m_chat) m_chat->stop(); if (m_chat) m_chat->stop();

View File

@ -4,6 +4,7 @@
#include <QObject> #include <QObject>
#include <QVariantList> #include <QVariantList>
#include <QHash> #include <QHash>
#include <QSet>
#if QT_VERSION_MAJOR >= 6 #if QT_VERSION_MAJOR >= 6
#include <QQmlEngine> #include <QQmlEngine>
#endif #endif
@ -93,6 +94,7 @@ signals:
void activeAgentIdChanged(); void activeAgentIdChanged();
void activeStateChanged(); void activeStateChanged();
void chatReceived(const QString &text); void chatReceived(const QString &text);
void sessionConnected();
void streamingDone(); // chat process exited (session killed) void streamingDone(); // chat process exited (session killed)
void rootBackendsLoadedChanged(); void rootBackendsLoadedChanged();
void availableBackendsChanged(); void availableBackendsChanged();
@ -112,6 +114,7 @@ private:
void ensureRootDataLoaded(); void ensureRootDataLoaded();
void setDaemonConnected(bool connected); void setDaemonConnected(bool connected);
void startAgentConnections(); void startAgentConnections();
void startActiveAgentStreams();
void reconcileAgentConnections(); void reconcileAgentConnections();
void stopAgentConnections(); void stopAgentConnections();
@ -131,6 +134,7 @@ private:
bool m_daemonConnected = false; bool m_daemonConnected = false;
QHash<QString, NinePConnection *> m_agentConnections; QHash<QString, NinePConnection *> m_agentConnections;
QHash<QString, bool> m_agentConnected; QHash<QString, bool> m_agentConnected;
QSet<QString> m_seenAgentConnections;
NinePConnection *m_daemon = nullptr; NinePConnection *m_daemon = nullptr;
// Streaming FSMs — each wraps a QProcess lifecycle // Streaming FSMs — each wraps a QProcess lifecycle