The engine can be embedded now instead of only driven from a terminal: the
same objects link into a Qt front end (app/) that owns a cst_agent_t inside
its own process -- no port, no token, no second process -- and that front end
now runs on the desktop and, engine-side, on the phone.
Capability metadata (include/cst_tools.h, src/tools/registry.c):
- cst_tool_def_t gains perm_name, platforms and available (a probe that
returns a reason), so a permission is addressable by name and a tool can
say why it cannot run here.
- cst_platform_name(), cst_tool_platform_ok(), cst_tool_available(),
cst_tool_meta_reload(), cst_tool_perm_name() and CST_PLAT_DESKTOP[].
- cst_tool_register() may take over a built-in name that is not runnable on
this platform, so a front end replaces it with its own implementation.
- the registry entries carry their class (delete_file -> "delete"), the
desktop clipboard tools carry a clipboard probe; find/active/run gate on it.
- /api/settings.tools[] exposes perm_name, platforms, available, why and the
declared parameters.
Engine as a library (Makefile, CMakeLists.txt):
- libcstcore.a from the CORE_OBJ set, and a CMake cstcore target shared by
the CLI and by app/ (-DCST_BUILD_APP=ON): the engine is compiled once.
- Android branch for the vendored sashidown: a per-ABI archive built with the
NDK's aarch64-linux-android24-clang wrapper, because CMake's compiler is
plain clang and keeps --target/--sysroot in its own variables.
Qt front end (app/, new):
- AgentCore wraps one cst_agent_t: turns run on a worker thread, every
cst_agent_hooks_t callback becomes a Qt signal, and the two hooks that need
an answer (on_permission, on_ask_user) block the worker until the GUI
replies; mode, approval and plan mode go through the engine's config.
- ToolProvider hands the front end's tools to the engine (owner "app:*"); the
Linux one is notification_send with a probe for notify-send/D-Bus.
- Main.qml: transcript, tool blocks, consent dialog (Deny/Once/Always ->
CST_PERM_*), ask_user, mode switch, Esc to cancel; --selftest stays
headless and runs the embedding checks without QML.
Android (scripts/, docs/android.md, new):
- scripts/build_third_party_android.sh builds static mbedTLS + libcurl for an
ABI (CA path /system/etc/security/cacerts); build_android.sh builds
cst-agent, libcstcore.a and the test binaries for the phone, can push them
and run the whole suite there.
- sandbox_exec.c keeps the plain exec path on Android: the seccomp supervisor
compiled in and the probe reported "seccomp", but writes outside the
workspace were not denied on the device -- claiming a sandbox that does not
hold is worse than reporting none.
- tests/test_diff.c compares layouts only against real GNU diffutils: toybox's
diff prints the same hunk header with its own hunk/context rules.
Tests: test_tool_meta (metadata, platform gate, probe reasons, takeover of a
built-in name) and test_embed (in-process embedding contract) are new; the
existing registry/llm_body assertions now follow availability. 38 test
binaries pass on the host, 36/36 on arm64-v8a Android 16, cst-app-selftest
passes in both the standalone app/ build and the repo build.
Version 0.8.7.
Working mode: --mode coding|general (CST_MODE, settings.json, /mode,
WebUI run settings). general swaps the default framing for a general
assistant one -- workflow, safety notes, tool wording (run_shell, the
subagents) -- and /status shows it; minimal (the paper profile) stays
byte-frozen. The prompt state carries the mode so a switch leaves a
system note in history.
Clipboard: clipboard_read / clipboard_write tools (16 MiB cap read,
default 64 KiB window; writing is a WRITE permission with a preview) on
pbcopy / wl-copy / xclip / pbpaste / wl-paste / xclip and the Win32
clipboard. In the TUI, /attach paste falls back to clipboard text when
there is no image on it.
PDF: a dependency-free text extractor (src/pdf_text.c): inflate,
Flate/ASCIIHex/ASCII85, PNG/TIFF predictors, layout-independent object
scan, object streams, page tree walk with inherited resources, content
lexer (Tj/TJ/'/" and the Td/TD/T*/Tm spacing heuristics), per-font
ToUnicode CMaps and inline images skipped. Encrypted files and pages
with no text layer are refused with the reason.
Attachments: /attach (a path or files dropped on the terminal) now take
images, PDFs and text. A PDF is stored as its text layer with the mime
still saying application/pdf and its page count; anything else has to be
UTF-8 text. Documents travel inline as text parts, so CST_NO_IMAGES and
text-only models no longer drop them, while images keep their old path.
Tests: test_pdf_text (plain, bare and zlib-framed Flate, inline image,
CID + ToUnicode, object stream, error paths), documents in test_attach,
inlined documents in test_llm_body, mode in test_config.
Images
- vision gate: text-only models get a note instead of image parts and no
view_image tool; unknown models retry once without images on HTTP 400
- paste: Ctrl+V and /attach paste read the clipboard (wl-paste, xclip,
pngpaste/osascript, Win32 PNG or CF_DIB converted to PNG); pasting image
file paths attaches them
- MCP image content is stored in the session and sent with the tool result
- attach: rel path validation, base64 decode, /retry keeps images
Model registry
- built-in src/models_builtin.json embedded at build time, merged with
<config>/models.json and cached GET /models metadata (context, vision,
pricing); most specific rule wins per field
- /model labels, /status source, --list-models [provider]
Cache and cost
- stable request prefix (frozen tools, notes as messages), DeepSeek prefix
completion, OpenAI/Anthropic cache hints, /cost and hit rate in the status