v0.8.6: general mode, clipboard text, PDF text extraction
ci / tcc-check (push) Successful in 59s
ci / linux (aarch64, 0) (push) Successful in 19m11s
ci / linux (x86, 1) (push) Successful in 11m31s
ci / linux (x86_64, 1) (push) Successful in 11m11s
ci / windows (push) Successful in 9m24s

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.
This commit is contained in:
WHC
2026-10-11 11:09:39 +08:00
parent 50abcf1a91
commit e0b0eddf3a
28 changed files with 3291 additions and 111 deletions
+25 -8
View File
@@ -26,6 +26,8 @@ static void usage(const char *argv0) {
" (last used)\n"
" -m, --model NAME model (last used for the provider)\n"
" -e, --effort LEVEL low | medium | high | max (last used)\n"
" -M, --mode NAME coding (default, code framed) | general\n"
" (documents, data, research; last used)\n"
" -w, --workspace DIR project directory (default: current)\n"
" -c, --continue reopen the last session (default)\n"
" -N, --new start a new session\n"
@@ -59,13 +61,13 @@ static void usage(const char *argv0) {
"API keys: the TUI asks for a missing key and saves it per provider\n"
" in the config dir (credentials, mode 0600); /key changes it.\n"
" Fallbacks: <provider env> (DEEPSEEK_API_KEY, ...), CST_API_KEY.\n"
"Settings: provider, models and effort are remembered in\n"
"Settings: provider, models, effort and mode are remembered in\n"
" settings.json next to the credentials.\n"
"Env: CST_PROVIDER CST_MODEL CST_EFFORT CST_PROTOCOL CST_TOOLS\n"
" CST_CONFIRM CST_MAX_STEPS CST_TOOL_OUT_CAP CST_CTX_WINDOW\n"
" CST_CTX_TOKENS CST_AUTO_COMPACT=0 CST_SHELL_TIMEOUT\n"
" CST_METRICS=0 CST_RECORD_SSE CST_REPLAY_SSE CST_MODELS\n"
" CST_APPROVAL=ask|auto|yolo CST_AUTO_APPROVE=1 (=yolo)\n"
"Env: CST_PROVIDER CST_MODEL CST_EFFORT CST_MODE CST_PROTOCOL\n"
" CST_TOOLS CST_CONFIRM CST_MAX_STEPS CST_TOOL_OUT_CAP\n"
" CST_CTX_WINDOW CST_CTX_TOKENS CST_AUTO_COMPACT=0\n"
" CST_SHELL_TIMEOUT CST_METRICS=0 CST_RECORD_SSE CST_REPLAY_SSE\n"
" CST_MODELS CST_APPROVAL=ask|auto|yolo CST_AUTO_APPROVE=1 (=yolo)\n"
" CST_SANDBOX=off CST_SHELL CST_NO_PROJECT_NOTES=1 NO_COLOR\n"
"TUI: type /help inside for commands and keys\n",
argv0);
@@ -374,6 +376,7 @@ int main(int argc, char **argv) {
{"approval", required_argument, 0, 'A'},
{"confirm", required_argument, 0, 'C'},
{"effort", required_argument, 0, 'e'},
{"mode", required_argument, 0, 'M'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{"list-models", optional_argument, 0, 0x100},
@@ -385,8 +388,8 @@ int main(int argc, char **argv) {
{"web-open", no_argument, 0, 0x106},
{0, 0, 0, 0}};
int c;
while ((c = getopt_long(argc, argv, "w:s:b:k:m:P:r:n:T:Hp:dahVNcR:LyA:C:e:", opts, NULL)) !=
-1) {
while ((c = getopt_long(argc, argv, "w:s:b:k:m:P:r:n:T:Hp:dahVNcR:LyA:C:e:M:",
opts, NULL)) != -1) {
switch (c) {
case 'w':
snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", optarg);
@@ -478,6 +481,14 @@ int main(int argc, char **argv) {
}
snprintf(cfg.effort, sizeof(cfg.effort), "%s", optarg);
break;
case 'M':
if (!cst_mode_valid(optarg)) {
fprintf(stderr, "bad --mode %s (coding | general)\n", optarg);
curl_global_cleanup();
return 1;
}
snprintf(cfg.mode, sizeof(cfg.mode), "%s", optarg);
break;
case 'h':
usage(argv[0]);
curl_global_cleanup();
@@ -557,6 +568,12 @@ int main(int argc, char **argv) {
snprintf(cfg.effort, sizeof(cfg.effort), "%s",
cst_effort_valid(e) ? e : CST_DEFAULT_EFFORT);
}
if (!cfg.mode[0]) {
const char *m = getenv("CST_MODE");
if (!cst_mode_valid(m))
m = saved.mode;
snprintf(cfg.mode, sizeof(cfg.mode), "%s", cst_mode_name(m));
}
if (!cfg.api_base[0]) {
const char *env_base = getenv("CST_API_BASE");
if (!env_base || !env_base[0])