From 51b2013ac3be1fa5765fd99adcca872b9b19f945 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:20:03 +0200 Subject: [PATCH] =?UTF-8?q?gui:=20fix=20deadlock=20=E2=80=94=20demand-driv?= =?UTF-8?q?en=20chat=20streaming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chat stream (ollie-9p read .../chat) blocks forever waiting for data. Starting it on agent switch meant stopStreams() would block the main thread trying to kill a hung process. Fix: stream chat only while the agent is active. - submit() starts the chat stream (agent is about to become active) - Event handler stops chat on idle transition, starts on non-idle - switchAgent() only starts chat if agent is already non-idle - NativeStreamer::stop() uses SIGTERM+200ms then SIGKILL+100ms instead of immediate SIGKILL+1000ms --- gui/nativestreamer.cpp | 7 +++++-- gui/ollie9pclient.cpp | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/gui/nativestreamer.cpp b/gui/nativestreamer.cpp index 00ba7ee..469802f 100644 --- a/gui/nativestreamer.cpp +++ b/gui/nativestreamer.cpp @@ -63,8 +63,11 @@ void NativeStreamer::stop() if (m_process) { disconnect(m_process, nullptr, this, nullptr); if (m_process->state() != QProcess::NotRunning) { - m_process->kill(); - m_process->waitForFinished(1000); + m_process->terminate(); // SIGTERM first + if (!m_process->waitForFinished(200)) { + m_process->kill(); // SIGKILL if still alive + m_process->waitForFinished(100); + } } m_process->deleteLater(); m_process = nullptr; diff --git a/gui/ollie9pclient.cpp b/gui/ollie9pclient.cpp index b864094..0dd6075 100644 --- a/gui/ollie9pclient.cpp +++ b/gui/ollie9pclient.cpp @@ -223,6 +223,13 @@ void Ollie9pClient::handleEvent(const QString &eventLine) m_agentStateValues[key] = payloadStr; emit agentStateChanged(sidStr, aidStr, payloadStr); emit activeStateChanged(); + + // Demand-driven chat: stop on idle, start on activity. + if (payloadStr == QLatin1String("idle")) { + m_chat->stop(); + } else if (!m_chat->isRunning()) { + m_chat->start(agentPath() + "/chat"); + } } else { // Non-active agent: need to check cache const QString sidStr = sessionId.toString(); @@ -378,6 +385,12 @@ bool Ollie9pClient::submit(const QString &prompt) { if (m_activeSessionId.isEmpty() || m_agentId.isEmpty() || prompt.trimmed().isEmpty()) return false; if (!m_9p || !m_9p->isConnected()) return false; + + // Start chat stream before submitting — agent will become active. + if (!m_chat->isRunning()) { + m_chat->start(agentPath() + "/chat"); + } + return m_9p->write(agentPath() + "/prompt", prompt.toUtf8()); } @@ -673,7 +686,11 @@ void Ollie9pClient::startActiveAgentStreams() if (!m_daemonConnected) return; if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return; - m_chat->start(agentPath() + "/chat"); + // Chat stream is demand-driven: started on submit(), stopped on idle. + // If the agent is already active (e.g. switching to a busy agent), start chat now. + if (m_activeState != "idle") { + m_chat->start(agentPath() + "/chat"); + } m_state->start(agentPath() + "/statewait"); }