gui: use native 9P client instead of subprocess calls
Convert most operations from spawning plan9port 9p subprocess to using the persistent Lib9pClient connection. This eliminates process spawn overhead for common operations like submit, interrupt, kill, pause, resume, rename, and create. Streaming operations (chat, statewait) still use subprocess because they need continuous tailing which the native lib doesn't support. Reduces code by ~70 lines and removes synchronous QProcess waits.
This commit is contained in:
parent
073cc2158b
commit
350d74866a
|
|
@ -391,57 +391,48 @@ void Ollie9pClient::refreshSessions()
|
|||
QString Ollie9pClient::readLog()
|
||||
{
|
||||
if (m_activeSessionId.isEmpty()) return {};
|
||||
QByteArray out = run9p({"read", agentPath() + "/log"});
|
||||
return QString::fromUtf8(out);
|
||||
if (m_9p && m_9p->isConnected()) {
|
||||
return QString::fromUtf8(m_9p->read(agentPath() + "/log"));
|
||||
}
|
||||
return QString::fromUtf8(run9p({"read", agentPath() + "/log"}));
|
||||
}
|
||||
|
||||
QString Ollie9pClient::readLogForSession(const QString &sessionId, const QString &agentId)
|
||||
{
|
||||
if (sessionId.isEmpty() || agentId.isEmpty()) return {};
|
||||
// Use immutable IDs — the 9P namespace resolves them via aliases.
|
||||
QByteArray out = run9p({"read", "session/" + sessionId + "/agent/" + agentId + "/log"});
|
||||
return QString::fromUtf8(out);
|
||||
QString path = "session/" + sessionId + "/agent/" + agentId + "/log";
|
||||
if (m_9p && m_9p->isConnected()) {
|
||||
return QString::fromUtf8(m_9p->read(path));
|
||||
}
|
||||
return QString::fromUtf8(run9p({"read", path}));
|
||||
}
|
||||
|
||||
bool Ollie9pClient::submit(const QString &prompt)
|
||||
{
|
||||
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty() || prompt.trimmed().isEmpty()) return false;
|
||||
QProcess proc;
|
||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/prompt"});
|
||||
proc.waitForStarted(3000);
|
||||
proc.write(prompt.toUtf8());
|
||||
proc.closeWriteChannel();
|
||||
proc.waitForFinished(5000);
|
||||
return proc.exitCode() == 0;
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
return m_9p->write(agentPath() + "/prompt", prompt.toUtf8());
|
||||
}
|
||||
|
||||
bool Ollie9pClient::interrupt()
|
||||
{
|
||||
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
|
||||
QProcess proc;
|
||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"});
|
||||
proc.waitForStarted(3000);
|
||||
proc.write("stop");
|
||||
proc.closeWriteChannel();
|
||||
proc.waitForFinished(3000);
|
||||
return proc.exitCode() == 0;
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
return m_9p->write(agentPath() + "/ctl", "stop");
|
||||
}
|
||||
|
||||
bool Ollie9pClient::kill()
|
||||
{
|
||||
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
|
||||
QProcess proc;
|
||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"});
|
||||
proc.waitForStarted(3000);
|
||||
proc.write("kill");
|
||||
proc.closeWriteChannel();
|
||||
proc.waitForFinished(3000);
|
||||
return proc.exitCode() == 0;
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
return m_9p->write(agentPath() + "/ctl", "kill");
|
||||
}
|
||||
|
||||
bool Ollie9pClient::killSession(const QString &sessionId)
|
||||
{
|
||||
if (sessionId.isEmpty()) return false;
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
|
||||
// If killing the active session, shut down its streams and deselect.
|
||||
if (sessionId == m_activeSessionId) {
|
||||
|
|
@ -454,26 +445,16 @@ bool Ollie9pClient::killSession(const QString &sessionId)
|
|||
|
||||
// Use immutable ID — the 9P namespace resolves it via alias.
|
||||
QString path = "session/" + sessionId + "/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;
|
||||
return m_9p->write(path, "kill");
|
||||
}
|
||||
|
||||
bool Ollie9pClient::pauseSession(const QString &sessionId)
|
||||
{
|
||||
if (sessionId.isEmpty()) return false;
|
||||
if (!m_9p || !m_9p->isConnected()) 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) {
|
||||
if (m_9p->write(path, "pause")) {
|
||||
refreshSessions();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -483,14 +464,10 @@ bool Ollie9pClient::pauseSession(const QString &sessionId)
|
|||
bool Ollie9pClient::resumeSession(const QString &sessionId)
|
||||
{
|
||||
if (sessionId.isEmpty()) return false;
|
||||
if (!m_9p || !m_9p->isConnected()) 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) {
|
||||
if (m_9p->write(path, "resume")) {
|
||||
refreshSessions();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -510,18 +487,25 @@ bool Ollie9pClient::isSessionPaused(const QString &sessionId) const
|
|||
QString Ollie9pClient::getConfig()
|
||||
{
|
||||
if (m_activeSessionId.isEmpty()) return {};
|
||||
QByteArray out = run9p({"read", agentPath() + "/cfg"});
|
||||
return QString::fromUtf8(out);
|
||||
if (m_9p && m_9p->isConnected()) {
|
||||
return QString::fromUtf8(m_9p->read(agentPath() + "/cfg"));
|
||||
}
|
||||
return QString::fromUtf8(run9p({"read", agentPath() + "/cfg"}));
|
||||
}
|
||||
|
||||
QStringList Ollie9pClient::getAgents(const QString &sessionId)
|
||||
{
|
||||
if (sessionId.isEmpty()) return {};
|
||||
// Use immutable ID — the 9P namespace resolves it via alias.
|
||||
QByteArray out = run9p({"ls", "session/" + sessionId + "/agent"});
|
||||
QString raw = QString::fromUtf8(out).trimmed();
|
||||
if (raw.isEmpty()) return {};
|
||||
QStringList all = raw.split('\n', Qt::SkipEmptyParts);
|
||||
QString path = "session/" + sessionId + "/agent";
|
||||
QStringList all;
|
||||
if (m_9p && m_9p->isConnected()) {
|
||||
all = m_9p->ls(path);
|
||||
} else {
|
||||
QString raw = QString::fromUtf8(run9p({"ls", path})).trimmed();
|
||||
if (!raw.isEmpty())
|
||||
all = raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
QStringList agents;
|
||||
for (const QString &a : all) {
|
||||
QString name = a.trimmed();
|
||||
|
|
@ -611,30 +595,23 @@ QStringList Ollie9pClient::getAvailableModels(const QString &backend) const
|
|||
|
||||
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;
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
QByteArray result = m_9p->rdwr(QStringLiteral("session/new"), "\n");
|
||||
return !result.isEmpty() || m_9p->lastError().isEmpty();
|
||||
}
|
||||
|
||||
QString Ollie9pClient::createSession(const QString &name, const QString &remote)
|
||||
{
|
||||
if (!m_9p || !m_9p->isConnected()) return QStringLiteral("not connected");
|
||||
|
||||
QStringList args;
|
||||
if (!name.isEmpty()) args << "name=" + name;
|
||||
if (!remote.isEmpty()) args << "remote=" + remote;
|
||||
|
||||
QProcess proc;
|
||||
proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "session/new"});
|
||||
proc.waitForStarted(3000);
|
||||
proc.write((args.join(" ") + "\n").toUtf8());
|
||||
proc.closeWriteChannel();
|
||||
proc.waitForFinished(5000);
|
||||
if (proc.exitCode() != 0) {
|
||||
QString err = QString::fromUtf8(proc.readAllStandardError()).trimmed();
|
||||
return err.isEmpty() ? QStringLiteral("failed to create session") : err;
|
||||
QByteArray result = m_9p->rdwr(QStringLiteral("session/new"), (args.join(" ") + "\n").toUtf8());
|
||||
QString err = m_9p->lastError();
|
||||
if (!err.isEmpty()) {
|
||||
return err;
|
||||
}
|
||||
return QString(); // success
|
||||
}
|
||||
|
|
@ -642,6 +619,7 @@ QString Ollie9pClient::createSession(const QString &name, const QString &remote)
|
|||
QString 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 QStringLiteral("session ID and directory are required");
|
||||
if (!m_9p || !m_9p->isConnected()) return QStringLiteral("not connected");
|
||||
|
||||
QStringList agentArgs;
|
||||
agentArgs << "cwd=" + cwd;
|
||||
|
|
@ -651,20 +629,11 @@ QString Ollie9pClient::createAgent(const QString &sessionId, const QString &cwd,
|
|||
if (!remote.isEmpty()) agentArgs << "remote=" + remote;
|
||||
if (!agentAlias.isEmpty()) agentArgs << "name=" + agentAlias;
|
||||
|
||||
QProcess proc;
|
||||
// Use immutable ID — the 9P namespace resolves it via alias.
|
||||
proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "session/" + sessionId + "/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();
|
||||
if (err.isEmpty())
|
||||
err = QStringLiteral("agent creation failed");
|
||||
QString path = "session/" + sessionId + "/agent/new";
|
||||
QByteArray result = m_9p->rdwr(path, (agentArgs.join(" ") + "\n").toUtf8());
|
||||
QString err = m_9p->lastError();
|
||||
if (!err.isEmpty()) {
|
||||
qDebug() << "createAgent failed:" << err;
|
||||
return err;
|
||||
}
|
||||
|
|
@ -674,88 +643,48 @@ QString Ollie9pClient::createAgent(const QString &sessionId, const QString &cwd,
|
|||
bool Ollie9pClient::killAgent(const QString &sessionId, const QString &agentId)
|
||||
{
|
||||
if (sessionId.isEmpty() || agentId.isEmpty()) return false;
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
|
||||
// Use immutable IDs — the 9P namespace resolves them via aliases.
|
||||
QString path = "session/" + sessionId + "/agent/" + agentId + "/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;
|
||||
return m_9p->write(path, "kill");
|
||||
}
|
||||
|
||||
bool Ollie9pClient::renameSession(const QString &sessionId, const QString &newName)
|
||||
{
|
||||
if (sessionId.isEmpty() || newName.isEmpty()) return false;
|
||||
|
||||
QString bin = ollie9pBin();
|
||||
if (bin.isEmpty()) {
|
||||
qDebug() << "renameSession: ollie-9p not found, cannot rename";
|
||||
return false;
|
||||
}
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
|
||||
// Session names are mutable display names. Writing the name file triggers
|
||||
// the rename. Use the immutable ID to address the session.
|
||||
QProcess proc;
|
||||
proc.setProgram(bin);
|
||||
proc.setArguments({"-a", serverAddr(), "write", "session/" + sessionId + "/name"});
|
||||
proc.start();
|
||||
if (!proc.waitForStarted(3000)) {
|
||||
qDebug() << "renameSession failed to start:" << proc.errorString();
|
||||
return false;
|
||||
}
|
||||
proc.write((newName + "\n").toUtf8());
|
||||
proc.closeWriteChannel();
|
||||
proc.waitForFinished(5000);
|
||||
|
||||
if (proc.exitCode() != 0) {
|
||||
qDebug() << "renameSession failed:" << QString::fromUtf8(proc.readAllStandardError()).trimmed();
|
||||
return false;
|
||||
}
|
||||
|
||||
QString path = "session/" + sessionId + "/name";
|
||||
if (m_9p->write(path, (newName + "\n").toUtf8())) {
|
||||
// With immutable-ID paths, renaming no longer invalidates streams or
|
||||
// the active selection. Just refresh the session list to pick up the
|
||||
// new display name.
|
||||
refreshSessions();
|
||||
return true;
|
||||
}
|
||||
qDebug() << "renameSession failed:" << m_9p->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Ollie9pClient::renameAgent(const QString &sessionId, const QString &agentId, const QString &newName)
|
||||
{
|
||||
if (sessionId.isEmpty() || agentId.isEmpty() || newName.isEmpty()) return false;
|
||||
|
||||
QString bin = ollie9pBin();
|
||||
if (bin.isEmpty()) {
|
||||
qDebug() << "renameAgent: ollie-9p not found, cannot rename";
|
||||
return false;
|
||||
}
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
|
||||
// Agent names are mutable display names. Write the name file directly
|
||||
// using immutable IDs to address the path.
|
||||
QProcess proc;
|
||||
proc.setProgram(bin);
|
||||
proc.setArguments({"-a", serverAddr(), "write",
|
||||
"session/" + sessionId + "/agent/" + agentId + "/name"});
|
||||
proc.start();
|
||||
if (!proc.waitForStarted(3000)) {
|
||||
qDebug() << "renameAgent failed to start:" << proc.errorString();
|
||||
return false;
|
||||
}
|
||||
proc.write((newName + "\n").toUtf8());
|
||||
proc.closeWriteChannel();
|
||||
proc.waitForFinished(5000);
|
||||
|
||||
if (proc.exitCode() != 0) {
|
||||
qDebug() << "renameAgent failed:" << QString::fromUtf8(proc.readAllStandardError()).trimmed();
|
||||
return false;
|
||||
}
|
||||
|
||||
QString path = "session/" + sessionId + "/agent/" + agentId + "/name";
|
||||
if (m_9p->write(path, (newName + "\n").toUtf8())) {
|
||||
// With immutable-ID paths, renaming no longer invalidates streams.
|
||||
refreshSessions();
|
||||
return true;
|
||||
}
|
||||
qDebug() << "renameAgent failed:" << m_9p->lastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Root data loading (lazy) ---
|
||||
|
||||
|
|
|
|||
Reference in New Issue