- consent: on_permission / on_ask_user wait at most two / five minutes, and in
the background (a phone off screen, a window that is gone) the answer is
refused at once with a reason -- a dialog nobody can see blocked the worker
for good, which is what "the app hangs while the agent works" looked like.
- finishTurn() no longer joins the thread it is called from; the event loop
reaps the Turn instead.
- AndroidTools: the hop to the GUI thread pings first and gives up after 3 s
instead of waiting forever on a busy GUI thread.
- the session list stopped reading every transcript: cst_agent_store_info()
counts newlines in 64 KiB blocks (first_user only within the first 256 KiB),
and AgentCore::sessions() reuses a row while (mtime, size) is unchanged.
- session_search gets a budget (max_bytes 32 MB, max_seconds 5 s), skips lines
over 64 KiB, and says when the budget stopped the walk.
- the header shows the phase ("shell: make -j8"), so a long turn no longer
looks like a stuck one.
- md_to_pdf: the front end's own Qt tool (owner app:qt, every platform):
markdown or a workspace .md/.html becomes an A4 PDF with 15 mm margins,
paginated by hand, written into the workspace and delivered as a file card.
No PrintSupport, so the phone gets it too.
- --uitest runs md_to_pdf through the registry with Chinese text and checks the
bytes are a PDF; app/CMakeLists.txt no longer drops src/web_md.c from the
standalone engine build, which the window needs to render markdown.
Version 0.9.7.
245 lines
11 KiB
CMake
245 lines
11 KiB
CMake
cmake_minimum_required(VERSION 3.16)
|
|
project(cst-agent-app LANGUAGES C CXX VERSION 0.1.0)
|
|
|
|
# The Qt front end (app/) embeds the C engine in-process: it links libcstcore
|
|
# and never opens a port. Two targets:
|
|
#
|
|
# cst-app-selftest pure C smoke test of the embedding path (no Qt), so the
|
|
# C side is still guarded on machines without Qt Quick
|
|
# cst-agent-app the GUI (Qt 6 Quick + QML), built when the Qt modules
|
|
# are there
|
|
#
|
|
# Standalone build (builds the engine from ../ too):
|
|
# cmake -S app -B build-app -DCMAKE_BUILD_TYPE=Debug && cmake --build build-app -j
|
|
# Inside the repo build (the parent defines the cstcore target, shared with the CLI):
|
|
# cmake -B build -DCST_BUILD_APP=ON && cmake --build build --target cst-agent-app
|
|
#
|
|
# Desktop install (the window, its launcher entry and the icon; the CLI has
|
|
# its own installer) -- default.nix installs exactly this component:
|
|
# cmake --install build --component app --prefix ~/.local
|
|
#
|
|
# Extra QML import paths, e.g. a Kirigami installed away from the Qt prefix:
|
|
# -DCST_APP_QML_IMPORT=... (added to QML2_IMPORT_PATH / the app's runtime)
|
|
|
|
option(CST_APP_GUI "build the Qt GUI (needs Qt6 Quick, Qml, QuickControls2)" ON)
|
|
option(CST_APP_SELFTEST "build the C embedding smoke test" ON)
|
|
|
|
# --- the engine ----------------------------------------------------------
|
|
# Used as a subproject (../CMakeLists.txt already defined cstcore, engine
|
|
# compiled once for CLI and app) or built here from the sources next door.
|
|
if(NOT TARGET cstcore)
|
|
set(CST_APP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
|
include_directories("${CST_APP_ROOT}/include" "${CST_APP_ROOT}/vendor/cjson")
|
|
file(GLOB CST_APP_CORE_SRC
|
|
"${CST_APP_ROOT}/src/*.c"
|
|
"${CST_APP_ROOT}/src/tools/*.c"
|
|
"${CST_APP_ROOT}/vendor/cjson/cJSON.c")
|
|
list(REMOVE_ITEM CST_APP_CORE_SRC
|
|
"${CST_APP_ROOT}/src/main.c"
|
|
"${CST_APP_ROOT}/src/tui.c"
|
|
"${CST_APP_ROOT}/src/tui_stub.c"
|
|
"${CST_APP_ROOT}/src/textsel.c")
|
|
# The HTTP front end is not part of an in-process app: no server, no port.
|
|
list(REMOVE_ITEM CST_APP_CORE_SRC
|
|
"${CST_APP_ROOT}/src/net.c"
|
|
"${CST_APP_ROOT}/src/http_srv.c"
|
|
"${CST_APP_ROOT}/src/webui.c")
|
|
# web_md.c stays in: the window renders markdown with it (AgentCore::
|
|
# renderMarkdown) and the md_to_html tool needs it. Only the HTTP server
|
|
# is left out.
|
|
if(EXISTS "${CST_APP_ROOT}/.gen/models_builtin.inc")
|
|
include_directories("${CST_APP_ROOT}/.gen")
|
|
endif()
|
|
add_library(cstcore STATIC ${CST_APP_CORE_SRC})
|
|
target_compile_definitions(cstcore PRIVATE CST_NO_WEBUI=1)
|
|
find_package(CURL REQUIRED)
|
|
find_package(Threads REQUIRED)
|
|
target_link_libraries(cstcore PUBLIC ${CURL_LIBRARIES} Threads::Threads m)
|
|
message(STATUS "app: building the engine from ${CST_APP_ROOT}")
|
|
else()
|
|
message(STATUS "app: linking the cstcore target of the parent build")
|
|
endif()
|
|
|
|
# --- the C smoke test ----------------------------------------------------
|
|
if(CST_APP_SELFTEST)
|
|
add_executable(cst-app-selftest tools/selftest.c)
|
|
target_link_libraries(cst-app-selftest PRIVATE cstcore)
|
|
enable_testing()
|
|
add_test(NAME cst-app-selftest COMMAND cst-app-selftest)
|
|
endif()
|
|
|
|
# --- the GUI -------------------------------------------------------------
|
|
if(CST_APP_GUI)
|
|
# Android's platform file talks to the platform through QtAndroidPrivate
|
|
# (the POST_NOTIFICATIONS probe), whose include path comes with CorePrivate.
|
|
if(ANDROID)
|
|
find_package(Qt6 6.4 COMPONENTS Core Gui Qml Quick QuickControls2 Test
|
|
CorePrivate QUIET)
|
|
else()
|
|
find_package(Qt6 6.4 COMPONENTS Core Gui Qml Quick QuickControls2 Test QUIET)
|
|
endif()
|
|
if(NOT Qt6_FOUND)
|
|
message(WARNING
|
|
"app: Qt 6 Quick/Qml not found, skipping cst-agent-app "
|
|
"(the C selftest still builds; install e.g. qt6.qtdeclarative / "
|
|
"qt6.qtquickcontrols2 and reconfigure)")
|
|
else()
|
|
qt_standard_project_setup()
|
|
# Qt 6.5+ policies: keep the module resource at :/qt/qml/<URI>/ (what
|
|
# main.cpp loads) and let CMake generate the qmldir of qml/ for us.
|
|
qt_policy(SET QTP0001 NEW)
|
|
qt_policy(SET QTP0004 NEW)
|
|
# The front end's platform tools: one file per platform, the same split
|
|
# core/ToolProvider.cpp makes. Android brings its own (JNI, owner
|
|
# "app:android"); the other platforms still use the Linux file, whose
|
|
# registration entry point is simply not called there.
|
|
if(ANDROID)
|
|
set(CST_APP_PLATFORM_SRC src/platform/android/AndroidTools.cpp)
|
|
else()
|
|
set(CST_APP_PLATFORM_SRC src/platform/linux/NotifyTool.cpp)
|
|
endif()
|
|
qt_add_executable(cst-agent-app
|
|
src/main.cpp
|
|
src/core/AgentCore.cpp
|
|
src/core/ToolProvider.cpp
|
|
src/core/PdfTools.cpp
|
|
src/ui/UiTest.cpp
|
|
src/ui/ThemeIcons.cpp
|
|
src/ui/SessionImages.cpp
|
|
${CST_APP_PLATFORM_SRC})
|
|
target_include_directories(cst-agent-app PRIVATE
|
|
src "${CMAKE_CURRENT_SOURCE_DIR}/../include")
|
|
target_compile_definitions(cst-agent-app PRIVATE CST_APP_VERSION="${PROJECT_VERSION}")
|
|
if(CST_APP_QML_IMPORT)
|
|
target_compile_definitions(cst-agent-app PRIVATE
|
|
CST_APP_QML_IMPORT="${CST_APP_QML_IMPORT}")
|
|
endif()
|
|
target_link_libraries(cst-agent-app PRIVATE
|
|
Qt6::Core Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Test cstcore)
|
|
if(ANDROID)
|
|
# app/src/platform/android/AndroidTools.cpp talks to the platform
|
|
# through QtAndroidPrivate (permissions) -- a private header, hence the
|
|
# private module for its include path.
|
|
target_link_libraries(cst-agent-app PRIVATE Qt6::CorePrivate)
|
|
endif()
|
|
if(WIN32)
|
|
set_target_properties(cst-agent-app PROPERTIES WIN32_EXECUTABLE TRUE)
|
|
endif()
|
|
|
|
# A desktop install. The icon goes where the theme layout says, so the
|
|
# entry's Icon= name finds it, and the entry's file name is the app_id Qt
|
|
# sets (QGuiApplication::applicationName) -- rename one and the launcher
|
|
# loses the icon. All of it in the "app" component: the CLI keeps coming
|
|
# from scripts/bootstrap_linux.sh, and two `cst-agent` in PATH is how a
|
|
# version nobody meant to run gets run.
|
|
if(NOT ANDROID)
|
|
include(GNUInstallDirs)
|
|
install(TARGETS cst-agent-app
|
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT app)
|
|
install(FILES linux/cst-agent-app.desktop
|
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications COMPONENT app)
|
|
install(FILES linux/cst-agent-app.svg
|
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps
|
|
COMPONENT app)
|
|
endif()
|
|
|
|
# --- Android (APK) -----------------------------------------------------
|
|
# The engine is compiled in (top-level CMakeLists: -DCST_BUILD_TUI=OFF,
|
|
# -DCST_BUILD_WEBUI=OFF, so src/webui_stub.c stands in for the server and
|
|
# nothing listens on a port). The manifest, icons and strings live in
|
|
# app/android/, which androiddeployqt copies next to its own gradle
|
|
# project. minSdk 24 is the engine's own API_LEVEL (scripts/
|
|
# build_third_party_android.sh builds curl/mbedTLS for android-24).
|
|
if(ANDROID)
|
|
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/../include/cst_version.h"
|
|
_cst_ver_lines REGEX "#define CST_VERSION_(MAJOR|MINOR|PATCH) ")
|
|
foreach(_line ${_cst_ver_lines})
|
|
if(_line MATCHES "CST_VERSION_MAJOR ([0-9]+)")
|
|
set(_cst_vmaj "${CMAKE_MATCH_1}")
|
|
elseif(_line MATCHES "CST_VERSION_MINOR ([0-9]+)")
|
|
set(_cst_vmin "${CMAKE_MATCH_1}")
|
|
elseif(_line MATCHES "CST_VERSION_PATCH ([0-9]+)")
|
|
set(_cst_vpat "${CMAKE_MATCH_1}")
|
|
endif()
|
|
endforeach()
|
|
set_target_properties(cst-agent-app PROPERTIES
|
|
QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android"
|
|
QT_ANDROID_MIN_SDK_VERSION 24
|
|
QT_ANDROID_TARGET_SDK_VERSION 35
|
|
QT_ANDROID_VERSION_NAME "${_cst_vmaj}.${_cst_vmin}.${_cst_vpat}"
|
|
QT_ANDROID_VERSION_CODE 1
|
|
QT_ANDROID_ABIS "${ANDROID_ABI}")
|
|
message(STATUS "app: Android package "
|
|
"${_cst_vmaj}.${_cst_vmin}.${_cst_vpat}, ABI ${ANDROID_ABI}, "
|
|
"minSdk 24, targetSdk 35")
|
|
|
|
# Signing: androiddeployqt signs the APK itself and refuses a signed
|
|
# gradle output ("Could not copy unsigned APK"), so the keystore goes
|
|
# here, not into gradle. The path, alias and passwords arrive through
|
|
# the QT_ANDROID_KEYSTORE_* environment variables;
|
|
# scripts/build_apk_android.sh points them at the standard Android debug
|
|
# key it creates on first use, and CST_ANDROID_KEYSTORE overrides it.
|
|
if(DEFINED ENV{QT_ANDROID_KEYSTORE_PATH} AND NOT "$ENV{QT_ANDROID_KEYSTORE_PATH}" STREQUAL "")
|
|
set(QT_ANDROID_SIGN_APK TRUE)
|
|
message(STATUS "app: signing the APK with "
|
|
"$ENV{QT_ANDROID_KEYSTORE_PATH} ($ENV{QT_ANDROID_KEYSTORE_ALIAS})")
|
|
else()
|
|
message(STATUS "app: QT_ANDROID_KEYSTORE_PATH unset -- the APK stays "
|
|
"unsigned and 'adb install' will refuse it (INSTALL_PARSE_FAILED_NO_CERTIFICATES)")
|
|
endif()
|
|
endif()
|
|
|
|
# Theme is read by every component, so it is a QML singleton (one instance,
|
|
# no object needed in the tree).
|
|
set_source_files_properties(qml/Theme.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
|
|
# Keep the module flat in the resource tree (:qt/qml/CstAgent/Main.qml).
|
|
# Sources live in qml/, but a module whose files sit in a subdirectory of
|
|
# the URI makes Qt register the types with relative URLs ("qmlRegisterType
|
|
# requires absolute URLs" on every start).
|
|
foreach(_qml Main ChatView Sidebar SessionRow MessageCard ToolCard ThinkBlock
|
|
NoteLine TodoPanel Composer ConsentDialog AskDialog SettingsDialog
|
|
ThemeIcon Theme ImageStrip)
|
|
set_source_files_properties(qml/${_qml}.qml PROPERTIES QT_RESOURCE_ALIAS ${_qml}.qml)
|
|
endforeach()
|
|
qt_add_qml_module(cst-agent-app
|
|
URI CstAgent
|
|
VERSION 1.0
|
|
QML_FILES
|
|
qml/Main.qml
|
|
qml/ChatView.qml
|
|
qml/Sidebar.qml
|
|
qml/SessionRow.qml
|
|
qml/MessageCard.qml
|
|
qml/ToolCard.qml
|
|
qml/ThinkBlock.qml
|
|
qml/NoteLine.qml
|
|
qml/TodoPanel.qml
|
|
qml/Composer.qml
|
|
qml/ConsentDialog.qml
|
|
qml/AskDialog.qml
|
|
qml/SettingsDialog.qml
|
|
qml/ThemeIcon.qml
|
|
qml/ImageStrip.qml
|
|
qml/Theme.qml)
|
|
|
|
# The icons the window asks for, compiled in (app/icons, Breeze's scalable
|
|
# names copied at 24@3x). The desktop finds them in the platform's icon
|
|
# theme and only falls back to these (ThemeIcons.cpp); a machine without
|
|
# any theme -- Android -- has nothing else, and before this the QML glyph
|
|
# fallback was all that showed, which the phone's fonts cannot render.
|
|
set(CST_APP_ICONS
|
|
go-down mail-attachment application-menu settings-configure edit-copy
|
|
edit-delete arrow-down arrow-right list-add task-complete box
|
|
document-edit text-x-generic folder-open edit-find code-context
|
|
internet-services format-list-unordered help-hint)
|
|
set(_cst_icon_files "")
|
|
foreach(_icon ${CST_APP_ICONS})
|
|
list(APPEND _cst_icon_files "${CMAKE_CURRENT_SOURCE_DIR}/icons/${_icon}.svg")
|
|
endforeach()
|
|
qt_add_resources(cst-agent-app "cst_icons"
|
|
PREFIX "/cst-icons"
|
|
BASE "${CMAKE_CURRENT_SOURCE_DIR}/icons"
|
|
FILES ${_cst_icon_files})
|
|
endif()
|
|
endif()
|