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.
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
+2
View File
@@ -1717,6 +1717,7 @@ async function showRunSettings() {
const proto = field('工具协议', select(s.protocols || [], s.protocol));
const steps = field('每轮步数', Object.assign(el('input'), { type: 'number', min: 1, max: 1000, value: s.max_steps }));
const prof = field('工具集', select(s.profiles || [], s.tool_profile));
const mode = field('工作模式', select(s.modes || [], s.mode));
box.appendChild(el('h4', 'sub', '需要确认(审批模式 ' + (s.approval || '-') + ')'));
const conf = {};
@@ -1786,6 +1787,7 @@ async function showRunSettings() {
if (proto.value !== s.protocol) body.protocol = proto.value;
if (+steps.value !== s.max_steps) body.max_steps = +steps.value;
if (prof.value !== s.tool_profile) body.tool_profile = prof.value;
if (mode.value !== s.mode) body.mode = mode.value;
const c = {};
for (const k of Object.keys(conf)) if (conf[k].checked !== !!(s.confirm || {})[k]) c[k] = conf[k].checked;
if (Object.keys(c).length) body.confirm = c;