feat(gui): handle empty sessions in ollie-gui

- getAgents(): filter out 'new' (agent creation file), return empty list
- setActiveSessionId(): skip agent init when no agents exist
- submit/interrupt/kill/streams: guard against empty m_agentId
- prompt input: disable when no agent, show 'No agent' placeholder
This commit is contained in:
Levi Neely 2026-08-01 19:51:07 +02:00
parent c4d6f2a1b9
commit 6fcef9f60c
2 changed files with 47 additions and 37 deletions

View File

@ -193,11 +193,13 @@ ApplicationWindow {
} }
Component.onCompleted: { Component.onCompleted: {
if (ollie.activeAgentId) {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
if (log) { if (log) {
chatModel.appendData(log) chatModel.appendData(log)
chatModel.trimFront(20) chatModel.trimFront(20)
} }
}
scrollTimer.start() scrollTimer.start()
buildSessionTree() buildSessionTree()
} }
@ -206,11 +208,13 @@ ApplicationWindow {
target: ollie target: ollie
function onActiveSessionIdChanged() { function onActiveSessionIdChanged() {
chatModel.reset() chatModel.reset()
if (ollie.activeAgentId) {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
if (log) { if (log) {
chatModel.appendData(log) chatModel.appendData(log)
chatModel.trimFront(20) chatModel.trimFront(20)
} }
}
scrollTimer.start() scrollTimer.start()
// Expand the active session in the tree // Expand the active session in the tree
buildSessionTree() buildSessionTree()
@ -886,8 +890,8 @@ property bool blockExpanded: {
TextArea { TextArea {
id: promptInput id: promptInput
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: ollie.activeSessionId ? "Message..." : "No session" placeholderText: !ollie.activeSessionId ? "No session" : !ollie.activeAgentId ? "No agent" : "Message..."
enabled: ollie.activeSessionId !== "" enabled: ollie.activeSessionId !== "" && ollie.activeAgentId !== ""
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
property string _lastText: "" property string _lastText: ""

View File

@ -62,13 +62,9 @@ void Ollie9pClient::setActiveSessionId(const QString &id)
} }
// Discover agent ID BEFORE emitting signals // Discover agent ID BEFORE emitting signals
QByteArray out = run9p({"ls", "session/" + id + "/agent"}); QStringList agents = getAgents(id);
QString agents = QString::fromUtf8(out).trimmed();
if (!agents.isEmpty()) { if (!agents.isEmpty()) {
m_agentId = agents.split('\n').first().trimmed(); m_agentId = agents.first();
} else {
m_agentId = "0";
}
// Read initial state immediately (state, not statewait) // Read initial state immediately (state, not statewait)
QByteArray stateOut = run9p({"read", agentPath() + "/state"}); QByteArray stateOut = run9p({"read", agentPath() + "/state"});
@ -81,6 +77,11 @@ void Ollie9pClient::setActiveSessionId(const QString &id)
startChatStream(); startChatStream();
startStateStream(); startStateStream();
} else {
// Empty session — no agent yet
emit activeSessionIdChanged();
emit activeStateChanged();
}
} }
void Ollie9pClient::refreshSessions() void Ollie9pClient::refreshSessions()
@ -126,7 +127,7 @@ QString Ollie9pClient::readLogForSession(const QString &sessionId, const QString
bool Ollie9pClient::submit(const QString &prompt) bool Ollie9pClient::submit(const QString &prompt)
{ {
if (m_activeSessionId.isEmpty() || prompt.trimmed().isEmpty()) return false; if (m_activeSessionId.isEmpty() || m_agentId.isEmpty() || prompt.trimmed().isEmpty()) return false;
QProcess proc; QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/prompt"}); proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/prompt"});
proc.waitForStarted(3000); proc.waitForStarted(3000);
@ -138,7 +139,7 @@ bool Ollie9pClient::submit(const QString &prompt)
bool Ollie9pClient::interrupt() bool Ollie9pClient::interrupt()
{ {
if (m_activeSessionId.isEmpty()) return false; if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
QProcess proc; QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"}); proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"});
proc.waitForStarted(3000); proc.waitForStarted(3000);
@ -150,7 +151,7 @@ bool Ollie9pClient::interrupt()
bool Ollie9pClient::kill() bool Ollie9pClient::kill()
{ {
if (m_activeSessionId.isEmpty()) return false; if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
QProcess proc; QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"}); proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"});
proc.waitForStarted(3000); proc.waitForStarted(3000);
@ -191,10 +192,15 @@ QStringList Ollie9pClient::getAgents(const QString &sessionId)
if (sessionId.isEmpty()) return {}; if (sessionId.isEmpty()) return {};
QByteArray out = run9p({"ls", "session/" + sessionId + "/agent"}); QByteArray out = run9p({"ls", "session/" + sessionId + "/agent"});
QString raw = QString::fromUtf8(out).trimmed(); QString raw = QString::fromUtf8(out).trimmed();
if (raw.isEmpty()) return {"0"}; // default fallback if (raw.isEmpty()) return {};
QStringList agents = raw.split('\n', Qt::SkipEmptyParts); QStringList all = raw.split('\n', Qt::SkipEmptyParts);
for (int i = 0; i < agents.size(); ++i) QStringList agents;
agents[i] = agents[i].trimmed(); for (const QString &a : all) {
QString name = a.trimmed();
// "new" is the agent creation file, not an actual agent
if (name != "new")
agents.append(name);
}
return agents; return agents;
} }
@ -425,6 +431,7 @@ void Ollie9pClient::ensureRootDataLoaded()
void Ollie9pClient::startChatStream() void Ollie9pClient::startChatStream()
{ {
if (m_chatProc) return; if (m_chatProc) return;
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return;
m_chatProc = new QProcess(this); m_chatProc = new QProcess(this);
connect(m_chatProc, &QProcess::readyReadStandardOutput, this, &Ollie9pClient::onChatReadyRead); connect(m_chatProc, &QProcess::readyReadStandardOutput, this, &Ollie9pClient::onChatReadyRead);
connect(m_chatProc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), connect(m_chatProc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
@ -437,16 +444,15 @@ void Ollie9pClient::startStateStream()
if (m_stateProc) return; if (m_stateProc) return;
// Check if session still exists before starting // Check if session still exists before starting
if (m_activeSessionId.isEmpty()) return; if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return;
// Re-discover agent ID — directory may have been renamed // Re-discover agent ID — directory may have been renamed
QByteArray out = run9p({"ls", "session/" + m_activeSessionId + "/agent"}); QStringList agents = getAgents(m_activeSessionId);
QString agents = QString::fromUtf8(out).trimmed();
if (agents.isEmpty()) { if (agents.isEmpty()) {
// Session or agent directory gone — don't start // Session or agent directory gone — don't start
return; return;
} }
QString newId = agents.split('\n').first().trimmed(); QString newId = agents.first();
if (newId != m_agentId) { if (newId != m_agentId) {
m_agentId = newId; m_agentId = newId;
emit activeAgentIdChanged(); emit activeAgentIdChanged();