235652604dadfa3e16cb25dae59088634d17cb78
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
235652604d | feat: add Kimi Code and web APIs | ||
|
|
60047ca5ce |
the session readers use getline(3), which a Windows CRT does not have
Building the Windows TUI by hand -- the first local build since the CI file went away -- stopped in src/tools/session_tools.c: `getline` was an implicit declaration there, which this GCC treats as an error. src/session.c had a private fgetc() shim for exactly that; the session-list scan is newer and used the POSIX call directly, and only the Windows build notices. The shim moves to os_compat as os_getline(): getline(3) itself on Unix, the fgetc() loop on Windows, one prototype in the header so a caller reads the same everywhere. src/session.c drops its copy and calls it; the scan does too (its `ssize_t` becomes `long long`, which Windows has). make check is green on Linux, the Windows cross build now finishes and packages (dist/cst-agent-0.9.7-windows-x86_64.zip plus the NSIS setup), and the static Linux tarball was rebuilt with it. |
||
|
|
042d7f6999 |
v0.9.7: a turn that cannot hang, a session list that reads nothing twice, and PDFs
- 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.
|
||
|
|
580d533656 |
v0.9.6: documents, downloads, PDFs and the phone's own files
Four tools and a document writer, plus the storage half of the phone: until now the picker's answer (a content:// URI) reached the engine as a path, and it said "cannot read" -- the app had no way to hand a phone's file over. - md_to_html (new, src/tools/web.c + cst_web_md_document()): markdown in, one self-contained HTML document out -- doctype, utf-8, viewport, escaped title, a small stylesheet; no script, no external request. It is written into the workspace and delivered as a file card (for_user, so it never enters a request), the way deliver_file does. The default name comes from the source file's stem or, for pasted markdown, from the title with what a path cannot carry turned into '-'. It has a preview, so an approval prompt says "render x -> y.html" before anything is written. - download (src/tools/web.c): an http(s) URL into a file in the workspace, streamed with a 128 MB ceiling; a redirect chain, the same CA handling and the same refusals as web_fetch, but the bytes stay on disk (a half file is removed on any failure, including HTTP >= 400). The name comes from the URL when the caller names only a directory. - read_pdf (src/tools/fs_tools.c): the text layer and page count of a PDF, which read_file refuses because the bytes are binary -- and a PDF is exactly what arrives from a phone. Same extractor an attachment uses, its own 4 MiB input ceiling, 256 KiB of text by default with a note when cut. - session_search (new, src/tools/session_tools.c): every stored session -- this one and all the ones before it -- searched for a substring, reporting the session's id, message count and title, then the matching lines with their number and role. Case-insensitive by default, capped at 20 matches. - device_info (same file, perm_name "device"): version, system/arch/CPU count, workspace, sandbox, model and permissions, config/temp/program directories, pid -- so the model stops guessing where it runs. - Tools 3 and 4 come with tests/test_tools_ext.c (also in both build files): the document is a whole page, a script tag and a javascript: link do not survive, a hostile title stays text and cannot write outside the workspace, the preview names the file, read_pdf/download refuse what they must (no network in a test), session_search finds a fixture session and says so when nothing matches, device_info names the workspace. README's tool table grew five rows. - Android storage: the picker (SAF) answers with a content:// URI, and the engine opens files by path, so the app now copies what it points at into its own directory first -- app/android/src/xyz/whcself/cstagent/ AttachFile.java, called through a new app::materializeAttachment() (ToolProvider.h: one API for every platform; a desktop strips file:// and refuses content:// with a reason). The copy needs no permission at all: the grant travels with the URI. - "All files access" (MANAGE_EXTERNAL_STORAGE) is the other half: it is what lets the engine read a path in shared storage that a task names rather than picks. Android has no runtime dialog for it, so main.cpp opens the one Settings screen once (AttachFile.java's requestAllFilesAccess, with the all-apps screen as a fallback) and never insists: without it everything works, one picked file at a time. On the NX789J the screen came up, the app-op reads back "allow", and a relaunch no longer asks. - Composer.qml hands a content:// URI through untouched (it is not a path, and decoding it would corrupt it); AgentCore::attachFile() turns whatever the picker gave into a path first. Desktop: build clean, make check 36/36 (the new test included, also under ASan), --uitest all green, --selftest and cst-app-selftest OK. The APK builds and installs (86 MB, release-signed) with the new Java class in its dex. Version 0.9.6. |
||
|
|
60cbce3c5c |
v0.9.2: files the agent delivers come back as cards, and the saved key is used
A turn could write a report and say "here it is" -- and there was nothing to
open. The engine had no way to hand a finished file to the person, and the
front end had no way to show one. This release adds the whole path: a tool to
deliver, a store that keeps the copy, a card in the transcript, and open /
share through the platform. It also fixes two bugs the phone exposed while it
was being tried there.
deliver_file (engine)
- cst_attachment_t.for_user marks a copy that belongs to the person, not to
the model; cst_attach_import_artifact() stores any bytes up to
CST_ARTIFACT_MAX_BYTES (64 MB) with the mime taken from the name's
extension. Attachments are unchanged (content decides the type, 16 MB).
- It is a read-policy engine tool: path resolves inside the workspace, a
directory is refused, title (optional) is the display name.
- The wire is what keeps it out of the conversation: parts_add_attachments()
skips for_user entries without even a "not sent" note, and a turn that only
delivered a file no longer invents an empty user message. tests/test_artifacts.c
pins that down end to end, including a session-file round trip.
The card (Qt front end)
- filesText() replaced imagesText(): one line per file -- kind, stored name,
name, origin, mime, bytes, pixels -- decoded by ImageStrip.qml. A for_user
entry is a file card (extension badge, name, type and size, Open, Share);
a picture stays a thumbnail and opens on tap. Both shapes are read, so
sessions written by an older version still draw.
- AgentCore::openArtifact() / shareArtifact() accept a name only when one of
the session's own messages carries it, and hand the display name to the
platform layer for the status line.
- Android opens through the app's FileProvider (content:// + ACTION_VIEW,
resolveActivity() checked, read permission granted) and shares through the
share sheet; a desktop uses QDesktopServices / the clipboard.
CST_APP_NO_OPEN=1 makes --uitest run without opening anything.
Two bugs the device found
- The session writer had its own copy of the attachment JSON and forgot
`for_user`: the file survived the file, but came back as an attachment --
no card, and put back in front of the model. It now uses the one writer
(cst_attachments_to_json()), with the round trip covered by a test.
- engineToolImages() looked the card up with findTool(), which only finds a
card that is still running -- and the engine reports the result *before*
the files, so the card was already complete and the files were dropped.
A live turn therefore showed no card while a replay of the same session
did. It now matches on the call id.
- A tap that could not work said nothing: ImageStrip.qml's errorText was
never drawn. The card now carries the reason ("no app on the phone opens
report.md").
Also
- The saved provider key reaches the engine on every start: AgentCore::open()
was handed an empty key and the engine never resolves credentials itself
(only the CLI did), so every start asked for it again. It resolves the same
order main.c uses (saved key, provider environment, CST_API_KEY) and wipes
the copy it made; --selftest checks it against a scratch credentials file.
- Android: notification_send reads the Java exception text into its result,
and keeps failing on the NX789J -- the build() returns null and Qt clears
the exception before C++ can read it, so the call has to be wrapped in Java
next (docs/android.md says so).
- --uitest grew the delivered-file case (card, Open, Share, a forged name
refused), the live order of result-then-files, and the startup row count.
Version 0.9.2.
|
||
|
|
5ba215b36c |
v0.8.7: tool capability metadata, in-process engine, Qt app, Android engine
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. |
||
|
|
e0b0eddf3a |
v0.8.6: general mode, clipboard text, PDF text extraction
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. |
||
|
|
50abcf1a91 | build: finish cross-platform release tooling | ||
|
|
895b47274c |
v0.7.0: image input, model registry, prefix-cache savings
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 |
||
|
|
2a43ae6622 |
Windows: official port -- UCRT static build, UTF-8 throughout, Low IL sandbox, ncurses TUI, installer; v0.6.0
Build
* cross build switches to pkgsCross.ucrt64 (shell-win.nix); curl (Schannel),
winpthreads and ncurses 6.6 are linked statically, so the release is
cst-agent.exe + busybox.exe (busybox-w32 Unicode) with no extra DLLs
* windres embeds a manifest with activeCodePage=UTF-8 and a version resource
* ncurses 6.6 Windows console driver patches (mouse button state, A_DIM,
wide-char declarations): scripts/patches/
* scripts/bootstrap_msys2.sh builds the same release natively in MSYS2
UCRT64; PR #1's MINGW64 notes now point there
* vendor/tre-regex (musl's TRE) provides <regex.h> on MinGW
UTF-8
* paths cross the system call boundary as UTF-16 (*W APIs); console code
pages set to 65001 and restored on exit; files are read and written in
binary mode
* child output that is not valid UTF-8 is decoded from the console, OEM or
ANSI system code page
* read_file hides CR; edit_file, apply_patch and write_file keep CRLF line
endings and a UTF-8 BOM
OS layer and processes
* os_dir_* and cst_fnmatch replace the POSIX-only directory walks in
subagent, plugins, skills, undo and fs_tools; realpath / path_under and
symlink rejection work on Windows
* every child goes through CreateProcessW with its own environment block
inside a Job Object, so timeouts and Esc end the whole process tree
Sandbox (auto mode, "auto · lowil")
* run_shell runs with a restricted Low integrity token; the workspace gets
an inheritable Low label once, the sandbox TEMP is labeled Low, and the
credentials file gets a no-read-up label
* drive roots, the profile and system folders are never labeled
* network commands still ask, since Low IL does not block the network;
Access denied and MSYS2's 0xc0000142 offer a rerun outside the sandbox
* /sandbox lists labeled folders, /sandbox reset-labels restores them
TUI
* raster console fonts are switched to Consolas / NSimSun while running
* the home logo falls back to text when the console font lacks U+2580
* mouse drag copy goes to the Windows clipboard
Distribution
* scripts/install_windows.ps1 installs a Gitea release into
%LOCALAPPDATA%\Programs\cst-agent and adds it to the user PATH;
cst-agent-update.cmd reruns it; scripts/release_windows.sh uploads the
zip with the token taken from the environment
* scripts/run_windows.bat is gone; docs/README-windows.md is rewritten
Tests
* new test_lowil, test_win_utf8 and test_os_dir; tests made portable
(cst_test_util.h)
* scripts/win_test.sh and scripts/winvm/ run the suite on English and
Chinese Windows Server VMs as Administrator, a standard user and a
Chinese user name: 29/29 on all six
|
||
|
|
7d3ec9fcb9 |
Extensions: skills, plugins, MCP, sub-agents; configurable keys
- Skills (SKILL.md discovery, skill tool, /skills /skill) - plugin.json exec tools with permission prompts, timeouts and workspace trust (/plugins) - MCP stdio client: JSON-RPC initialize/tools/list/tools/call, mcp__server__tool, /mcp and sidebar status - task tool with general/explore/plan and .agent/agents/*.md roles, parallel read-only runs, cancel propagation, /agents - Keymap with Ctrl+X leader, keybinds.json overrides and /keys panel - Collapsed thinking, throttled streaming Markdown, leaner tool output, parallel read-only tools - Spawn: close-on-exec pipes and a process-wide fork lock - Tests for keymap, skills, plugins, MCP, sub-agents, shell compaction; docs/plugins.md |
||
|
|
ef63f6d612 |
OpenCode-style TUI, model probing, sandboxed auto approval
- TUI: centered home screen with logo, boxed input with mode line, sidebar (>=120 cols), Ctrl+P palette, Tab Build/Plan, Shift+Tab approval cycle, mouse (cards, cursor, pickers, wheel), /sidebar /mouse /approval - /models probing: key check on entry (401 re-asks), API models in /model - Shell sandbox in C: Linux Landlock (writes limited to workspace/tmp, ABI>=4 blocks TCP), macOS sandbox_init, rules fallback elsewhere - Approval modes ask/auto/yolo (default auto); read-only command whitelist; ask to rerun outside the sandbox when a command looks blocked - Upgradable bootstrap scripts (git pull --ff-only, rebuild, reinstall) and cst-agent-update; cst-agent --version - tests: test_sandbox, test_models |
||
|
|
2fde898da0 |
Usability pass: tool registry, TUI polish, saved keys and settings
Tools - Table-driven tool registry with permission classes and default/minimal profiles; new tools: edit_file, list_dir, glob, grep, delete_file, move_file, todo_write, web_fetch, ask_user (plus read_file paging). - Built-in Myers diff; write tools return unified diffs. - Persistent permission rules (.agent/permissions.json), per-turn undo snapshots (.agent/undo), AGENTS.md project notes, auto-compact at 80% of the model context, cancellable run_shell with process-group kill. TUI - Table-driven slash commands: /provider /key /effort /status /tools /permissions /yolo /undo /retry /diff /export /copy /rename /delete /init /max-steps /todo, with / and @ completion. - Permission modal with diff and prefix rules, ask_user modal, todo panel, header with model/effort/context, bracketed paste, line editing, wide-char cursor, NO_COLOR and ASCII fallback; Esc never quits. Startup and configuration - API keys are entered in the TUI (hidden) and saved per provider in <config>/credentials (0600); env variables are only a fallback. - Last provider, per-provider model and reasoning effort are remembered in <config>/settings.json; --effort/-e and /effort send reasoning_effort (low|medium|high|max) to models that accept it. - DeepSeek preset uses deepseek-flash / deepseek-v4-pro. - Session flags --new/--continue/--resume/--list-sessions, --yolo, --confirm. Compatibility and tests - ncurses detection, macOS/Homebrew support, make install, XDG config dir, POSIX cleanups; Gitea CI (gcc/clang/asan/musl/macOS). - Unit tests for diff, fs tools, registry, undo/perms and config; e2e fixtures multi_file_refactor and ask_or_todo; vendored cJSON. |
||
|
|
1f1fcba7ad |
Initial CST-Agent: portable Linux/Termux/Windows coding agent.
Vendored sashidown, OpenAI-compatible env (OPENAI_*), and bootstrap scripts. Co-authored-by: Cursor <cursoragent@cursor.com> |