# app/ — the Qt front end CST-agent's GUI: **Qt 6 + QML + the C engine, in one process**. No HTTP server, no port, no token, no second process — the app creates a `cst_agent_t` directly and drives it through `cst_agent.h`. ``` app/ the GUI src/main.cpp entry point, --workspace / --selftest src/core/AgentCore.* QObject around cst_agent_t: turns, hooks, consent src/core/ToolProvider.h how the front end hands tools to the engine src/platform// platform tools the app implements itself qml/Main.qml the window (chat, tool runs, consent dialog) tools/selftest.c C smoke test of the embedding path (no Qt) ``` ## Build ```sh # engine + GUI + C smoke test cmake -S app -B build-app -DCMAKE_BUILD_TYPE=Debug cmake --build build-app -j QT_QPA_PLATFORM=offscreen ctest --test-dir build-app # cst-app-selftest # the app alone ./build-app/cst-agent-app --workspace ~/notes ./build-app/cst-agent-app --size 480x900 # the narrow (drawer) layout ./build-app/cst-agent-app --uitest # drive the window, exit code = failures ./build-app/cst-agent-app --selftest # same checks as the C test ``` The standalone build is the supported one: `app/CMakeLists.txt` uses the `cstcore` target when the parent project defines one, and otherwise builds the engine itself from `../src` (TUI, `main.c` and the HTTP front end excluded). Wiring it into the repository's `CMakeLists.txt` as `-DCST_APP=ON` is on the list below. Needs Qt 6.4+ with **Quick / Qml / QuickControls2** (`qt6.qtdeclarative`, `qt6.qtquickcontrols2` on Nix; the online installer's "Qt 6.x Desktop" on Windows/macOS). Without them the configure step warns and builds only the C smoke test — that is deliberate: the embedding path stays verifiable everywhere. Kirigami is not required yet; `qml/Main.qml` uses Qt Quick Controls and keeps the layout in Kirigami's shape (single column, cards, navigation drawer) so swapping in `org.kde.kirigami` later is a QML-only change. Point the app at a Kirigami outside the Qt prefix with `-DCST_APP_QML_IMPORT=/path/to/qml`. ## Why in one process The engine is a library, not a daemon: `libcstcore.a` (Makefile) / the `cstcore` target (CMake). The app owns one `cst_agent_t` per window, so there is no protocol to version, no socket to authenticate and no second process to babysit — and the CLI, TUI and WebUI keep working off the same source. - `AgentCore::send()` runs `cst_agent_run_turn_ex()` on a worker thread; the `cst_agent_hooks_t` callbacks turn into Qt signals (assistant deltas, tool events, tool results, todo, turn end). - Consent is a blocking hook: the worker waits on a `QWaitCondition`, the GUI answers, the turn goes on. `CST_PERM_DENY / ONCE / ALWAYS_TOOL / ALWAYS_PREFIX` map 1:1 onto the buttons, and `ALWAYS_*` still lands in `.agent/permissions.json`, so a desktop decision is visible to the TUI. - `cancel()` is `cst_agent_request_cancel()`; the turn unwinds as a normal result, not an exception. ## Layout and input One shell, three sizes (everything is styled from `qml/Theme.qml`): - **Desktop**: the sidebar is docked and its divider can be dragged (double click restores the auto width). Hover states, tooltips, selectable text and a wheel-scrolled transcript. - **Narrow window** (below `Theme.compactBreakpoint`, 880 px): the sidebar folds away, the header grows a `☰` button, and the settings slide in over the transcript with a scrim; the sidebar itself scrolls, because a phone window cannot show it all. - **Touch**: controls keep a `Theme.touchTarget` (34 px) hit area, buttons do not depend on hover, the composer asks the platform keyboard not to autocorrect (`ImhNoPredictiveText`), and Enter/Shift+Enter still behave. `--uitest` checks this without a human: it loads the window, clicks with synthesized **mouse** events, taps with a synthesized **touch** device, types into the composer, then narrows the window to 460 px to assert the dock → drawer switch and opens/closes the drawer. One line per check, non-zero exit on failure: ```sh QT_QPA_PLATFORM=offscreen ./build-app/cst-agent-app --uitest # ok wide window: sidebar docked # ok mouse click on "general" reaches the engine # ok touch tap on "coding" reaches the engine # ... ``` ## Tools the app provides A tool implemented in the front end is registered with `cst_tool_register()` and the owner `app:`, exactly like a plugin or MCP server would: ```c cst_tool_def_t def = {0}; def.name = "notification_send"; def.desc = "Send a system notification."; def.perm = CST_TP_ASK; /* class: drives the consent dialog */ def.perm_name = "notify"; /* policy: what the user is really granting */ def.platforms = ANDROID_ONLY; /* NULL = everywhere */ def.available = probe; /* 0 / -1 with a reason; cached */ def.run_ex = my_run; def.ud = my_state; def.owner = "app:android"; cst_tool_register(&def); ``` - `platforms` + `available` decide *whether* the tool exists here: an Android-only tool never reaches the prompt or `tools[]` on Linux, and `cst_tool_run()` refuses it with the reason if a model asks anyway. - A front-end tool may take over the **name** of a built-in that cannot run on this host (the Android clipboard replacing the desktop `xclip` one); it may not shadow one that works. - `cst_tool_unregister_owner("app:android")` drops them again — do it when a platform service goes away, then `cst_tool_meta_reload()`. - `setenv("CST_PLATFORM", ...)` overrides the detected host name (tests, a launcher that knows better). Note `run_ex` runs on the worker thread while the turn is going: touch the GUI only through queued signals. ## Platform notes | Platform | How the engine gets built | Platform tools | |---|---|---| | Linux | `cmake -S app -B build-app` (libcurl via pkg-config) | clipboard (shell helpers), notification (`notify-send` / D-Bus) | | Windows | Qt's MinGW kit, `cmake -G Ninja -DCMAKE_PREFIX_PATH=/lib/cmake`; the engine needs libcurl for Windows (see `docs/README-windows.md`) | notification via the WinRT/tray API, clipboard through the core's Win32 path | | Android | engine compiled with the NDK inside the APK (`-DCST_TUI=0`, `CST_NO_WEBUI=1`); the app talks to Android APIs through Qt (JNI) | notification (Android), share sheet, clipboard, file picker | | macOS | Qt from the installer, libcurl from the system | notification (`osascript`), clipboard (`pbpaste`/`pbcopy`) | Android's long-term shape: the core lives in the APK (no Termux needed), the platform tools are Kotlin/JNI behind the same `cst_tool_register()` contract. The build flag that keeps Unix helpers out of the picture is `CST_PLATFORM=android` (or the `__ANDROID__` detection inside the engine). ## Status - [x] engine as a library (`libcstcore.a` / `cstcore`), in-process embed API - [x] tool metadata: `perm_name`, `platforms`, `available`, shadowing rule - [x] `AgentCore` (turns, cancel, consent, todo) and the C smoke test - [x] QML shell: transcript cards, tool cards with diff coloring, thinking blocks, consent/ask dialogs, sidebar, responsive dock/drawer layout, `--uitest` for mouse, touch and small-screen checks - [ ] a real turn driven through the window (needs a provider key) - [ ] session list / switch, attachments, settings page - [ ] Kirigami (Breeze look), tray, global shortcuts - [ ] Android APK (engine in-process, JNI platform tools)