gui: separate session and agent creation, add agent context menu

- '+' button now creates an empty session directly (no dialog)
- Dialog becomes 'New Agent' (session name field removed)
- Session context menu: 'New Agent...' + 'Kill Session'
- Agent context menu: 'Kill' (writes kill to agent ctl)
- New C++ methods: createQuickSession(), createAgent(), killAgent()
This commit is contained in:
Levi Neely 2026-08-03 09:16:00 +02:00
parent 042f287796
commit 84fb53332c
3 changed files with 107 additions and 24 deletions

View File

@ -92,6 +92,11 @@ ApplicationWindow {
// Session ID to kill — set on right-click, consumed by context menu
property string killTargetSessionId: ""
// Target session for "New Agent" dialog
property string newAgentTargetSessionId: ""
// Agent kill target — set on right-click
property string killTargetAgentSessionId: ""
property string killTargetAgentId: ""
// Session tree model — flat list with level info
ListModel {
@ -355,10 +360,10 @@ ApplicationWindow {
}
}
// --- New Session Dialog ---
// --- New Agent Dialog ---
Dialog {
id: newSessionDialog
title: "New Session"
id: newAgentDialog
title: "New Agent"
anchors.centerIn: parent
width: 420
modal: true
@ -368,20 +373,6 @@ ApplicationWindow {
spacing: 16
Layout.margins: 16
// --- Session ---
Label { text: "Session"; font.bold: true; font.pointSize: theme.fontSize > 0 ? theme.fontSize + 1 : Qt.application.font.pointSize + 1 }
// Name
RowLayout {
Layout.fillWidth: true
Label { text: "Name:"; Layout.preferredWidth: 80 }
TextField {
id: nameField
Layout.fillWidth: true
placeholderText: "(auto-generated)"
}
}
// --- Agent ---
Label { text: "Agent"; font.bold: true; font.pointSize: theme.fontSize > 0 ? theme.fontSize + 1 : Qt.application.font.pointSize + 1 }
@ -503,21 +494,20 @@ ApplicationWindow {
onAccepted: {
var cwd = dirField.text.trim()
if (cwd === "") {
console.log("New session: directory is required")
console.log("New agent: directory is required")
return
}
var name = nameField.text.trim()
var backend = backendCombo.currentText.trim()
var model = modelCombo.currentIndex >= 0 ? modelCombo.currentText.trim() : ""
var agent = agentCombo.currentText
var remote = remoteField.text.trim()
var agentAlias = agentAliasField.text.trim()
var ok = ollie.createSession(cwd, name, backend, model, agent, remote, agentAlias)
var ok = ollie.createAgent(root.newAgentTargetSessionId, cwd, backend, model, agent, remote, agentAlias)
if (ok) {
ollie.refreshSessions()
} else {
console.log("New session: creation failed")
console.log("New agent: creation failed")
}
}
}
@ -538,7 +528,10 @@ ApplicationWindow {
Label { text: "Sessions"; Layout.fillWidth: true; font.bold: true; leftPadding: 8 }
ToolButton {
text: "\u2795"
onClicked: newSessionDialog.open()
onClicked: {
if (ollie.createQuickSession())
ollie.refreshSessions()
}
ToolTip.text: "New Session"
ToolTip.visible: hovered
}
@ -599,6 +592,13 @@ ApplicationWindow {
if (item)
root.killTargetSessionId = item.id
sessionCtxMenu.popup()
} else if (type === "agent") {
var agentItem = sessionTreeModel.get(parent.modelIndex)
if (agentItem) {
root.killTargetAgentSessionId = agentItem.parentId
root.killTargetAgentId = agentItem.agentId
}
agentCtxMenu.popup()
}
return
}
@ -619,8 +619,14 @@ ApplicationWindow {
Menu {
id: sessionCtxMenu
MenuItem {
text: "Kill"
icon.color: "#f44336"
text: "New Agent..."
onClicked: {
root.newAgentTargetSessionId = root.killTargetSessionId
newAgentDialog.open()
}
}
MenuItem {
text: "Kill Session"
onClicked: {
if (root.killTargetSessionId)
ollie.killSession(root.killTargetSessionId)
@ -628,6 +634,18 @@ ApplicationWindow {
}
}
}
Menu {
id: agentCtxMenu
MenuItem {
text: "Kill"
onClicked: {
if (root.killTargetAgentSessionId && root.killTargetAgentId)
ollie.killAgent(root.killTargetAgentSessionId, root.killTargetAgentId)
ollie.refreshSessions()
}
}
}
}
contentItem: RowLayout {

View File

@ -593,6 +593,68 @@ bool Ollie9pClient::createSession(const QString &cwd, const QString &name, const
return true;
}
bool Ollie9pClient::createQuickSession()
{
QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "session/new"});
proc.waitForStarted(3000);
proc.write("\n");
proc.closeWriteChannel();
proc.waitForFinished(5000);
return proc.exitCode() == 0;
}
bool Ollie9pClient::createAgent(const QString &sessionId, const QString &cwd, const QString &backend, const QString &model, const QString &agent, const QString &remote, const QString &agentAlias)
{
if (sessionId.isEmpty() || cwd.isEmpty()) return false;
QString sessName = sessionNameForId(sessionId);
if (sessName.isEmpty()) sessName = sessionId;
QStringList agentArgs;
agentArgs << "cwd=" + cwd;
if (!backend.isEmpty()) agentArgs << "backend=" + backend;
if (!model.isEmpty()) agentArgs << "model=" + model;
if (!agent.isEmpty()) agentArgs << "agent=" + agent;
if (!remote.isEmpty()) agentArgs << "remote=" + remote;
if (!agentAlias.isEmpty()) agentArgs << "name=" + agentAlias;
QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "session/" + sessName + "/agent/new"});
proc.waitForStarted(3000);
proc.write((agentArgs.join(" ") + "\n").toUtf8());
proc.closeWriteChannel();
proc.waitForFinished(5000);
if (proc.exitCode() != 0) {
QString err = QString::fromUtf8(proc.readAllStandardError()).trimmed();
if (err.isEmpty())
err = QString::fromUtf8(proc.readAllStandardOutput()).trimmed();
qDebug() << "createAgent failed:" << err;
return false;
}
return true;
}
bool Ollie9pClient::killAgent(const QString &sessionId, const QString &agentId)
{
if (sessionId.isEmpty() || agentId.isEmpty()) return false;
QString sessName = sessionNameForId(sessionId);
if (sessName.isEmpty()) sessName = sessionId;
QString agentName = agentNameForId(sessionId, agentId);
if (agentName.isEmpty()) agentName = agentId;
QString path = "session/" + sessName + "/agent/" + agentName + "/ctl";
QProcess proc;
proc.start(ninepBin(), {"-a", serverAddr(), "write", path});
proc.waitForStarted(3000);
proc.write("kill");
proc.closeWriteChannel();
proc.waitForFinished(3000);
return proc.exitCode() == 0;
}
bool Ollie9pClient::renameSession(const QString &sessionId, const QString &newName)
{
if (sessionId.isEmpty() || newName.isEmpty()) return false;

View File

@ -85,6 +85,9 @@ public:
Q_INVOKABLE void loadRootAgents();
Q_INVOKABLE QStringList getAvailableModels(const QString &backend) const;
Q_INVOKABLE bool createSession(const QString &cwd, const QString &name, const QString &backend, const QString &model, const QString &agent, const QString &remote, const QString &agentAlias);
Q_INVOKABLE bool createQuickSession();
Q_INVOKABLE bool createAgent(const QString &sessionId, const QString &cwd, const QString &backend, const QString &model, const QString &agent, const QString &remote, const QString &agentAlias);
Q_INVOKABLE bool killAgent(const QString &sessionId, const QString &agentId);
Q_INVOKABLE bool renameSession(const QString &sessionId, const QString &newName);
Q_INVOKABLE bool renameAgent(const QString &sessionId, const QString &agentId, const QString &newName);