Fix pipe desync: send_ok_raw escapes newlines, expand-components? in env

- send_ok_raw: when SX wire format contains newlines (string literals),
  fall back to (ok "...escaped...") instead of (ok-raw ...) to keep
  the pipe single-line. Prevents multi-line responses from desyncing
  subsequent requests.
- expand-components? flag set in kernel env (not just VM adapter globals)
  so aser-list's env-has? check finds it during component expansion.
- SX_STANDALONE: restore no_oauth but generate CSRF via session cookie
  so mutation handlers (DELETE etc.) still work without account service.
- Shell statics injection: only inject small values (hashes, URLs) as
  kernel vars. Large blobs (CSS, component_defs) use placeholder tokens.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 12:32:03 +00:00
parent ae0e87fbf8
commit 373a4f0134
3 changed files with 43 additions and 23 deletions

View File

@@ -71,6 +71,22 @@ let send_ok_value v = send (Printf.sprintf "(ok %s)" (serialize_value v))
let send_ok_string s = send (Printf.sprintf "(ok \"%s\")" (escape_sx_string s))
let send_error msg = send (Printf.sprintf "(error \"%s\")" (escape_sx_string msg))
(** Send ok-raw, ensuring single-line output.
SX wire format from aser may contain newlines inside string literals.
We must escape those to prevent pipe desync (Python reads one line
at a time), but we can't blindly replace newlines in the raw SX
because that would break string content.
Strategy: wrap as a properly escaped string literal.
Python side will unescape it. *)
let send_ok_raw s =
(* If the result has no newlines, send as-is for backward compat *)
if not (String.contains s '\n') then
send (Printf.sprintf "(ok-raw %s)" s)
else
(* Wrap as escaped string so newlines are preserved *)
send (Printf.sprintf "(ok \"%s\")" (escape_sx_string s))
(* ====================================================================== *)
(* IO bridge — primitives that yield to Python *)
@@ -806,7 +822,7 @@ let dispatch env cmd =
| RawHTML s -> "\"" ^ escape_sx_string s ^ "\""
| _ -> "nil"
in
send (Printf.sprintf "(ok-raw %s)" (raw_serialize result))
send_ok_raw (raw_serialize result)
with
| Eval_error msg -> send_error msg
| exn -> send_error (Printexc.to_string exn))
@@ -830,7 +846,7 @@ let dispatch env cmd =
(* Send raw SX wire format without re-escaping.
Use (ok-raw ...) so Python knows not to unescape. *)
(match result with
| String s | SxExpr s -> send (Printf.sprintf "(ok-raw %s)" s)
| String s | SxExpr s -> send_ok_raw s
| _ -> send_ok_value result)
with
| Eval_error msg -> send_error msg
@@ -891,7 +907,7 @@ let dispatch env cmd =
let t2 = Unix.gettimeofday () in
Printf.eprintf "[aser-slot] eval=%.1fs io_flush=%.1fs batched=%d result=%d chars\n%!"
(t1 -. t0) (t2 -. t1) n_batched (String.length final);
send (Printf.sprintf "(ok-raw %s)" final)
send_ok_raw final
with
| Eval_error msg ->
io_batch_mode := false;