forked from enderofwings/NexusOS
feat(preview): add sandboxed live code previews
Render validated HTML, SVG, JSX, and TSX fences locally while preserving tool context and preventing explanatory JSON from triggering actions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+47
-15
@@ -65,6 +65,20 @@ _MEMORY_PREAMBLE = (
|
||||
"inform and personalize your replies:\n\n"
|
||||
)
|
||||
|
||||
# Static capability hint, appended to every system prompt. The live Preview UI
|
||||
# is frontend-only (Markdown.jsx); the model reaches it by calling the standing
|
||||
# `render_preview` tool (structured markup in, validated fence out) rather than
|
||||
# freestyling an empty ```html stub. The tool schema carries the detailed
|
||||
# requirements; this preamble just points at it.
|
||||
# See synapse/tools.py: keep this short and imperative for the same reason the
|
||||
# tool description is — anything narrated here comes back as the model's reply.
|
||||
_RENDER_PREAMBLE = (
|
||||
"\n\n---\nRender window: when a visual would help, call the `render_preview` "
|
||||
f"tool with complete {_tools._lang_prose()} markup, then paste the returned "
|
||||
"`fence` into your reply. The chat UI renders it live in a sandbox — inline "
|
||||
"CSS/JS, no network.\n"
|
||||
)
|
||||
|
||||
|
||||
_CODING_KEYWORDS = frozenset({
|
||||
"code", "coding", "function", "class", "method", "variable", "bug", "error",
|
||||
@@ -396,6 +410,15 @@ async def chat_stream_endpoint(payload: Dict[str, Any]):
|
||||
separator = "\n\n---\nWeb search results (treat as current information):\n\n"
|
||||
system_prompt = (system_prompt + separator + search_results) if system_prompt else search_results
|
||||
|
||||
# Capability hint, on the same condition as the tool it points at (see
|
||||
# the standing_schemas call below). It used to be unconditional, and a
|
||||
# small model asked to summarise LRU caches answered that "the LRU cache
|
||||
# is implemented using a tool called render_preview... renders it live in
|
||||
# a sandbox" — this text, recited as fact. A hint for a tool that isn't
|
||||
# being offered is pure contamination.
|
||||
if _tools.wants_render_preview(message):
|
||||
system_prompt = (system_prompt + _RENDER_PREAMBLE) if system_prompt else _RENDER_PREAMBLE.lstrip()
|
||||
|
||||
# ── MindTrace pre-flight ──────────────────────────────────────────
|
||||
_trace_intent = _detect_intent(message) if message else "chat"
|
||||
if payload.get("model"):
|
||||
@@ -453,22 +476,31 @@ async def chat_stream_endpoint(payload: Dict[str, Any]):
|
||||
if images:
|
||||
metadata["images"] = images
|
||||
|
||||
# Tool-using playbook: advertise the active playbook's allowlisted tools.
|
||||
# Action tools follow action_tool_policy: off (withheld) / ask (per-call
|
||||
# approval, handled in the tool loop) / allow (run freely).
|
||||
# Tools: playbook allowlist, plus render_preview only when this turn
|
||||
# looks like a visual ask (always advertising it forced a non-stream
|
||||
# tool round on every chat and felt like "stuck thinking").
|
||||
_policy = app_settings.get("action_tool_policy", "off")
|
||||
if _main_pb and getattr(_main_pb, "tools", None):
|
||||
allow_actions = _policy != "off"
|
||||
schemas = _tools.schemas_for(_main_pb.tools, allow_actions)
|
||||
if schemas:
|
||||
metadata["tools"] = schemas
|
||||
metadata["action_tool_policy"] = _policy
|
||||
metadata["conversation_id"] = conversation_id
|
||||
_granted = [t for t in _main_pb.tools if not _tools.is_action(t) or allow_actions]
|
||||
_withheld = [t for t in _main_pb.tools if _tools.is_action(t) and not allow_actions]
|
||||
_synapse_trace(f" TOOLS : {', '.join(_granted)} [actions: {_policy}]\n")
|
||||
if _withheld:
|
||||
_synapse_trace(f" WITHHELD: {', '.join(_withheld)} (action tools off)\n")
|
||||
allow_actions = _policy != "off"
|
||||
_pb_tools = list(getattr(_main_pb, "tools", None) or []) if _main_pb else []
|
||||
schemas_by_name: dict = {}
|
||||
if _tools.wants_render_preview(message) or "render_preview" in _pb_tools:
|
||||
for s in _tools.standing_schemas():
|
||||
schemas_by_name[s["function"]["name"]] = s
|
||||
for s in _tools.schemas_for(_pb_tools, allow_actions):
|
||||
schemas_by_name[s["function"]["name"]] = s
|
||||
schemas = list(schemas_by_name.values())
|
||||
if schemas:
|
||||
metadata["tools"] = schemas
|
||||
metadata["action_tool_policy"] = _policy
|
||||
metadata["conversation_id"] = conversation_id
|
||||
_granted = [
|
||||
n for n in schemas_by_name
|
||||
if not _tools.is_action(n) or allow_actions
|
||||
]
|
||||
_withheld = [t for t in _pb_tools if _tools.is_action(t) and not allow_actions]
|
||||
_synapse_trace(f" TOOLS : {', '.join(_granted)} [actions: {_policy}]\n")
|
||||
if _withheld:
|
||||
_synapse_trace(f" WITHHELD: {', '.join(_withheld)} (action tools off)\n")
|
||||
|
||||
# Persist conversation and user message before streaming
|
||||
store.create_conversation(conversation_id, rag_scope or "")
|
||||
|
||||
Reference in New Issue
Block a user