v0.8.2: build fixes, WebUI security and correctness
ci / linux (clang) (push) Successful in 5m45s
ci / linux (gcc) (push) Successful in 6m46s
ci / musl (push) Successful in 7m9s
ci / windows-cross (push) Successful in 11m52s

Build: <fcntl.h> in tui.c, format-truncation in http_srv.c and /web.

WebUI:
- refuse cross-site requests (Origin must be the page itself) in the
  default no-token mode: any page could POST approval=yolo and a prompt
- session/workspace/undo/compact/attach/model changes are 409 while a
  turn runs (use-after-free, messages landing in the wrong session)
- ask_user returned "dismissed" at once instead of waiting
- cancel and shutdown wake a turn parked on a permission or question;
  SIGTERM mid-turn no longer hangs or crashes
- a page opened while a question is pending still gets it;
  Last-Event-ID resumes the stream; event seq matches the SSE id
- token mode: page assets load without the token, images via cookie
- --web-open works, --web-port/--web-token are validated
- frontend: gate keys ignored while typing, clear-images button,
  reasoning no longer wiped while streaming

HTTP server: auth and size cap before reading the body, strict
Content-Length, NUL in url_decode, 413 on keep-alive, idle timeout,
absolute-form only without a leading '/', case-insensitive Connection,
header deadline, 400 for long request lines, unlink before close,
poll() instead of select(), SO_EXCLUSIVEADDRUSE on Windows.

Markdown sanitizer escapes quotes in attribute values.

/web: passes this session's provider/model/base/effort/approval (key
via the environment), keeps output off the TUI, shows the real URL and
keeps CURRENT on the TUI's session.

Tests: scripts/e2e_webui_ask.py, scripts/e2e_webui_http.py (run from
e2e_webui.sh), sanitizer quote test.
This commit is contained in:
WHC
2026-10-09 13:51:34 +08:00
parent adf3e9f024
commit e238f5f37d
15 changed files with 1001 additions and 144 deletions
+3 -3
View File
@@ -340,8 +340,8 @@ body.no-side #side { pointer-events: none; }
padding: 9px 15px; max-width: 76%; white-space: pre-wrap; word-break: break-word;
}
.msg.error .body { color: var(--danger); }
.body > :first-child { margin-top: 0; }
.body > :last-child { margin-bottom: 0; }
.body > :first-child, .live-html > :first-child { margin-top: 0; }
.body > :last-child, .live-html > :last-child { margin-bottom: 0; }
.body h1, .body h2, .body h3, .body h4 { line-height: 1.35; margin: 1.1em 0 .5em; }
.body h1 { font-size: 1.45em; } .body h2 { font-size: 1.25em; } .body h3 { font-size: 1.1em; }
.body p, .body ul, .body ol { margin: .55em 0; }
@@ -364,7 +364,7 @@ body.no-side #side { pointer-events: none; }
.body img { max-width: 100%; border-radius: 10px; }
.body hr { border: 0; border-top: 1px solid var(--line); margin: 1.2em 0; }
/* a caret while tokens are still arriving */
.body.live > :last-child::after {
.body.live > .live-html > :last-child::after {
content: ""; display: inline-block; width: 7px; height: 14px;
margin-left: 3px; vertical-align: -2px; border-radius: 2px;
background: var(--accent); animation: blink 1.1s steps(2, start) infinite;