session pause/resume UI

- Add Pause/Resume toggle to session context menu
- Gray agent dots when parent session is paused
- Disable input field with 'Session paused' placeholder when paused
- Preserve focus on prompt input during tree rebuilds
This commit is contained in:
Levi Neely 2026-08-03 18:36:18 +02:00
parent 453cef397f
commit eedee30332
3 changed files with 73 additions and 2 deletions

View File

@ -108,6 +108,8 @@ ApplicationWindow {
function buildSessionTree() {
console.log("buildSessionTree: sessions count = " + ollie.sessions.length)
// Preserve focus if prompt input has it
var hadFocus = promptInput.activeFocus
sessionTreeModel.clear()
var sessions = ollie.sessions
for (var i = 0; i < sessions.length; ++i) {
@ -147,6 +149,9 @@ ApplicationWindow {
}
}
console.log(" total tree items: " + sessionTreeModel.count)
// Restore focus if it was on the prompt input
if (hadFocus)
promptInput.forceActiveFocus()
}
function toggleExpand(index) {
@ -702,6 +707,17 @@ ApplicationWindow {
newAgentDialog.open()
}
}
MenuItem {
text: ollie.isSessionPaused(root.killTargetSessionId) ? "Resume" : "Pause"
onClicked: {
if (root.killTargetSessionId) {
if (ollie.isSessionPaused(root.killTargetSessionId))
ollie.resumeSession(root.killTargetSessionId)
else
ollie.pauseSession(root.killTargetSessionId)
}
}
}
MenuItem {
text: "Kill Session"
onClicked: {
@ -805,6 +821,9 @@ ApplicationWindow {
if (cc === "red") return "#f44336"
return "#9e9e9e"
}
// Agent: check if parent session is paused
if (ollie.isSessionPaused(parentId))
return "#9e9e9e" // gray when paused
// Agent: execution state - query live state
var s = ollie.agentState(parentId, agentId) || "idle"
if (s === "idle") return "#4caf50"
@ -1052,8 +1071,13 @@ property bool blockExpanded: {
TextArea {
id: promptInput
Layout.fillWidth: true
placeholderText: !ollie.activeSessionId ? "No session" : !ollie.activeAgentId ? "No agent" : "Message..."
enabled: ollie.activeSessionId !== "" && ollie.activeAgentId !== ""
placeholderText: {
if (!ollie.activeSessionId) return "No session"
if (!ollie.activeAgentId) return "No agent"
if (ollie.isSessionPaused(ollie.activeSessionId)) return "Session paused"
return "Message..."
}
enabled: ollie.activeSessionId !== "" && ollie.activeAgentId !== "" && !ollie.isSessionPaused(ollie.activeSessionId)
wrapMode: TextEdit.Wrap
property string _lastText: ""

View File

@ -538,6 +538,50 @@ bool Ollie9pClient::killSession(const QString &sessionId)
return proc.exitCode() == 0;
}
bool Ollie9pClient::pauseSession(const QString &sessionId)
{
if (sessionId.isEmpty()) return false;
QString path = "session/" + sessionId + "/ctl";
QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "write", path});
proc.waitForStarted(3000);
proc.write("pause");
proc.closeWriteChannel();
proc.waitForFinished(3000);
if (proc.exitCode() == 0) {
refreshSessions();
return true;
}
return false;
}
bool Ollie9pClient::resumeSession(const QString &sessionId)
{
if (sessionId.isEmpty()) return false;
QString path = "session/" + sessionId + "/ctl";
QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "write", path});
proc.waitForStarted(3000);
proc.write("resume");
proc.closeWriteChannel();
proc.waitForFinished(3000);
if (proc.exitCode() == 0) {
refreshSessions();
return true;
}
return false;
}
bool Ollie9pClient::isSessionPaused(const QString &sessionId) const
{
for (const QVariant &v : m_sessions) {
const QVariantMap s = v.toMap();
if (s.value("id").toString() == sessionId)
return s.value("paused").toBool();
}
return false;
}
QString Ollie9pClient::getConfig()
{
if (m_activeSessionId.isEmpty()) return {};

View File

@ -67,6 +67,9 @@ public:
Q_INVOKABLE bool interrupt();
Q_INVOKABLE bool kill();
Q_INVOKABLE bool killSession(const QString &sessionId);
Q_INVOKABLE bool pauseSession(const QString &sessionId);
Q_INVOKABLE bool resumeSession(const QString &sessionId);
Q_INVOKABLE bool isSessionPaused(const QString &sessionId) const;
Q_INVOKABLE QString getConfig();
Q_INVOKABLE QStringList getAgents(const QString &sessionId);