/* AgentCore: the Qt wrapper around one cst_agent_t. * * The engine runs turns on a worker thread; every cst_agent_hooks_t callback * is turned into a Qt signal, and the two hooks that need an answer * (on_permission, on_ask_user) block the worker until the GUI replies. No * HTTP, no socket: the GUI owns the engine in its own process. */ #ifndef CST_APP_AGENTCORE_H #define CST_APP_AGENTCORE_H #include #include #include #include #include #include "cst_agent.h" #include "cst_tools.h" class QThread; class AgentCore : public QObject { Q_OBJECT Q_PROPERTY(bool running READ running NOTIFY runningChanged) Q_PROPERTY(QString workspace READ workspace NOTIFY workspaceChanged) Q_PROPERTY(QString mode READ mode NOTIFY modeChanged) Q_PROPERTY(QString provider READ provider NOTIFY configChanged) Q_PROPERTY(QString model READ model NOTIFY configChanged) Q_PROPERTY(int approval READ approval WRITE setApproval NOTIFY approvalChanged) Q_PROPERTY(bool planMode READ planMode WRITE setPlanMode NOTIFY planModeChanged) Q_PROPERTY(QString todo READ todo NOTIFY todoChanged) public: explicit AgentCore(QObject *parent = nullptr); ~AgentCore() override; /* Start the engine on a workspace. provider/model/apiKey may be empty to * fall back to the saved credentials and the provider's default model. */ bool open(const QString &workspace, const QString &provider, const QString &model, const QString &apiKey, QString *err); void close(); bool running() const { return m_running; } QString workspace() const { return m_workspace; } QString mode() const { return QString::fromUtf8(m_cfg.mode); } QString provider() const { return QString::fromUtf8(m_cfg.provider); } QString model() const { return QString::fromUtf8(m_cfg.model); } int approval() const { return m_cfg.approval; } bool planMode() const { return m_cfg.plan_mode != 0; } QString todo() const { return m_todo; } /* Markdown -> HTML with the engine's own renderer (cst_web_md_html), so a * message looks the same here as it does in the WebUI. */ Q_INVOKABLE QString renderMarkdown(const QString &text) const; /* The engine, for the parts that need it (tool registration). */ cst_agent_t *agent() const { return m_agent; } const cst_agent_config_t *config() const { return &m_cfg; } /* Ask a question; emits the streaming signals, then turnFinished(). */ Q_INVOKABLE void send(const QString &prompt); Q_INVOKABLE void cancel(); /* Consent: answers map to CST_PERM_*. */ Q_INVOKABLE void answerPermission(int choice); Q_INVOKABLE void answerQuestion(const QString &answer); Q_INVOKABLE void setMode(const QString &mode); /* coding | general */ Q_INVOKABLE void setApproval(int approval); /* cst_approval_t */ Q_INVOKABLE void setPlanMode(bool on); /* Tools the front end implements itself. The engine copies the definition * (strings included), so a stack value is fine; ud must outlive it. * owner is what unregisterOwner() later matches. */ bool registerTool(const cst_tool_def_t *def); void unregisterOwner(const QString &owner); /* Re-probe platform tools after a service appeared or went away. */ void reloadToolMeta() { cst_tool_meta_reload(); } /* Answer every consent question with "once" (the C smoke test, headless * runs, a --yes flag). */ void setAutoApprove(bool on) { m_autoApprove = on; } signals: void configChanged(); void approvalChanged(); void planModeChanged(); void statusChanged(const QString &text); void assistantDelta(const QString &text); void thinkDelta(const QString &text); void toolEvent(const QString &phase, const QString &name, const QString &detail); void toolResult(const QString &name, int exitCode, const QString &output); void todoChanged(const QString &text); void permissionRequested(const QString &tool, const QString &detail, const QString &preview); void questionAsked(const QString &question, const QString &options); void turnFinished(int rc, const QString &err); void runningChanged(); void workspaceChanged(); void modeChanged(); private: friend class Turn; void finishTurn(int rc, const QString &err); void setRunning(bool on); /* Hook trampolines (C, run on the worker thread). */ static void hookStatus(const char *msg, void *ud); static void hookAssistant(const char *text, size_t len, void *ud); static void hookThink(const char *text, size_t len, void *ud); static void hookToolEvent(const char *phase, const ctl_tool_call_t *call, const char *detail, void *ud); static void hookToolResult(const ctl_tool_call_t *call, const char *out, size_t len, int exit_code, void *ud); static void hookTodo(const char *text, void *ud); static int hookPermission(const char *tool, const char *detail, const char *preview, void *ud); static int hookAskUser(const char *question, const char *options, char *ans, size_t ans_sz, void *ud); /* Post a lambda onto the GUI thread. */ template void post(F f) { QMetaObject::invokeMethod(this, f, Qt::QueuedConnection); } cst_agent_t *m_agent = nullptr; cst_agent_config_t m_cfg; cst_agent_hooks_t m_hooks; QString m_workspace; QThread *m_turn = nullptr; bool m_running = false; bool m_closing = false; /* consent handshake between the worker and the GUI thread */ QMutex m_askMu; QWaitCondition m_askCv; int m_permAnswer = CST_PERM_DENY; bool m_permPending = false; QString m_question; QString m_todo; QByteArray m_answer; bool m_questionPending = false; bool m_autoApprove = false; }; #endif /* CST_APP_AGENTCORE_H */