#ifndef OLLIE9PCLIENT_H #define OLLIE9PCLIENT_H #include #include #include #include #if QT_VERSION_MAJOR >= 6 #include #endif #include "streamfsm.h" #include "ninepconnection.h" // 9P-based client for ollie. Uses ollie-9p subprocess for transport. // Streaming files (chat, statewait) use StreamFsm instances for lifecycle. class Ollie9pClient : public QObject { Q_OBJECT #if QT_VERSION_MAJOR >= 6 QML_ELEMENT QML_SINGLETON #endif Q_PROPERTY(QVariantList sessions READ sessions NOTIFY sessionsChanged) Q_PROPERTY(QString activeSessionId READ activeSessionId WRITE setActiveSessionId NOTIFY activeSessionIdChanged) Q_PROPERTY(QString activeAgentId READ activeAgentId NOTIFY activeAgentIdChanged) Q_PROPERTY(QString activeState READ activeState NOTIFY activeStateChanged) Q_PROPERTY(bool rootBackendsLoaded READ rootBackendsLoaded NOTIFY rootBackendsLoadedChanged) Q_PROPERTY(QStringList availableBackends READ availableBackends NOTIFY availableBackendsChanged) Q_PROPERTY(bool rootAgentsLoaded READ rootAgentsLoaded NOTIFY rootAgentsLoadedChanged) Q_PROPERTY(QStringList availableAgents READ availableAgents NOTIFY availableAgentsChanged) Q_PROPERTY(bool rootModelsLoaded READ rootModelsLoaded NOTIFY rootModelsLoadedChanged) Q_PROPERTY(QString currentBackend READ currentBackend WRITE setCurrentBackend NOTIFY currentBackendChanged) Q_PROPERTY(bool daemonConnected READ daemonConnected NOTIFY daemonConnectedChanged) public: explicit Ollie9pClient(QObject *parent = nullptr); ~Ollie9pClient(); QVariantList sessions() const { return m_sessions; } QString activeSessionId() const { return m_activeSessionId; } Q_INVOKABLE void setActiveSessionId(const QString &id); QString activeAgentId() const { return m_agentId; } QString activeState() const { return m_activeState; } bool rootBackendsLoaded() const { return m_rootBackendsLoaded; } QStringList availableBackends() const { return m_availableBackends; } bool rootAgentsLoaded() const { return m_rootAgentsLoaded; } QStringList availableAgents() const { return m_availableAgents; } bool rootModelsLoaded() const { return m_rootModelsLoaded; } QString currentBackend() const { return m_currentBackend; } bool daemonConnected() const { return m_daemonConnected; } Q_INVOKABLE bool agentConnected(const QString &sessionId, const QString &agentId) const; Q_INVOKABLE QString sessionConnectionColor(const QString &sessionId) const; Q_INVOKABLE void setCurrentBackend(const QString &backend) { if (m_currentBackend == backend) return; m_currentBackend = backend; emit currentBackendChanged(); } Q_INVOKABLE void refreshSessions(); Q_INVOKABLE QString readLog(); Q_INVOKABLE QString readLogForSession(const QString &sessionId, const QString &agentId); Q_INVOKABLE bool submit(const QString &prompt); Q_INVOKABLE bool interrupt(); Q_INVOKABLE bool kill(); Q_INVOKABLE bool killSession(const QString &sessionId); Q_INVOKABLE QString getConfig(); Q_INVOKABLE QStringList getAgents(const QString &sessionId); struct AgentRecord { QString id; QString name; bool connected = false; }; QList readAgentRecords(const QString &sessionId); void reconcileAgentConnections(const QVariantList &sessions); void setAgentConnected(const QString &key, bool connected); QString agentKey(const QString &sessionId, const QString &agentId) const; Q_INVOKABLE void setActiveAgentId(const QString &agentId); Q_INVOKABLE void switchAgent(const QString &sessionId, const QString &agentId); Q_INVOKABLE void loadRootBackends(); 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 QString 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); signals: void sessionsChanged(); void activeSessionIdChanged(); void activeAgentIdChanged(); void activeStateChanged(); void chatReceived(const QString &text); void sessionConnected(); void streamingDone(); // chat process exited (session killed) void rootBackendsLoadedChanged(); void availableBackendsChanged(); void rootAgentsLoadedChanged(); void availableAgentsChanged(); void rootModelsLoadedChanged(); void currentBackendChanged(); void daemonConnectedChanged(); private: void stopAgentStreams(); void stopStreams(); QString agentPath() const; QByteArray run9p(const QStringList &args); void ensureRootDataLoaded(); void setDaemonConnected(bool connected); void startAgentConnections(); void startActiveAgentStreams(); void reconcileAgentConnections(); void stopAgentConnections(); QVariantList m_sessions; QString m_activeSessionId; QString m_activeState = "idle"; QString m_agentId; // Root data cache bool m_rootBackendsLoaded = false; QStringList m_availableBackends; bool m_rootAgentsLoaded = false; QStringList m_availableAgents; bool m_rootModelsLoaded = false; QVariantMap m_rootModels; QString m_currentBackend; bool m_daemonConnected = false; QHash m_agentConnections; QHash m_agentConnected; QSet m_seenAgentConnections; NinePConnection *m_daemon = nullptr; // Streaming FSMs — each wraps a QProcess lifecycle StreamFsm *m_chat = nullptr; // Oneshot — reads agent chat log StreamFsm *m_state = nullptr; // Guarded — reads agent statewait StreamFsm *m_event = nullptr; // Looping — reads server eventwait }; #endif // OLLIE9PCLIENT_H