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()
|
QString Ollie9pClient::readLog()
|
||||||
{
|
{
|
||||||
if (m_activeSessionId.isEmpty()) return {};
|
if (m_activeSessionId.isEmpty()) return {};
|
||||||
QByteArray out = run9p({"read", agentPath() + "/log"});
|
if (m_9p && m_9p->isConnected()) {
|
||||||
return QString::fromUtf8(out);
|
return QString::fromUtf8(m_9p->read(agentPath() + "/log"));
|
||||||
|
}
|
||||||
|
return QString::fromUtf8(run9p({"read", agentPath() + "/log"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Ollie9pClient::readLogForSession(const QString &sessionId, const QString &agentId)
|
QString Ollie9pClient::readLogForSession(const QString &sessionId, const QString &agentId)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty() || agentId.isEmpty()) return {};
|
if (sessionId.isEmpty() || agentId.isEmpty()) return {};
|
||||||
// Use immutable IDs — the 9P namespace resolves them via aliases.
|
// Use immutable IDs — the 9P namespace resolves them via aliases.
|
||||||
QByteArray out = run9p({"read", "session/" + sessionId + "/agent/" + agentId + "/log"});
|
QString path = "session/" + sessionId + "/agent/" + agentId + "/log";
|
||||||
return QString::fromUtf8(out);
|
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)
|
bool Ollie9pClient::submit(const QString &prompt)
|
||||||
{
|
{
|
||||||
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty() || prompt.trimmed().isEmpty()) return false;
|
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty() || prompt.trimmed().isEmpty()) return false;
|
||||||
QProcess proc;
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/prompt"});
|
return m_9p->write(agentPath() + "/prompt", prompt.toUtf8());
|
||||||
proc.waitForStarted(3000);
|
|
||||||
proc.write(prompt.toUtf8());
|
|
||||||
proc.closeWriteChannel();
|
|
||||||
proc.waitForFinished(5000);
|
|
||||||
return proc.exitCode() == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ollie9pClient::interrupt()
|
bool Ollie9pClient::interrupt()
|
||||||
{
|
{
|
||||||
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
|
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
|
||||||
QProcess proc;
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"});
|
return m_9p->write(agentPath() + "/ctl", "stop");
|
||||||
proc.waitForStarted(3000);
|
|
||||||
proc.write("stop");
|
|
||||||
proc.closeWriteChannel();
|
|
||||||
proc.waitForFinished(3000);
|
|
||||||
return proc.exitCode() == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ollie9pClient::kill()
|
bool Ollie9pClient::kill()
|
||||||
{
|
{
|
||||||
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
|
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
|
||||||
QProcess proc;
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", agentPath() + "/ctl"});
|
return m_9p->write(agentPath() + "/ctl", "kill");
|
||||||
proc.waitForStarted(3000);
|
|
||||||
proc.write("kill");
|
|
||||||
proc.closeWriteChannel();
|
|
||||||
proc.waitForFinished(3000);
|
|
||||||
return proc.exitCode() == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ollie9pClient::killSession(const QString &sessionId)
|
bool Ollie9pClient::killSession(const QString &sessionId)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty()) return false;
|
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 killing the active session, shut down its streams and deselect.
|
||||||
if (sessionId == m_activeSessionId) {
|
if (sessionId == m_activeSessionId) {
|
||||||
|
|
@ -454,26 +445,16 @@ bool Ollie9pClient::killSession(const QString &sessionId)
|
||||||
|
|
||||||
// Use immutable ID — the 9P namespace resolves it via alias.
|
// Use immutable ID — the 9P namespace resolves it via alias.
|
||||||
QString path = "session/" + sessionId + "/ctl";
|
QString path = "session/" + sessionId + "/ctl";
|
||||||
QProcess proc;
|
return m_9p->write(path, "kill");
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", path});
|
|
||||||
proc.waitForStarted(3000);
|
|
||||||
proc.write("kill");
|
|
||||||
proc.closeWriteChannel();
|
|
||||||
proc.waitForFinished(3000);
|
|
||||||
return proc.exitCode() == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ollie9pClient::pauseSession(const QString &sessionId)
|
bool Ollie9pClient::pauseSession(const QString &sessionId)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty()) return false;
|
if (sessionId.isEmpty()) return false;
|
||||||
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
|
|
||||||
QString path = "session/" + sessionId + "/ctl";
|
QString path = "session/" + sessionId + "/ctl";
|
||||||
QProcess proc;
|
if (m_9p->write(path, "pause")) {
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", path});
|
|
||||||
proc.waitForStarted(3000);
|
|
||||||
proc.write("pause");
|
|
||||||
proc.closeWriteChannel();
|
|
||||||
proc.waitForFinished(3000);
|
|
||||||
if (proc.exitCode() == 0) {
|
|
||||||
refreshSessions();
|
refreshSessions();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -483,14 +464,10 @@ bool Ollie9pClient::pauseSession(const QString &sessionId)
|
||||||
bool Ollie9pClient::resumeSession(const QString &sessionId)
|
bool Ollie9pClient::resumeSession(const QString &sessionId)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty()) return false;
|
if (sessionId.isEmpty()) return false;
|
||||||
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
|
|
||||||
QString path = "session/" + sessionId + "/ctl";
|
QString path = "session/" + sessionId + "/ctl";
|
||||||
QProcess proc;
|
if (m_9p->write(path, "resume")) {
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", path});
|
|
||||||
proc.waitForStarted(3000);
|
|
||||||
proc.write("resume");
|
|
||||||
proc.closeWriteChannel();
|
|
||||||
proc.waitForFinished(3000);
|
|
||||||
if (proc.exitCode() == 0) {
|
|
||||||
refreshSessions();
|
refreshSessions();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -510,18 +487,25 @@ bool Ollie9pClient::isSessionPaused(const QString &sessionId) const
|
||||||
QString Ollie9pClient::getConfig()
|
QString Ollie9pClient::getConfig()
|
||||||
{
|
{
|
||||||
if (m_activeSessionId.isEmpty()) return {};
|
if (m_activeSessionId.isEmpty()) return {};
|
||||||
QByteArray out = run9p({"read", agentPath() + "/cfg"});
|
if (m_9p && m_9p->isConnected()) {
|
||||||
return QString::fromUtf8(out);
|
return QString::fromUtf8(m_9p->read(agentPath() + "/cfg"));
|
||||||
|
}
|
||||||
|
return QString::fromUtf8(run9p({"read", agentPath() + "/cfg"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Ollie9pClient::getAgents(const QString &sessionId)
|
QStringList Ollie9pClient::getAgents(const QString &sessionId)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty()) return {};
|
if (sessionId.isEmpty()) return {};
|
||||||
// Use immutable ID — the 9P namespace resolves it via alias.
|
// Use immutable ID — the 9P namespace resolves it via alias.
|
||||||
QByteArray out = run9p({"ls", "session/" + sessionId + "/agent"});
|
QString path = "session/" + sessionId + "/agent";
|
||||||
QString raw = QString::fromUtf8(out).trimmed();
|
QStringList all;
|
||||||
if (raw.isEmpty()) return {};
|
if (m_9p && m_9p->isConnected()) {
|
||||||
QStringList all = raw.split('\n', Qt::SkipEmptyParts);
|
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;
|
QStringList agents;
|
||||||
for (const QString &a : all) {
|
for (const QString &a : all) {
|
||||||
QString name = a.trimmed();
|
QString name = a.trimmed();
|
||||||
|
|
@ -611,30 +595,23 @@ QStringList Ollie9pClient::getAvailableModels(const QString &backend) const
|
||||||
|
|
||||||
bool Ollie9pClient::createQuickSession()
|
bool Ollie9pClient::createQuickSession()
|
||||||
{
|
{
|
||||||
QProcess proc;
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "session/new"});
|
QByteArray result = m_9p->rdwr(QStringLiteral("session/new"), "\n");
|
||||||
proc.waitForStarted(3000);
|
return !result.isEmpty() || m_9p->lastError().isEmpty();
|
||||||
proc.write("\n");
|
|
||||||
proc.closeWriteChannel();
|
|
||||||
proc.waitForFinished(5000);
|
|
||||||
return proc.exitCode() == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Ollie9pClient::createSession(const QString &name, const QString &remote)
|
QString Ollie9pClient::createSession(const QString &name, const QString &remote)
|
||||||
{
|
{
|
||||||
|
if (!m_9p || !m_9p->isConnected()) return QStringLiteral("not connected");
|
||||||
|
|
||||||
QStringList args;
|
QStringList args;
|
||||||
if (!name.isEmpty()) args << "name=" + name;
|
if (!name.isEmpty()) args << "name=" + name;
|
||||||
if (!remote.isEmpty()) args << "remote=" + remote;
|
if (!remote.isEmpty()) args << "remote=" + remote;
|
||||||
|
|
||||||
QProcess proc;
|
QByteArray result = m_9p->rdwr(QStringLiteral("session/new"), (args.join(" ") + "\n").toUtf8());
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "session/new"});
|
QString err = m_9p->lastError();
|
||||||
proc.waitForStarted(3000);
|
if (!err.isEmpty()) {
|
||||||
proc.write((args.join(" ") + "\n").toUtf8());
|
return err;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
return QString(); // success
|
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)
|
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 (sessionId.isEmpty() || cwd.isEmpty()) return QStringLiteral("session ID and directory are required");
|
||||||
|
if (!m_9p || !m_9p->isConnected()) return QStringLiteral("not connected");
|
||||||
|
|
||||||
QStringList agentArgs;
|
QStringList agentArgs;
|
||||||
agentArgs << "cwd=" + cwd;
|
agentArgs << "cwd=" + cwd;
|
||||||
|
|
@ -651,20 +629,11 @@ QString Ollie9pClient::createAgent(const QString &sessionId, const QString &cwd,
|
||||||
if (!remote.isEmpty()) agentArgs << "remote=" + remote;
|
if (!remote.isEmpty()) agentArgs << "remote=" + remote;
|
||||||
if (!agentAlias.isEmpty()) agentArgs << "name=" + agentAlias;
|
if (!agentAlias.isEmpty()) agentArgs << "name=" + agentAlias;
|
||||||
|
|
||||||
QProcess proc;
|
|
||||||
// Use immutable ID — the 9P namespace resolves it via alias.
|
// Use immutable ID — the 9P namespace resolves it via alias.
|
||||||
proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "session/" + sessionId + "/agent/new"});
|
QString path = "session/" + sessionId + "/agent/new";
|
||||||
proc.waitForStarted(3000);
|
QByteArray result = m_9p->rdwr(path, (agentArgs.join(" ") + "\n").toUtf8());
|
||||||
proc.write((agentArgs.join(" ") + "\n").toUtf8());
|
QString err = m_9p->lastError();
|
||||||
proc.closeWriteChannel();
|
if (!err.isEmpty()) {
|
||||||
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");
|
|
||||||
qDebug() << "createAgent failed:" << err;
|
qDebug() << "createAgent failed:" << err;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
@ -674,87 +643,47 @@ QString Ollie9pClient::createAgent(const QString &sessionId, const QString &cwd,
|
||||||
bool Ollie9pClient::killAgent(const QString &sessionId, const QString &agentId)
|
bool Ollie9pClient::killAgent(const QString &sessionId, const QString &agentId)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty() || agentId.isEmpty()) return false;
|
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.
|
// Use immutable IDs — the 9P namespace resolves them via aliases.
|
||||||
QString path = "session/" + sessionId + "/agent/" + agentId + "/ctl";
|
QString path = "session/" + sessionId + "/agent/" + agentId + "/ctl";
|
||||||
QProcess proc;
|
return m_9p->write(path, "kill");
|
||||||
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)
|
bool Ollie9pClient::renameSession(const QString &sessionId, const QString &newName)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty() || newName.isEmpty()) return false;
|
if (sessionId.isEmpty() || newName.isEmpty()) return false;
|
||||||
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
QString bin = ollie9pBin();
|
|
||||||
if (bin.isEmpty()) {
|
|
||||||
qDebug() << "renameSession: ollie-9p not found, cannot rename";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session names are mutable display names. Writing the name file triggers
|
// Session names are mutable display names. Writing the name file triggers
|
||||||
// the rename. Use the immutable ID to address the session.
|
// the rename. Use the immutable ID to address the session.
|
||||||
QProcess proc;
|
QString path = "session/" + sessionId + "/name";
|
||||||
proc.setProgram(bin);
|
if (m_9p->write(path, (newName + "\n").toUtf8())) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// With immutable-ID paths, renaming no longer invalidates streams or
|
// With immutable-ID paths, renaming no longer invalidates streams or
|
||||||
// the active selection. Just refresh the session list to pick up the
|
// the active selection. Just refresh the session list to pick up the
|
||||||
// new display name.
|
// new display name.
|
||||||
refreshSessions();
|
refreshSessions();
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
qDebug() << "renameSession failed:" << m_9p->lastError();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ollie9pClient::renameAgent(const QString &sessionId, const QString &agentId, const QString &newName)
|
bool Ollie9pClient::renameAgent(const QString &sessionId, const QString &agentId, const QString &newName)
|
||||||
{
|
{
|
||||||
if (sessionId.isEmpty() || agentId.isEmpty() || newName.isEmpty()) return false;
|
if (sessionId.isEmpty() || agentId.isEmpty() || newName.isEmpty()) return false;
|
||||||
|
if (!m_9p || !m_9p->isConnected()) return false;
|
||||||
QString bin = ollie9pBin();
|
|
||||||
if (bin.isEmpty()) {
|
|
||||||
qDebug() << "renameAgent: ollie-9p not found, cannot rename";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agent names are mutable display names. Write the name file directly
|
// Agent names are mutable display names. Write the name file directly
|
||||||
// using immutable IDs to address the path.
|
// using immutable IDs to address the path.
|
||||||
QProcess proc;
|
QString path = "session/" + sessionId + "/agent/" + agentId + "/name";
|
||||||
proc.setProgram(bin);
|
if (m_9p->write(path, (newName + "\n").toUtf8())) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// With immutable-ID paths, renaming no longer invalidates streams.
|
// With immutable-ID paths, renaming no longer invalidates streams.
|
||||||
refreshSessions();
|
refreshSessions();
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
qDebug() << "renameAgent failed:" << m_9p->lastError();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Root data loading (lazy) ---
|
// --- Root data loading (lazy) ---
|
||||||
|
|
|
||||||
Reference in New Issue