From 50ebcedb8a29d0667e428f569457ffe31b836359 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 9 Oct 2026 10:21:09 +0200 Subject: [PATCH] gui: support multiple concurrent bypass requests per agent Frontend changes to match the backend's multi-request support: - bypassResolved signal now includes the request ID - ChatPane tracks pendingBypasses as a map (id -> request) - onBypassRequested adds to the map; onBypassResolved removes by ID - currentBypass shows the first pending request for the active agent - Switching agents updates the banner to show that agent's pending request - Approve/Deny now remove only the specific resolved request This allows each agent to have multiple pending bypass requests without blocking other agents, and the banner correctly shows requests only for the currently active agent. --- kde/gui/ChatPane.qml | 99 ++++++++++++++++++++------------------- kde/gui/ollie9pclient.cpp | 4 +- kde/gui/ollie9pclient.h | 2 +- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/kde/gui/ChatPane.qml b/kde/gui/ChatPane.qml index 87c6332..6f5e413 100644 --- a/kde/gui/ChatPane.qml +++ b/kde/gui/ChatPane.qml @@ -7,12 +7,27 @@ Item { id: chatPane property bool sidebarVisible: true property int maxVisibleBlocks: 80 - property var pendingBypass: null // {sessionId, id, cmd, cwd} - property bool hasPendingBypass: false + // Map of pending bypass requests: id -> {sessionId, agentId, id, cmd, cwd} + property var pendingBypasses: ({}) + property var currentBypass: null // The one currently shown in the banner signal toggleSidebar() // Do not opt the chat pane into focus navigation. activeFocusOnTab: false + // Helper to get the first pending bypass for the active agent + function updateCurrentBypass() { + var activeKey = ollie.activeSessionId + ":" + ollie.activeAgentId + for (var id in pendingBypasses) { + var req = pendingBypasses[id] + var key = req.sessionId + ":" + req.agentId + if (key === activeKey) { + currentBypass = req + return + } + } + currentBypass = null + } + // Expose method to set prompt text (for Edit & Resend) function setPromptText(text) { promptInput.text = text @@ -175,7 +190,7 @@ Item { color: "#fff3e0" // amber light border.color: "#ff9800" border.width: 1 - visible: chatPane.hasPendingBypass + visible: chatPane.currentBypass !== null z: 100 ColumnLayout { @@ -192,7 +207,7 @@ Item { } Label { - text: chatPane.pendingBypass ? chatPane.pendingBypass.cmd : "" + text: chatPane.currentBypass ? chatPane.currentBypass.cmd : "" font.family: "monospace" font.pixelSize: theme.fontSize > 0 ? theme.fontSize : 12 wrapMode: Text.WrapAnywhere @@ -201,10 +216,10 @@ Item { } Label { - text: chatPane.pendingBypass ? "cwd: " + chatPane.pendingBypass.cwd : "" + text: chatPane.currentBypass ? "cwd: " + chatPane.currentBypass.cwd : "" font.pixelSize: theme.fontSize > 0 ? theme.fontSize - 1 : 10 color: "#666" - visible: chatPane.pendingBypass && chatPane.pendingBypass.cwd !== "" + visible: chatPane.currentBypass && chatPane.currentBypass.cwd !== "" } RowLayout { @@ -215,10 +230,8 @@ Item { text: "Approve" highlighted: true onClicked: { - if (chatPane.pendingBypass) { - ollie.resolveBypass(chatPane.pendingBypass.sessionId, chatPane.pendingBypass.agentId, chatPane.pendingBypass.id, true) - chatPane.pendingBypass = null - chatPane.hasPendingBypass = false + if (chatPane.currentBypass) { + ollie.resolveBypass(chatPane.currentBypass.sessionId, chatPane.currentBypass.agentId, chatPane.currentBypass.id, true) } } } @@ -226,10 +239,8 @@ Item { Button { text: "Deny" onClicked: { - if (chatPane.pendingBypass) { - ollie.resolveBypass(chatPane.pendingBypass.sessionId, chatPane.pendingBypass.agentId, chatPane.pendingBypass.id, false) - chatPane.pendingBypass = null - chatPane.hasPendingBypass = false + if (chatPane.currentBypass) { + ollie.resolveBypass(chatPane.currentBypass.sessionId, chatPane.currentBypass.agentId, chatPane.currentBypass.id, false) } } } @@ -580,56 +591,46 @@ Connections { target: ollie function onActiveSessionIdChanged() { if (!ollie.activeAgentId) chatPane.loadChat() - // Hide bypass banner if it's not for the now-active session - if (chatPane.pendingBypass && chatPane.pendingBypass.sessionId !== ollie.activeSessionId) { - chatPane.hasPendingBypass = false - } + // Update current bypass for the new active agent + chatPane.updateCurrentBypass() } function onActiveAgentIdChanged() { if (ollie.activeAgentId) chatPane.loadChat() - // Hide bypass banner if it's not for the now-active agent - if (chatPane.pendingBypass && - (chatPane.pendingBypass.sessionId !== ollie.activeSessionId || - chatPane.pendingBypass.agentId !== ollie.activeAgentId)) { - chatPane.hasPendingBypass = false - } + // Update current bypass for the new active agent + chatPane.updateCurrentBypass() } function onBypassRequested(sessionId, agentId, id, cmd, cwd) { - // Show bypass request only for the currently active agent - if (sessionId === ollie.activeSessionId && agentId === ollie.activeAgentId) { - chatPane.pendingBypass = { - sessionId: sessionId, - agentId: agentId, - id: id, - cmd: cmd, - cwd: cwd - } - chatPane.hasPendingBypass = true + // Add to pending bypasses map + var newMap = chatPane.pendingBypasses + newMap[id] = { + sessionId: sessionId, + agentId: agentId, + id: id, + cmd: cmd, + cwd: cwd } + chatPane.pendingBypasses = newMap + chatPane.updateCurrentBypass() } - function onBypassResolved(sessionId, agentId) { - // Clear bypass banner if it was for this agent (resolved by CLI or another client) - if (chatPane.pendingBypass && - chatPane.pendingBypass.sessionId === sessionId && - chatPane.pendingBypass.agentId === agentId) { - chatPane.pendingBypass = null - chatPane.hasPendingBypass = false + function onBypassResolved(sessionId, agentId, requestId) { + // Remove the specific resolved request from the map + if (chatPane.pendingBypasses[requestId]) { + var newMap = chatPane.pendingBypasses + delete newMap[requestId] + chatPane.pendingBypasses = newMap + chatPane.updateCurrentBypass() } } // Public functions for keyboard shortcuts function approveBypass() { - if (chatPane.pendingBypass) { - ollie.resolveBypass(chatPane.pendingBypass.sessionId, chatPane.pendingBypass.agentId, chatPane.pendingBypass.id, true) - chatPane.pendingBypass = null - chatPane.hasPendingBypass = false + if (chatPane.currentBypass) { + ollie.resolveBypass(chatPane.currentBypass.sessionId, chatPane.currentBypass.agentId, chatPane.currentBypass.id, true) } } function denyBypass() { - if (chatPane.pendingBypass) { - ollie.resolveBypass(chatPane.pendingBypass.sessionId, chatPane.pendingBypass.agentId, chatPane.pendingBypass.id, false) - chatPane.pendingBypass = null - chatPane.hasPendingBypass = false + if (chatPane.currentBypass) { + ollie.resolveBypass(chatPane.currentBypass.sessionId, chatPane.currentBypass.agentId, chatPane.currentBypass.id, false) } } } diff --git a/kde/gui/ollie9pclient.cpp b/kde/gui/ollie9pclient.cpp index ac2e622..6558347 100644 --- a/kde/gui/ollie9pclient.cpp +++ b/kde/gui/ollie9pclient.cpp @@ -280,7 +280,7 @@ void Ollie9pClient::handleEvent(const QString &eventLine) } emit pendingBypassCountChanged(sidStr, aidStr); } - emit bypassResolved(sidStr, aidStr); + emit bypassResolved(sidStr, aidStr, id); } } } else { @@ -895,7 +895,7 @@ bool Ollie9pClient::resolveBypass(const QString &sessionId, const QString &agent m_pendingBypasses.remove(key); } emit pendingBypassCountChanged(sessionId, agentId); - emit bypassResolved(sessionId, agentId); + emit bypassResolved(sessionId, agentId, id); } return true; } diff --git a/kde/gui/ollie9pclient.h b/kde/gui/ollie9pclient.h index 2ad1590..88021cb 100644 --- a/kde/gui/ollie9pclient.h +++ b/kde/gui/ollie9pclient.h @@ -112,7 +112,7 @@ signals: void agentStateChanged(const QString &sessionId, const QString &agentId, const QString &state); void chatReceived(const QString &text); void bypassRequested(const QString &sessionId, const QString &agentId, const QString &id, const QString &cmd, const QString &cwd); - void bypassResolved(const QString &sessionId, const QString &agentId); + void bypassResolved(const QString &sessionId, const QString &agentId, const QString &requestId); void pendingBypassCountChanged(const QString &sessionId, const QString &agentId); void procStarted(const QString &sessionId, const QString &agentId, const QString &payload); void rootBackendsLoadedChanged();