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,10 +193,12 @@ ApplicationWindow {
} }
Component.onCompleted: { Component.onCompleted: {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) if (ollie.activeAgentId) {
if (log) { var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
chatModel.appendData(log) if (log) {
chatModel.trimFront(20) chatModel.appendData(log)
chatModel.trimFront(20)
}
} }
scrollTimer.start() scrollTimer.start()
buildSessionTree() buildSessionTree()
@ -206,10 +208,12 @@ ApplicationWindow {
target: ollie target: ollie
function onActiveSessionIdChanged() { function onActiveSessionIdChanged() {
chatModel.reset() chatModel.reset()
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) if (ollie.activeAgentId) {
if (log) { var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
chatModel.appendData(log) if (log) {
chatModel.trimFront(20) chatModel.appendData(log)
chatModel.trimFront(20)
}
} }
scrollTimer.start() scrollTimer.start()
// Expand the active session in the tree // Expand the active session in the tree
@ -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,25 +62,26 @@ 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();
// Read initial state immediately (state, not statewait)
QByteArray stateOut = run9p({"read", agentPath() + "/state"});
QString stateStr = QString::fromUtf8(stateOut).trimmed();
if (!stateStr.isEmpty())
m_activeState = stateStr;
emit activeSessionIdChanged();
emit activeStateChanged();
startChatStream();
startStateStream();
} else { } else {
m_agentId = "0"; // Empty session — no agent yet
emit activeSessionIdChanged();
emit activeStateChanged();
} }
// Read initial state immediately (state, not statewait)
QByteArray stateOut = run9p({"read", agentPath() + "/state"});
QString stateStr = QString::fromUtf8(stateOut).trimmed();
if (!stateStr.isEmpty())
m_activeState = stateStr;
emit activeSessionIdChanged();
emit activeStateChanged();
startChatStream();
startStateStream();
} }
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();