diff --git a/backend/apps/agents/agent_manager.py b/backend/apps/agents/agent_manager.py
index 43a06031..d0aa654b 100644
--- a/backend/apps/agents/agent_manager.py
+++ b/backend/apps/agents/agent_manager.py
@@ -14,7 +14,6 @@ from backend.apps.agents.models import (
)
from backend.apps.agents.ws_manager import ws_manager
from backend.apps.modes.modes import load_mode
-from backend.apps.outputs.outputs import _load_all as load_all_outputs
from backend.apps.settings.settings import load_settings
from backend.apps.tools_lib.tools_lib import (
_load_all as load_all_tools,
@@ -188,7 +187,6 @@ FULL_TOOLS = [
"EnterPlanMode", "ExitPlanMode", "EnterWorktree",
"TaskOutput", "TaskStop",
"CronCreate", "CronList", "CronDelete",
- "RenderOutput",
"InvokeAgent",
"Agent",
# ToolSearch is the loader the CLI uses to expose deferred tool schemas
@@ -530,55 +528,6 @@ class AgentManager:
+ "\n"
)
- def _build_outputs_context(self, active_outputs: list[str] | None = None) -> str | None:
- """Outputs context for the system prompt.
-
- Two-mode emission gated by session.active_outputs:
- - Cheap one-line index for ALL Outputs (name + id + description)
- so the model can OutputSearch / OutputActivate against them.
- - FULL input_schema only for the ids in active_outputs. Defaults
- to empty: nothing ships full-schema until the model has
- explicitly activated the Output.
-
- This drops typical 30-Output context from ~30KB to ~2KB at
- steady state; an active Output adds ~1KB back per id.
- """
- import json as _json
- all_outputs = load_all_outputs()
- if not all_outputs:
- return None
-
- active_set = set(active_outputs or [])
- index_lines = []
- full_schemas: list[str] = []
- for out in all_outputs:
- desc = f" — {out.description}" if out.description else ""
- marker = " [active]" if out.id in active_set else ""
- index_lines.append(f"- `{out.id}` **{out.name}**{desc}{marker}")
- if out.id in active_set:
- schema_str = _json.dumps(out.input_schema, indent=2)
- full_schemas.append(
- f"### `{out.id}` ({out.name})\n```json\n{schema_str}\n```"
- )
-
- sections = [""]
- sections.append(
- "The following reusable View artifacts are available. The model "
- "must call OutputActivate(output_id) before RenderOutput so that "
- "the schema is in context — otherwise RenderOutput input_data may "
- "be malformed. Activated Outputs appear under "
- "below."
- )
- sections.append("")
- sections.extend(index_lines)
- sections.append("")
- if full_schemas:
- sections.append("")
- sections.append("")
- sections.extend(full_schemas)
- sections.append("")
- return "\n".join(sections)
-
def _build_browser_context(self, dashboard_id: str | None, selected_browser_ids: list[str] | None = None) -> str | None:
"""Build a context block listing browser cards and delegation instructions.
@@ -736,8 +685,8 @@ class AgentManager:
sections.append("")
return "\n".join(sections)
- def _compose_system_prompt(self, default_prompt: str | None, mode_prompt: str | None, session_prompt: str | None, connected_tools_ctx: str | None = None, outputs_ctx: str | None = None, browser_ctx: str | None = None, mcp_registry_ctx: str | None = None) -> str | None:
- parts = [p for p in (default_prompt, mode_prompt, session_prompt, connected_tools_ctx, mcp_registry_ctx, outputs_ctx, browser_ctx) if p]
+ def _compose_system_prompt(self, default_prompt: str | None, mode_prompt: str | None, session_prompt: str | None, connected_tools_ctx: str | None = None, browser_ctx: str | None = None, mcp_registry_ctx: str | None = None) -> str | None:
+ parts = [p for p in (default_prompt, mode_prompt, session_prompt, connected_tools_ctx, mcp_registry_ctx, browser_ctx) if p]
return "\n\n".join(parts) if parts else None
async def launch_agent(self, config: AgentConfig) -> AgentSession:
@@ -963,7 +912,7 @@ class AgentManager:
# - compact_threshold_pct (default 0.65): summarize stale tool_results
# and old user/assistant pairs before the next query() call
# - context_soft_cap_pct (default 0.90): pre-send hard guard. After
- # compaction, if still over, LRU-trim active_outputs/active_mcps
+ # compaction, if still over, LRU-trim active_mcps
# - >= 1.0 hits the proxy/Anthropic 200K ceiling — friendly card
# surfaces from the catch-all
# ------------------------------------------------------------------
@@ -1411,9 +1360,9 @@ class AgentManager:
sub_session_id = uuid4().hex
sub_name = agent_prompt[:50] if agent_prompt else "Sub-agent"
# Subagent context isolation invariant (Phase 3, Layer P):
- # children DO NOT inherit the parent's active_mcps,
- # active_outputs, or compaction state. They start with the
- # AgentSession defaults (empty lists). Reasoning:
+ # children DO NOT inherit the parent's active_mcps or
+ # compaction state. They start with the AgentSession
+ # defaults (empty lists). Reasoning:
# - Security: a parent that activated Gmail shouldn't
# leak Gmail tools to a subagent doing an unrelated
# task. The user only approved Gmail for the parent.
@@ -1441,11 +1390,10 @@ class AgentManager:
],
dashboard_id=session.dashboard_id,
parent_session_id=session_id,
- # Explicit empty lists (matches the model defaults) so
+ # Explicit empty list (matches the model default) so
# the invariant is visible at the spawn site rather
# than relying on the field's default_factory.
active_mcps=[],
- active_outputs=[],
)
self.sessions[sub_session_id] = sub_session
await ws_manager.broadcast_global("agent:status", {
@@ -1496,7 +1444,6 @@ class AgentManager:
# tool-call layer instead — prompt rules are not a security
# boundary.
connected_tools_ctx = None
- outputs_ctx = self._build_outputs_context(session.active_outputs)
browser_ctx = self._build_browser_context(session.dashboard_id, selected_browser_ids=selected_browser_ids)
# Reconcile active_mcps against currently-enabled tools (Phase 3).
@@ -1530,7 +1477,6 @@ class AgentManager:
mode_sys_prompt,
session.system_prompt,
connected_tools_ctx,
- outputs_ctx,
browser_ctx,
mcp_registry_ctx,
)
@@ -1630,24 +1576,6 @@ class AgentManager:
"type": "stdio",
}
- # Outputs/Views activation gate (Phase 2). Same shape as the
- # MCP meta-server but for Outputs. The model only sees a
- # one-line index of available Outputs in the system prompt
- # (see _build_outputs_context); to load any specific
- # Output's full input_schema, it must call OutputActivate.
- outputs_meta_server_path = os.path.join(
- os.path.dirname(__file__), "outputs_meta_server.py"
- )
- mcp_servers["openswarm-outputs-meta"] = {
- "command": sys.executable,
- "args": [outputs_meta_server_path],
- "env": {
- "OPENSWARM_PORT": os.environ.get("OPENSWARM_PORT", "8324"),
- "OPENSWARM_AUTH_TOKEN": _get_auth_token3(),
- "OPENSWARM_PARENT_SESSION_ID": session.id,
- },
- "type": "stdio",
- }
# The CLI's built-in WebSearch/WebFetch wraps Anthropic's
# web_search_20250305. For non-Claude primaries the CLI
@@ -2260,8 +2188,8 @@ class AgentManager:
# Pre-send hard guard (Phase 2). After compaction, if the
# session is still over context_soft_cap_pct of the window,
- # LRU-trim oldest active_outputs then active_mcps. Stops the
- # 429 from ever firing on predictable overflow paths.
+ # LRU-trim oldest active_mcps. Stops the 429 from ever
+ # firing on predictable overflow paths.
try:
# Use the most recent measurement (the prior turn's
# input_tokens) as the estimate. Conservative because the
@@ -2272,9 +2200,6 @@ class AgentManager:
_hard_cap = int(session.context_window * session.context_soft_cap_pct)
if _est_tokens >= _hard_cap:
trimmed: list[str] = []
- while _est_tokens >= _hard_cap and session.active_outputs:
- trimmed.append(f"output:{session.active_outputs.pop(0)}")
- _est_tokens -= 5_000 # rough per-Output schema cost
while _est_tokens >= _hard_cap and len(session.active_mcps) > 1:
# Keep at least one MCP active so the model can
# finish whatever it was doing; trim from oldest
diff --git a/backend/apps/agents/models.py b/backend/apps/agents/models.py
index 8aee6b84..3c6790b8 100644
--- a/backend/apps/agents/models.py
+++ b/backend/apps/agents/models.py
@@ -155,12 +155,6 @@ class AgentSession(BaseModel):
# filter lives at the dispatch layer (mcp_servers passed to the SDK),
# not the prompt layer.
active_mcps: list[str] = Field(default_factory=list)
- # Output ids the model has activated this session via the
- # OutputActivate meta-tool. Empty by default — _build_outputs_context
- # only emits the cheap one-line index for unactivated outputs; full
- # input_schema is shipped only for the ids in this list. Same gate
- # pattern as active_mcps but for the Outputs/Views surface.
- active_outputs: list[str] = Field(default_factory=list)
# Estimated framework preamble tokens (preset + tool defs + MCP descs +
# composed prompt). Subtracted from displayed input for honest "this turn"
# numbers. Heuristic; clamped >= 0.
@@ -177,9 +171,8 @@ class AgentSession(BaseModel):
# Pre-send hard guard. Fires later than the compaction threshold —
# 0.90 of 200K = 180K — to give the auto-compact path a chance to
# bring the request back under the ceiling. If still over after
- # compaction, LRU-trim the oldest active_outputs / active_mcps. Past
- # this we surface the friendly context-overflow card instead of
- # letting a 429 hit.
+ # compaction, LRU-trim the oldest active_mcps. Past this we surface
+ # the friendly context-overflow card instead of letting a 429 hit.
context_soft_cap_pct: float = 0.90
context_window: int = 200_000
# How much the model should "think" before answering. Provider-agnostic
diff --git a/backend/apps/agents/outputs_meta_server.py b/backend/apps/agents/outputs_meta_server.py
deleted file mode 100644
index a256721d..00000000
--- a/backend/apps/agents/outputs_meta_server.py
+++ /dev/null
@@ -1,250 +0,0 @@
-#!/usr/bin/env python3
-"""Stdio MCP server exposing the Outputs (Views) activation gate.
-
-Same shape as mcp_meta_server.py but for the Outputs surface. The model
-sees a one-line index of all available Outputs in the system prompt; to
-get the full input_schema for a specific Output (so it can call
-RenderOutput correctly), it must call OutputActivate first. The full
-schema is then injected on the next turn.
-
-Tools:
- - OutputList: enumerate all Outputs (active + available).
- - OutputSearch(query): rank by name/description match + use_count.
- - OutputActivate(output_id): pin the Output's schema into context.
-
-Same security/anti-hallucination guarantees as mcp_meta_server: input
-validated against the canonical store, unknown ids return the valid
-options instead of activating.
-"""
-
-import json
-import os
-import sys
-import urllib.error
-import urllib.request
-
-BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8324")
-BACKEND_AUTH = os.environ.get("OPENSWARM_AUTH_TOKEN", "")
-BACKEND_URL = f"http://127.0.0.1:{BACKEND_PORT}/api/outputs-meta"
-PARENT_SESSION_ID = os.environ.get("OPENSWARM_PARENT_SESSION_ID", "")
-
-
-TOOLS = [
- {
- "name": "OutputList",
- "description": (
- "List all reusable View artifacts (Outputs) installed on this "
- "machine. Returns one entry per Output with id, name, "
- "description, and activation status. The full input_schema "
- "is NOT included — call OutputActivate to load it. Use this "
- "for a broad survey before picking one."
- ),
- "inputSchema": {"type": "object", "properties": {}, "additionalProperties": False},
- },
- {
- "name": "OutputSearch",
- "description": (
- "Find Outputs relevant to a query. Ranks by name/description "
- "match plus recent-use frequency. Returns the top matches "
- "without their schemas. Call OutputActivate after picking one."
- ),
- "inputSchema": {
- "type": "object",
- "properties": {
- "query": {
- "type": "string",
- "description": "Free-form description of what you want to render (e.g. 'inbox dashboard', 'sales chart').",
- },
- },
- "required": ["query"],
- "additionalProperties": False,
- },
- },
- {
- "name": "OutputActivate",
- "description": (
- "Activate an Output for this session — pins its full input_schema "
- "into context starting next turn so RenderOutput can validate "
- "the input_data shape. Validate the id by calling OutputList or "
- "OutputSearch first; invalid ids return the valid options "
- "instead of activating."
- ),
- "inputSchema": {
- "type": "object",
- "properties": {
- "output_id": {
- "type": "string",
- "description": "Output id as returned by OutputList/OutputSearch.",
- },
- "reason": {
- "type": "string",
- "description": "One-sentence explanation of why this Output is needed for the user's task.",
- },
- },
- "required": ["output_id"],
- "additionalProperties": False,
- },
- },
-]
-
-
-def send_response(id_, result=None, error=None):
- msg = {"jsonrpc": "2.0", "id": id_}
- if error is not None:
- msg["error"] = error
- else:
- msg["result"] = result
- sys.stdout.write(json.dumps(msg) + "\n")
- sys.stdout.flush()
-
-
-def call_backend(action: str, payload: dict) -> dict:
- full = {**payload, "parent_session_id": PARENT_SESSION_ID}
- body = json.dumps(full).encode()
- headers = {"Content-Type": "application/json"}
- if BACKEND_AUTH:
- headers["Authorization"] = f"Bearer {BACKEND_AUTH}"
- req = urllib.request.Request(
- f"{BACKEND_URL}/{action}",
- data=body,
- headers=headers,
- method="POST",
- )
- try:
- with urllib.request.urlopen(req, timeout=60) as resp:
- return json.loads(resp.read().decode())
- except urllib.error.HTTPError as e:
- body = e.read().decode() if e.fp else str(e)
- return {"error": f"HTTP {e.code}: {body}"}
- except Exception as e:
- return {"error": str(e)}
-
-
-def format_outputs(outputs: list[dict], heading: str = "") -> str:
- if not outputs:
- return ""
- lines = []
- if heading:
- lines.append(heading)
- for o in outputs:
- oid = o.get("id", "")
- name = o.get("name", "")
- desc = o.get("description", "") or "no description"
- status = o.get("status", "available")
- used = o.get("use_count", 0)
- used_hint = f" (used {used}×)" if used else ""
- lines.append(f"- `{oid}` **{name}** [{status}]{used_hint} — {desc}")
- return "\n".join(lines)
-
-
-def handle_tool_call(tool_name: str, arguments: dict) -> dict:
- if tool_name == "OutputList":
- result = call_backend("list", {})
- if "error" in result:
- return {"content": [{"type": "text", "text": f"Error: {result['error']}"}], "isError": True}
- active = result.get("active", [])
- available = result.get("available", [])
- if not active and not available:
- return {"content": [{"type": "text", "text": "No Outputs / Views are defined yet. Use the App Builder mode to create one."}]}
- parts = []
- if active:
- parts.append(format_outputs(active, "Active (full schema in context, RenderOutput can use these now):"))
- if available:
- parts.append(format_outputs(available, "Available (call OutputActivate to load schema):"))
- return {"content": [{"type": "text", "text": "\n\n".join(parts)}]}
-
- if tool_name == "OutputSearch":
- query = arguments.get("query", "")
- if not query:
- return {"content": [{"type": "text", "text": "Error: query is required"}], "isError": True}
- result = call_backend("search", {"query": query})
- if "error" in result:
- return {"content": [{"type": "text", "text": f"Error: {result['error']}"}], "isError": True}
- matches = result.get("matches", [])
- if not matches:
- return {"content": [{"type": "text", "text": f"No Outputs matched '{query}'. Try OutputList to see everything available."}]}
- body = format_outputs(matches, f"Top matches for '{query}':")
- body += "\n\nNext step: call OutputActivate(output_id) to pin the schema."
- return {"content": [{"type": "text", "text": body}]}
-
- if tool_name == "OutputActivate":
- output_id = arguments.get("output_id", "")
- reason = arguments.get("reason", "")
- if not output_id:
- return {"content": [{"type": "text", "text": "Error: output_id is required"}], "isError": True}
- result = call_backend("activate", {"output_id": output_id, "reason": reason})
- if "error" in result:
- return {"content": [{"type": "text", "text": f"Error: {result['error']}"}], "isError": True}
- if result.get("status") == "unknown_output":
- available = result.get("available", [])
- return {
- "content": [{
- "type": "text",
- "text": (
- f"Unknown Output id '{output_id}'. Valid options: "
- + ", ".join(f"`{o}`" for o in available)
- + ". Call OutputList for full descriptions."
- ),
- }],
- "isError": True,
- }
- if result.get("status") == "already_active":
- return {"content": [{"type": "text", "text": f"`{output_id}` is already active for this session — its schema is in context now, RenderOutput can use it."}]}
- if result.get("status") == "activated":
- return {
- "content": [{
- "type": "text",
- "text": (
- f"Activated Output `{output_id}`. Its full input_schema "
- f"will be in context on the NEXT turn. End this turn now "
- f"and call RenderOutput with the activated id."
- ),
- }],
- }
- return {"content": [{"type": "text", "text": f"Unexpected response: {json.dumps(result)}"}], "isError": True}
-
- return {"content": [{"type": "text", "text": f"Unknown tool: {tool_name}"}], "isError": True}
-
-
-def main():
- for line in sys.stdin:
- line = line.strip()
- if not line:
- continue
- try:
- msg = json.loads(line)
- except json.JSONDecodeError:
- continue
-
- method = msg.get("method")
- id_ = msg.get("id")
- params = msg.get("params", {})
-
- if method == "initialize":
- send_response(id_, {
- "protocolVersion": "2024-11-05",
- "capabilities": {"tools": {}},
- "serverInfo": {"name": "openswarm-outputs-meta", "version": "1.0.0"},
- })
- elif method == "notifications/initialized":
- pass
- elif method == "tools/list":
- send_response(id_, {"tools": TOOLS})
- elif method == "tools/call":
- tool_name = params.get("name", "")
- arguments = params.get("arguments", {})
- try:
- result = handle_tool_call(tool_name, arguments)
- send_response(id_, result)
- except Exception as e:
- send_response(id_, error={"code": -32000, "message": str(e)})
- elif method == "resources/list":
- send_response(id_, {"resources": []})
- elif method == "prompts/list":
- send_response(id_, {"prompts": []})
- elif id_ is not None:
- send_response(id_, error={"code": -32601, "message": f"Method not found: {method}"})
-
-
-if __name__ == "__main__":
- main()
diff --git a/backend/apps/outputs/models.py b/backend/apps/outputs/models.py
index c54d8d56..540a3553 100644
--- a/backend/apps/outputs/models.py
+++ b/backend/apps/outputs/models.py
@@ -4,15 +4,6 @@ from uuid import uuid4
from datetime import datetime
-class AutoRunConfig(BaseModel):
- enabled: bool = False
- prompt: str = ""
- context_paths: list[dict[str, str]] = Field(default_factory=list)
- forced_tools: list[dict[str, Any]] = Field(default_factory=list)
- mode: str = "agent"
- model: str = "sonnet"
-
-
class Output(BaseModel):
id: str = Field(default_factory=lambda: uuid4().hex)
name: str
@@ -25,7 +16,6 @@ class Output(BaseModel):
})
files: dict[str, str] = Field(default_factory=dict)
permission: str = "ask"
- auto_run_config: Optional[AutoRunConfig] = None
thumbnail: Optional[str] = None
# Linkage so reopening the App Builder reattaches to the in-progress session
# and reuses the same on-disk workspace folder instead of seeding a fresh one
@@ -34,11 +24,6 @@ class Output(BaseModel):
workspace_id: Optional[str] = None
created_at: str = Field(default_factory=lambda: datetime.now().isoformat())
updated_at: str = Field(default_factory=lambda: datetime.now().isoformat())
- # Usage stats: bumped by RenderOutput dispatch + OutputActivate. Drives
- # ranking in OutputSearch so frequently-used Outputs surface first.
- # Both default to absent for backward compat with old on-disk records.
- last_used_at: Optional[str] = None
- use_count: int = 0
@model_validator(mode="before")
@classmethod
@@ -79,7 +64,6 @@ class OutputCreate(BaseModel):
"required": [],
})
files: dict[str, str] = Field(default_factory=dict)
- auto_run_config: Optional[dict[str, Any]] = None
thumbnail: Optional[str] = None
session_id: Optional[str] = None
workspace_id: Optional[str] = None
@@ -111,7 +95,6 @@ class OutputUpdate(BaseModel):
input_schema: Optional[dict[str, Any]] = None
files: Optional[dict[str, str]] = None
permission: Optional[str] = None
- auto_run_config: Optional[dict[str, Any]] = None
thumbnail: Optional[str] = None
session_id: Optional[str] = None
workspace_id: Optional[str] = None
@@ -153,29 +136,6 @@ class OutputExecuteResult(BaseModel):
error: Optional[str] = None
-class AutoRunRequest(BaseModel):
- # extra="ignore" so callers that historically sent `backend_code` (the
- # field was removed for security — see auto_run_output endpoint) don't
- # 422 on the way in. The endpoint silently drops it now; backend code
- # only runs via the persisted-Output flow at /api/outputs/execute.
- model_config = {"extra": "ignore"}
-
- prompt: str
- input_schema: dict[str, Any] = Field(default_factory=dict)
- context_paths: list[dict[str, str]] = Field(default_factory=list)
- forced_tools: list[str] = Field(default_factory=list)
- model: str = "sonnet"
-
-
-class AutoRunAgentRequest(BaseModel):
- prompt: str
- input_schema: dict[str, Any] = Field(default_factory=dict)
- output_id: str
- model: str = "sonnet"
- forced_tools: list[str] = Field(default_factory=list)
- context_paths: list[dict[str, str]] = Field(default_factory=list)
-
-
class WorkspaceSeedRequest(BaseModel):
workspace_id: str
files: Optional[dict[str, str]] = None
diff --git a/backend/apps/outputs/outputs.py b/backend/apps/outputs/outputs.py
index a72878fe..640da094 100644
--- a/backend/apps/outputs/outputs.py
+++ b/backend/apps/outputs/outputs.py
@@ -13,8 +13,7 @@ from jsonschema import validate as schema_validate, ValidationError as SchemaVal
from backend.config.Apps import SubApp
from backend.apps.outputs.models import (
Output, OutputCreate, OutputUpdate, OutputExecute, OutputExecuteResult,
- VibeCodeRequest, AutoRunRequest, AutoRunConfig, AutoRunAgentRequest,
- WorkspaceSeedRequest,
+ VibeCodeRequest, WorkspaceSeedRequest,
)
from backend.apps.outputs.executor import execute_backend_code
from backend.apps.outputs.view_builder_templates import VIEW_BUILDER_SKILL, VIEW_TEMPLATE_FILES
@@ -367,7 +366,6 @@ async def create_output(body: OutputCreate):
icon=body.icon,
input_schema=body.input_schema,
files=body.files,
- auto_run_config=body.auto_run_config,
thumbnail=body.thumbnail,
created_at=now,
updated_at=now,
@@ -381,8 +379,6 @@ async def create_output(body: OutputCreate):
async def update_output(output_id: str, body: OutputUpdate):
output = _load(output_id)
for k, v in body.model_dump(exclude_none=True).items():
- if k == "auto_run_config" and isinstance(v, dict):
- v = AutoRunConfig(**v)
setattr(output, k, v)
output.updated_at = datetime.now().isoformat()
_save(output)
@@ -503,88 +499,6 @@ async def vibe_code(body: VibeCodeRequest):
}
-AUTO_RUN_SYSTEM_PROMPT = """\
-You generate structured JSON data matching a given schema.
-The user provides a prompt describing what data to generate and a JSON Schema.
-Return ONLY valid JSON that conforms to the schema. No markdown fences, no extra text, no explanation.
-Every required field must be present. Use realistic, meaningful data.\
-"""
-
-
-@outputs.router.post("/auto-run")
-async def auto_run_output(body: AutoRunRequest):
- """Use an LLM to generate input data matching the schema, then optionally execute backend code."""
- try:
- import anthropic
- except ImportError:
- return {"error": "anthropic SDK not installed", "input_data": None, "backend_result": None}
-
- schema_str = json.dumps(body.input_schema, indent=2)
- user_message = f"Schema:\n```json\n{schema_str}\n```\n\nGenerate data for: {body.prompt}"
-
- # Resolve body.model via the registry so non-Anthropic selections are
- # routed through 9Router with the correct prefix (cx/, gc/).
- # If body.model is unset or unknown, fall back to whichever aux model
- # is available (prefers Claude, else any connected subscription).
- from backend.apps.agents.providers.registry import (
- _find_builtin_model,
- resolve_model_id_for_sdk,
- resolve_aux_model,
- )
- settings = load_settings()
- if body.model and _find_builtin_model(body.model) is not None:
- api_model = resolve_model_id_for_sdk(body.model, settings)
- else:
- try:
- api_model, _ = await resolve_aux_model(settings, preferred_tier="haiku")
- except ValueError as e:
- return {"error": str(e), "input_data": None, "backend_result": None}
-
- client = _get_anthropic_client(api_model)
- try:
- resp = await client.messages.create(
- model=api_model,
- max_tokens=4000,
- system=AUTO_RUN_SYSTEM_PROMPT,
- messages=[{"role": "user", "content": user_message}],
- )
- from backend.apps.agents.agent_manager import _safe_resp_text
- raw = _safe_resp_text(resp).strip()
- if not raw:
- return {"error": "Aux model returned no content.", "input_data": None, "backend_result": None}
- if raw.startswith("```"):
- raw = raw.split("\n", 1)[1] if "\n" in raw else raw[3:]
- if raw.endswith("```"):
- raw = raw[:-3]
-
- input_data = json.loads(raw)
-
- validation_err = _validate_against_schema(input_data, body.input_schema)
- if validation_err:
- return {"input_data": input_data, "backend_result": None, "error": validation_err}
-
- # SECURITY: this endpoint used to accept arbitrary `backend_code` in
- # the request body and pass it straight to execute_backend_code —
- # which is an unsandboxed `python -c` subprocess. That gave anyone
- # holding the install token (which is readable by every process
- # running as the same OS user, and is also handed to every agent
- # subprocess via OPENSWARM_AUTH_TOKEN) a one-shot RCE primitive.
- # The field is now ignored at the model layer; backend code can
- # only run via /api/outputs/execute against a persisted Output.
- return {
- "input_data": input_data,
- "backend_result": None,
- "stdout": None,
- "stderr": None,
- "error": None,
- }
- except json.JSONDecodeError:
- return {"error": "Failed to parse generated data as JSON", "input_data": None, "backend_result": None}
- except Exception as e:
- logger.exception("Auto-run failed")
- return {"error": str(e), "input_data": None, "backend_result": None}
-
-
@outputs.router.post("/execute")
async def execute_output(body: OutputExecute):
output = _load(body.output_id)
@@ -627,72 +541,3 @@ async def execute_output(body: OutputExecute):
).model_dump()
-AUTO_RUN_AGENT_SYSTEM_PROMPT = """\
-You are a data-gathering agent. Your job is to use the available tools to collect \
-real data, then render it into a structured View.
-
-You have access to MCP tools (e.g. Gmail, calendar, etc.) that let you fetch live data. \
-Use them as needed to fulfil the user's request.
-
-When you have gathered enough data, call the **RenderOutput** tool with:
-- `output_id`: `{output_id}`
-- `input_data`: a JSON object conforming to this schema:
-```json
-{schema}
-```
-
-Do NOT fabricate data. Use the tools to get real information, then structure it to match \
-the schema above. If a tool call fails, report the error clearly.\
-"""
-
-
-@outputs.router.post("/auto-run-agent")
-async def auto_run_agent(body: AutoRunAgentRequest):
- """Launch a temporary agent session that uses MCP tools to gather data for a view."""
- from backend.apps.agents.agent_manager import agent_manager, FULL_TOOLS
- from backend.apps.agents.models import AgentConfig
-
- output = _load(body.output_id)
- schema_str = json.dumps(body.input_schema or output.input_schema, indent=2)
-
- system_prompt = AUTO_RUN_AGENT_SYSTEM_PROMPT.format(
- output_id=body.output_id,
- schema=schema_str,
- )
-
- allowed_tools = list(FULL_TOOLS)
- for tool_name in body.forced_tools:
- if tool_name not in allowed_tools:
- allowed_tools.append(tool_name)
-
- config = AgentConfig(
- name=f"AutoRun: {output.name}",
- model=body.model,
- mode="agent",
- system_prompt=system_prompt,
- allowed_tools=allowed_tools,
- max_turns=20,
- )
-
- session = await agent_manager.launch_agent(config)
-
- await agent_manager.send_message(
- session.id,
- body.prompt,
- context_paths=body.context_paths if body.context_paths else None,
- forced_tools=body.forced_tools if body.forced_tools else None,
- )
-
- return {"session_id": session.id}
-
-
-@outputs.router.delete("/auto-run-agent/{session_id}")
-async def cleanup_auto_run_agent(session_id: str):
- """Delete a temporary auto-run agent session."""
- from backend.apps.agents.agent_manager import agent_manager
-
- try:
- await agent_manager.delete_session(session_id)
- except Exception as e:
- logger.warning(f"Auto-run agent cleanup failed for {session_id}: {e}")
- return {"ok": True}
diff --git a/backend/apps/tools_lib/models.py b/backend/apps/tools_lib/models.py
index 0f2f66ad..f4118e65 100644
--- a/backend/apps/tools_lib/models.py
+++ b/backend/apps/tools_lib/models.py
@@ -33,7 +33,6 @@ BUILTIN_TOOLS: list[BuiltinTool] = [
BuiltinTool(name="CronCreate", description="Create a scheduled or recurring task", category="scheduling", deferred=True),
BuiltinTool(name="CronList", description="List all scheduled tasks", category="scheduling", deferred=True),
BuiltinTool(name="CronDelete", description="Delete a scheduled task", category="scheduling", deferred=True),
- BuiltinTool(name="RenderOutput", description="Render a reusable View artifact with structured input data", category="views", deferred=True),
# Agent tools
BuiltinTool(name="Agent", display_name="CreateAgent", description="Spawn a sub-agent to handle a complex subtask", category="agents"),
BuiltinTool(name="InvokeAgent", description="Invoke a copy of an existing agent with a new message, preserving full conversation context", category="agents"),
diff --git a/backend/main.py b/backend/main.py
index bc536a8f..3f508455 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -608,7 +608,7 @@ async def session_clear(session_id: str):
Preserves session.messages (so the chat UI keeps the visible history)
but clears the SDK-side conversation by minting a new sdk_session_id.
- Also drops active_mcps/active_outputs so the user starts fresh.
+ Also drops active_mcps so the user starts fresh.
"""
from backend.apps.agents.agent_manager import agent_manager
from backend.apps.agents.ws_manager import ws_manager as _ws
@@ -617,7 +617,6 @@ async def session_clear(session_id: str):
return JSONResponse({"error": "session not found"}, status_code=404)
session.sdk_session_id = None
session.active_mcps = []
- session.active_outputs = []
session.compacted_through_msg_id = None
session.tokens = {"input": 0, "output": 0}
session.cost_usd = 0.0
@@ -634,115 +633,6 @@ async def session_clear(session_id: str):
return JSONResponse({"cleared": True})
-@app.post("/api/outputs-meta/{action}")
-async def outputs_meta(action: str, request: Request):
- """Back the openswarm-outputs-meta stdio MCP server.
-
- Mirrors mcp_meta but for Outputs/Views: list/search/activate against
- the canonical Output store. Anti-hallucination: unknown ids return
- the valid options instead of activating. Schema spilled into context
- on next turn via session.active_outputs (consumed by
- _build_outputs_context in agent_manager).
- """
- from datetime import datetime as _dt
- from backend.apps.agents.agent_manager import agent_manager
- from backend.apps.outputs.outputs import _load_all as load_all_outputs, _save as save_output
-
- body = await request.json()
- parent_session_id = body.get("parent_session_id", "")
-
- def _all_outputs_ranked() -> list[dict]:
- items = []
- for o in load_all_outputs():
- items.append({
- "id": o.id,
- "name": o.name,
- "description": (o.description or "").strip(),
- "use_count": int(getattr(o, "use_count", 0) or 0),
- "last_used_at": getattr(o, "last_used_at", None),
- })
- # Sort: most-used first, then most-recent, then alphabetical.
- items.sort(key=lambda i: (-(i["use_count"] or 0), -(len(i["last_used_at"] or "")), i["name"].lower()))
- return items
-
- if action == "list":
- outputs = _all_outputs_ranked()
- session = agent_manager.sessions.get(parent_session_id) if parent_session_id else None
- active_set = set(session.active_outputs) if session else set()
- active = [{**o, "status": "active"} for o in outputs if o["id"] in active_set]
- available = [{**o, "status": "available"} for o in outputs if o["id"] not in active_set]
- return JSONResponse({"active": active, "available": available})
-
- if action == "search":
- query = (body.get("query") or "").strip().lower()
- if not query:
- return JSONResponse({"matches": []})
- outputs = _all_outputs_ranked()
- session = agent_manager.sessions.get(parent_session_id) if parent_session_id else None
- active_set = set(session.active_outputs) if session else set()
- scored: list[tuple[int, dict]] = []
- for o in outputs:
- hay = f"{o['name']} {o['description']}".lower()
- score = 0
- for tok in query.split():
- if tok and tok in hay:
- score += 2 if tok in o["name"].lower() else 1
- # Use-count bonus so frequently-rendered Outputs surface for
- # generic queries like "dashboard" / "chart".
- if score:
- score += min(3, (o["use_count"] or 0) // 5)
- annotated = {**o, "status": "active" if o["id"] in active_set else "available"}
- scored.append((score, annotated))
- scored.sort(key=lambda t: (-t[0], 0 if t[1]["status"] == "active" else 1, t[1]["name"]))
- matches = [s for _, s in scored[:5]]
- return JSONResponse({"matches": matches})
-
- if action == "activate":
- output_id = (body.get("output_id") or "").strip()
- reason = body.get("reason") or ""
- if not output_id:
- return JSONResponse({"error": "output_id is required"}, status_code=400)
- if not parent_session_id:
- return JSONResponse({"error": "parent_session_id is required"}, status_code=400)
- session = agent_manager.sessions.get(parent_session_id)
- if not session:
- return JSONResponse({"error": "session not found"}, status_code=404)
-
- all_outputs = load_all_outputs()
- match = next((o for o in all_outputs if o.id == output_id), None)
- if not match:
- return JSONResponse({"status": "unknown_output", "available": [o.id for o in all_outputs]})
-
- if output_id in session.active_outputs:
- return JSONResponse({"status": "already_active", "output_id": output_id})
-
- session.active_outputs.append(output_id)
- session.needs_fork = True
- # Bump usage stats. Activation is a pretty strong "model intends
- # to use this" signal — even if RenderOutput isn't called, the
- # Output is meaningfully in scope. last_used_at lets ranking
- # surface "stuff I touched recently" to the model later.
- try:
- match.use_count = int(getattr(match, "use_count", 0) or 0) + 1
- match.last_used_at = _dt.now().isoformat()
- save_output(match)
- except Exception:
- logger.exception("Failed to bump Output usage stats")
- try:
- from backend.apps.agents.ws_manager import ws_manager as _ws
- await _ws.send_to_session(parent_session_id, "agent:status", {
- "session_id": parent_session_id,
- "status": session.status,
- "session": session.model_dump(mode="json"),
- })
- except Exception:
- logger.exception("Failed to broadcast post-activate session status")
- pass # Output activation captured via session dump on close
- return JSONResponse({"status": "activated", "output_id": output_id})
-
- return JSONResponse({"error": f"unknown action: {action}"}, status_code=400)
-
-
@app.post("/api/invoke-agent/run")
async def invoke_agent_run(request: Request):
"""Fork an existing agent session and send it a new message.
diff --git a/backend/tests/test_v2_invariants.py b/backend/tests/test_v2_invariants.py
index 8038ecea..6a0f9282 100644
--- a/backend/tests/test_v2_invariants.py
+++ b/backend/tests/test_v2_invariants.py
@@ -653,11 +653,9 @@ def test_active_mcps_persistence_on_session():
from backend.apps.agents.models import AgentSession
s = AgentSession(id="x", name="t", model="sonnet", mode="agent")
s.active_mcps = ["gmail", "slack"]
- s.active_outputs = ["view-1"]
dumped = json.dumps(s.model_dump(mode="json"))
rehydrated = AgentSession.model_validate(json.loads(dumped))
assert rehydrated.active_mcps == ["gmail", "slack"]
- assert rehydrated.active_outputs == ["view-1"]
# ===========================================================================
diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md
index e4c81bee..1442801a 100644
--- a/frontend/CLAUDE.md
+++ b/frontend/CLAUDE.md
@@ -12,7 +12,8 @@ React 18 + TypeScript + webpack 5 + Redux. Entry: `src/app/Main.tsx`. Dev server
- **Spatial dashboard** — agents are draggable nodes on a canvas; layout + selection state lives in Redux.
- **Settings draft persistence** — `AppSettings.dismissed_mcp_suggestions` is a map of MCP id → ISO timestamp; preserve this shape when modifying settings serialization.
-- **Onboarding wizard** (`src/app/pages/Onboarding/`) — 8-step agentic cursor walkthrough. Cursor offsets, fit-to-view, AC popup timing, and group-meta dedup were each delicate to land; verify visually after touching this code.
+- **Onboarding wizard** (`src/app/components/Onboarding/`) — 8-step agentic cursor walkthrough. Cursor offsets, fit-to-view, AC popup timing, and group-meta dedup were each delicate to land; verify visually after touching this code. Note: steps 3/5/6 launch real agent sessions that hit the cloud's analytics ingest — don't treat them as visual-only.
+- **SignInGate** (`src/app/components/SignInGate.tsx`, mounted in `Main.tsx`) — first-launch gate that captures `user_id` + email via Google OAuth or email magic link, hitting the cloud's `/api/auth/{google,email}/*`. Auto-dismisses for users with a valid bearer.
- **Custom providers** — `AppSettings.custom_providers: CustomProvider[]` supports any OpenAI-compatible endpoint (e.g. LM Studio).
## Conventions
diff --git a/frontend/src/app/components/CommandPicker.tsx b/frontend/src/app/components/CommandPicker.tsx
index d381db2b..04213763 100644
--- a/frontend/src/app/components/CommandPicker.tsx
+++ b/frontend/src/app/components/CommandPicker.tsx
@@ -16,7 +16,6 @@ import ViewQuiltOutlinedIcon from '@mui/icons-material/ViewQuiltOutlined';
import { useAppSelector, useAppDispatch } from '@/shared/hooks';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { fetchBuiltinTools, fetchTools } from '@/shared/state/toolsSlice';
-import { fetchOutputs } from '@/shared/state/outputsSlice';
import { fetchSkills } from '@/shared/state/skillsSlice';
const GoogleIcon: React.FC<{ sx?: object }> = ({ sx }) => (
@@ -95,21 +94,18 @@ const CommandPicker: React.FC = ({ trigger, filter, onSelect, onClose, vi
const modesMap = useAppSelector((s) => s.modes.items);
const builtinTools = useAppSelector((s) => s.tools.builtinTools);
const customTools = useAppSelector((s) => s.tools.items);
- const outputItems = useAppSelector((s) => s.outputs.items);
const [selectedIndex, setSelectedIndex] = useState(0);
const containerRef = useRef(null);
const toolsLoaded = useAppSelector((s) => s.tools.loaded);
const builtinLoaded = useAppSelector((s) => s.tools.builtinLoaded);
- const outputsLoaded = useAppSelector((s) => s.outputs.loaded);
const skillsLoaded = useAppSelector((s) => s.skills.loaded);
useEffect(() => {
if (!builtinLoaded) dispatch(fetchBuiltinTools());
if (!toolsLoaded) dispatch(fetchTools());
- if (!outputsLoaded) dispatch(fetchOutputs());
if (!skillsLoaded) dispatch(fetchSkills());
- }, [dispatch, builtinLoaded, toolsLoaded, outputsLoaded, skillsLoaded]);
+ }, [dispatch, builtinLoaded, toolsLoaded, skillsLoaded]);
const items: CommandPickerItem[] = useMemo(() => {
let all: CommandPickerItem[] = [];
@@ -252,22 +248,6 @@ const CommandPicker: React.FC = ({ trigger, filter, onSelect, onClose, vi
}
}
- for (const out of Object.values(outputItems)) {
- if (out.permission === 'deny') continue;
- const cmd = out.name.toLowerCase().replace(/\s+/g, '-');
- atItems.push({
- id: `view-${out.id}`,
- type: 'context' as const,
- category: 'Apps',
- name: out.name,
- description: out.description || `Render ${out.name} view`,
- command: cmd,
- icon: ,
- toolNames: ['RenderOutput'],
- iconKey: 'View',
- });
- }
-
all = atItems;
}
@@ -279,7 +259,7 @@ const CommandPicker: React.FC = ({ trigger, filter, onSelect, onClose, vi
item.command.toLowerCase().includes(lower) ||
item.description.toLowerCase().includes(lower),
);
- }, [trigger, skills, modesMap, builtinTools, customTools, outputItems, filter]);
+ }, [trigger, skills, modesMap, builtinTools, customTools, filter]);
const flatItems = useMemo(() => {
const result: { item: CommandPickerItem; isGroupStart: boolean; category: string }[] = [];
diff --git a/frontend/src/app/components/Onboarding/selectors.ts b/frontend/src/app/components/Onboarding/selectors.ts
index 659668d9..5aac73f7 100644
--- a/frontend/src/app/components/Onboarding/selectors.ts
+++ b/frontend/src/app/components/Onboarding/selectors.ts
@@ -71,7 +71,6 @@ export const S = {
// new — apps / views page
appsNewButton: 'apps-new-button',
- appBuilderInput: 'app-builder-input',
appBuilderSubmit: 'app-builder-submit',
appCardLatest: 'app-card-latest',
diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx
index d12b6d80..5b794940 100644
--- a/frontend/src/app/pages/AgentChat/AgentChat.tsx
+++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx
@@ -618,16 +618,6 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose
const sessionRunning = session?.status === 'running' || session?.status === 'waiting_approval';
const renderItems: RenderItem[] = useMemo(() => {
- const isOutputCall = (m: AgentMessage) =>
- m.role === 'tool_call' && typeof m.content === 'object' && m.content.tool === 'RenderOutput';
- const isOutputResult = (m: AgentMessage) => {
- if (m.role !== 'tool_result') return false;
- try {
- const parsed = typeof m.content === 'string' ? JSON.parse(m.content) : m.content;
- return !!(parsed?.output_id && parsed?.frontend_code);
- } catch { return false; }
- };
-
const items: RenderItem[] = [];
let i = 0;
while (i < activeBranchMessages.length) {
@@ -643,15 +633,8 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose
i++;
}
- const regular: typeof activeBranchMessages = [];
- const outputItems: typeof activeBranchMessages = [];
- for (const m of group) {
- if (isOutputCall(m) || isOutputResult(m)) { outputItems.push(m); continue; }
- regular.push(m);
- }
-
- const calls = regular.filter((m) => m.role === 'tool_call');
- const results = regular.filter((m) => m.role === 'tool_result');
+ const calls = group.filter((m) => m.role === 'tool_call');
+ const results = group.filter((m) => m.role === 'tool_result');
const pairs: ToolPair[] = calls.map((call, idx) => ({
type: 'tool_pair' as const,
id: `pair-${call.id}`,
@@ -699,8 +682,6 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose
callCount: calls.length,
} satisfies ToolGroup);
}
-
- items.push(...outputItems);
} else {
if (!msg.hidden) {
items.push(msg);
diff --git a/frontend/src/app/pages/AgentChat/ContextDrawer.tsx b/frontend/src/app/pages/AgentChat/ContextDrawer.tsx
index 993d4ff0..dec2cae3 100644
--- a/frontend/src/app/pages/AgentChat/ContextDrawer.tsx
+++ b/frontend/src/app/pages/AgentChat/ContextDrawer.tsx
@@ -83,12 +83,6 @@ export default function ContextDrawer() {
))}
-
- {((session as any).active_outputs || []).map((o: string) => (
-
- ))}
-
-
{session.compacted_through_msg_id
diff --git a/frontend/src/app/pages/AgentChat/MessageBubble.tsx b/frontend/src/app/pages/AgentChat/MessageBubble.tsx
index 50303124..47b49ce0 100644
--- a/frontend/src/app/pages/AgentChat/MessageBubble.tsx
+++ b/frontend/src/app/pages/AgentChat/MessageBubble.tsx
@@ -23,7 +23,6 @@ import { openSettingsModal } from '@/shared/state/settingsSlice';
import { useAppDispatch } from '@/shared/hooks';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { SKILL_COLOR } from '@/app/components/richEditorUtils';
-import ViewBubble from './ViewBubble';
import PlanPicker from '@/app/components/PlanPicker';
import { ErrorSlime } from '@/app/components/ErrorSlime';
@@ -842,26 +841,7 @@ const MessageBubble: React.FC = React.memo(({ message, editing = false, o
);
}
- if (role === 'tool_call') {
- const toolData = typeof content === 'object' ? content : {};
- const toolInput = toolData.input || {};
- if (toolData.tool === 'RenderOutput') {
- return ;
- }
- return null;
- }
-
- if (role === 'tool_result') {
- let parsedContent: any = null;
- try { parsedContent = typeof content === 'string' ? JSON.parse(content) : content; } catch {}
- if (parsedContent?.output_id && parsedContent?.frontend_code) {
- return (
-
- );
- }
+ if (role === 'tool_call' || role === 'tool_result') {
return null;
}
diff --git a/frontend/src/app/pages/AgentChat/ViewBubble.tsx b/frontend/src/app/pages/AgentChat/ViewBubble.tsx
deleted file mode 100644
index ec7b2062..00000000
--- a/frontend/src/app/pages/AgentChat/ViewBubble.tsx
+++ /dev/null
@@ -1,278 +0,0 @@
-import React, { useState, useMemo } from 'react';
-import Box from '@mui/material/Box';
-import Typography from '@mui/material/Typography';
-import IconButton from '@mui/material/IconButton';
-import Collapse from '@mui/material/Collapse';
-import Dialog from '@mui/material/Dialog';
-import DialogContent from '@mui/material/DialogContent';
-import Icon from '@mui/material/Icon';
-import OpenInFullIcon from '@mui/icons-material/OpenInFull';
-import CloseIcon from '@mui/icons-material/Close';
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
-import { useAppSelector } from '@/shared/hooks';
-import { SERVE_BASE } from '@/shared/state/outputsSlice';
-import { useClaudeTokens } from '@/shared/styles/ThemeContext';
-import ViewPreview from '../Views/ViewPreview';
-
-interface Props {
- toolInput: Record;
- toolResult?: string | Record;
- isStreaming?: boolean;
-}
-
-const ViewBubble: React.FC = ({ toolInput, toolResult, isStreaming }) => {
- const c = useClaudeTokens();
- const [expanded, setExpanded] = useState(false);
- const [showInputs, setShowInputs] = useState(false);
-
- const outputId = toolInput?.output_id;
- const inputData = toolInput?.input_data || {};
- const outputsMap = useAppSelector((state) => state.outputs.items);
- const output = outputId ? outputsMap[outputId] : null;
-
- const parsedResult = useMemo(() => {
- if (!toolResult) return null;
- if (typeof toolResult === 'object') return toolResult;
- try { return JSON.parse(toolResult as string); } catch { return null; }
- }, [toolResult]);
-
- const frontendCode = parsedResult?.frontend_code || (output?.files?.['index.html'] ?? '') || '';
- const backendResult = parsedResult?.backend_result || null;
- const outputName = parsedResult?.output_name || output?.name || 'App';
- const outputColor = c.accent.primary;
- const outputIcon = output?.icon || 'view_quilt';
- const hasPreview = !!frontendCode.trim();
- const serveUrl = outputId ? `${SERVE_BASE}/${outputId}/serve/index.html` : undefined;
- const inputEntries = Object.entries(inputData);
-
- if (isStreaming && !hasPreview) {
- return (
-
-
- {outputIcon}
-
- {outputName}
-
-
-
- Rendering…
-
-
-
- );
- }
-
- return (
- <>
-
-
- {/* Header */}
-
- {outputIcon}
-
- {outputName}
-
- {inputEntries.length > 0 && (
- setShowInputs(!showInputs)}
- sx={{
- color: c.text.tertiary,
- p: 0.5,
- transform: showInputs ? 'rotate(180deg)' : 'rotate(0deg)',
- transition: 'transform 0.2s ease',
- }}
- >
-
-
- )}
- {hasPreview && (
- setExpanded(true)}
- sx={{ color: c.text.tertiary, p: 0.5, '&:hover': { color: outputColor } }}
- >
-
-
- )}
-
-
- {/* Collapsible input params */}
-
-
- {inputEntries.map(([key, val]) => {
- const display = typeof val === 'string' ? val : JSON.stringify(val);
- return (
-
-
- {key}
-
-
- {display.length > 120 ? display.slice(0, 120) + '…' : display}
-
-
- );
- })}
-
-
-
- {/* Preview */}
- {hasPreview && (
-
-
-
- )}
-
- {parsedResult?.error && (
-
-
- {parsedResult.error}
-
-
- )}
-
-
-
- {/* Fullscreen dialog */}
-
- >
- );
-};
-
-export default ViewBubble;
diff --git a/frontend/src/app/pages/Dashboard/DashboardViewCard.tsx b/frontend/src/app/pages/Dashboard/DashboardViewCard.tsx
index 9bb843ce..c76fe958 100644
--- a/frontend/src/app/pages/Dashboard/DashboardViewCard.tsx
+++ b/frontend/src/app/pages/Dashboard/DashboardViewCard.tsx
@@ -3,12 +3,10 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
-import CircularProgress from '@mui/material/CircularProgress';
import RefreshIcon from '@mui/icons-material/Refresh';
-import BoltIcon from '@mui/icons-material/Bolt';
import CloseIcon from '@mui/icons-material/Close';
import GridViewRoundedIcon from '@mui/icons-material/GridViewRounded';
-import { Output, autoRunOutput, autoRunAgentOutput, executeOutput, OutputExecuteResult, getBackendCode, SERVE_BASE } from '@/shared/state/outputsSlice';
+import { Output, SERVE_BASE } from '@/shared/state/outputsSlice';
import { setViewCardPosition, setViewCardSize, removeViewCard } from '@/shared/state/dashboardLayoutSlice';
import { useAppDispatch } from '@/shared/hooks';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
@@ -71,11 +69,8 @@ const DashboardViewCard: React.FC = ({
const scrollOverlayRef = useOverlayScrollPassthrough(isSelected);
const previewRef = useRef(null);
- const [inputData, setInputData] = useState>(() => getDefault(output.input_schema));
- const [backendResult, setBackendResult] = useState | null>(null);
- const [autoRunning, setAutoRunning] = useState(false);
-
- const hasAutoRun = !!(output.auto_run_config?.enabled && output.auto_run_config?.prompt);
+ const [inputData] = useState>(() => getDefault(output.input_schema));
+ const [backendResult] = useState | null>(null);
// ---- Drag via header ----
const DRAG_THRESHOLD = 3;
@@ -235,70 +230,6 @@ const DashboardViewCard: React.FC = ({
previewRef.current?.reload();
};
- const handleAutoRun = async (e: React.MouseEvent) => {
- e.stopPropagation();
- if (!output.auto_run_config?.prompt) return;
- setAutoRunning(true);
-
- const config = output.auto_run_config;
- const forcedToolNames = config.forced_tools?.flatMap((ft) => ft.tools) ?? [];
-
- try {
- if (forcedToolNames.length > 0) {
- const res = await dispatch(autoRunAgentOutput({
- prompt: config.prompt,
- input_schema: output.input_schema,
- output_id: output.id,
- model: config.model,
- forced_tools: forcedToolNames,
- context_paths: config.context_paths,
- })).unwrap();
-
- // For agent-based auto-run, we execute with default input for now
- // since the agent session result flow is complex for dashboard cards
- const execRes = await dispatch(executeOutput({
- output_id: output.id,
- input_data: inputData,
- })).unwrap();
- setInputData(execRes.input_data);
- setBackendResult(execRes.backend_result);
- } else {
- const res = await dispatch(autoRunOutput({
- prompt: config.prompt,
- input_schema: output.input_schema,
- context_paths: config.context_paths,
- forced_tools: forcedToolNames.length > 0 ? forcedToolNames : undefined,
- model: config.model,
- })).unwrap();
- if (res.input_data) {
- setInputData(res.input_data);
- // Auto-run no longer executes backend.py inline (the server-side
- // endpoint dropped that field — it was a direct RCE primitive).
- // Chain a separate executeOutput against the persisted Output so
- // backend code still runs for dashboards that need backend_result.
- if (getBackendCode(output)) {
- try {
- const execRes = await dispatch(executeOutput({
- output_id: output.id,
- input_data: res.input_data,
- })).unwrap();
- setBackendResult(execRes.backend_result);
- } catch {
- // Backend execution failure shouldn't break the input render.
- setBackendResult(null);
- }
- } else {
- setBackendResult(res.backend_result);
- }
- }
- }
- } catch {
- // Silently handle errors on dashboard
- } finally {
- setAutoRunning(false);
- }
- };
-
const mdDx = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dx : 0;
const mdDy = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dy : 0;
const displayX = localResize?.x ?? localDragPos?.x ?? (cardX + mdDx);
@@ -437,22 +368,6 @@ const DashboardViewCard: React.FC = ({
- {hasAutoRun && (
-
-
- e.stopPropagation()}
- disabled={autoRunning}
- sx={{ color: '#f59e0b', p: 0.5, '&:hover': { color: '#d97706' } }}
- >
- {autoRunning ? : }
-
-
-
- )}
-
= {
- name: 'Jane Smith', first_name: 'Jane', last_name: 'Smith', firstName: 'Jane', lastName: 'Smith',
- email: 'jane@example.com', url: 'https://example.com', website: 'https://example.com',
- phone: '+1 (555) 123-4567', address: '123 Main St, Springfield',
- city: 'Springfield', state: 'CA', country: 'US', zip: '90210',
- title: 'Sample Title', subject: 'Hello World', message: 'This is a sample message.',
- description: 'A brief description of the item.', content: 'Lorem ipsum dolor sit amet.',
- username: 'janesmith', password: 'P@ssw0rd!', token: 'tok_sample_abc123',
- id: 'item_001', uuid: '550e8400-e29b-41d4-a716-446655440000',
- date: '2025-03-15', time: '14:30', datetime: '2025-03-15T14:30:00Z',
- color: '#4a90d9', label: 'Important', tag: 'sample', category: 'general',
- query: 'search term', search: 'example query', text: 'Sample text content',
- path: '/home/user/file.txt', file: 'document.pdf', filename: 'report.pdf',
- company: 'Acme Corp', organization: 'Acme Corp',
-};
-
-function stubString(key: string): string {
- const lower = key.toLowerCase().replace(/[-_]/g, '');
- for (const [pattern, val] of Object.entries(STRING_STUBS)) {
- if (lower === pattern.toLowerCase().replace(/[-_]/g, '') || lower.endsWith(pattern.toLowerCase().replace(/[-_]/g, ''))) {
- return val;
- }
- }
- return `sample_${key}`;
-}
-
-function getStubbed(schema: SchemaNode, key?: string): any {
- if (schema.default !== undefined) return schema.default;
- if (schema.enum && schema.enum.length > 0) return schema.enum[0];
- switch (schema.type) {
- case 'string': return stubString(key || 'value');
- case 'number': return 42;
- case 'integer': return 7;
- case 'boolean': return true;
- case 'array': {
- if (!schema.items) return [];
- return [getStubbed(schema.items, key ? `${key}_item` : 'item')];
- }
- case 'object': {
- const obj: Record = {};
- if (schema.properties) {
- for (const [k, v] of Object.entries(schema.properties)) {
- obj[k] = getStubbed(v, k);
- }
- }
- return obj;
- }
- default: return stubString(key || 'value');
- }
-}
-
const InputSchemaForm: React.FC = ({ schema, value, onChange, label, depth = 0 }) => {
const c = useClaudeTokens();
@@ -309,5 +258,5 @@ const InputSchemaForm: React.FC = ({ schema, value, onChange, label, dept
);
};
-export { getDefault, getStubbed };
+export { getDefault };
export default InputSchemaForm;
diff --git a/frontend/src/app/pages/Views/ViewEditor.tsx b/frontend/src/app/pages/Views/ViewEditor.tsx
index 9f9c47f5..a47e509d 100644
--- a/frontend/src/app/pages/Views/ViewEditor.tsx
+++ b/frontend/src/app/pages/Views/ViewEditor.tsx
@@ -7,7 +7,6 @@ import TextField from '@mui/material/TextField';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Tooltip from '@mui/material/Tooltip';
-import Switch from '@mui/material/Switch';
import CircularProgress from '@mui/material/CircularProgress';
import SaveIcon from '@mui/icons-material/Save';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
@@ -20,23 +19,18 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
import FolderIcon from '@mui/icons-material/Folder';
import AddIcon from '@mui/icons-material/Add';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
-import BoltIcon from '@mui/icons-material/Bolt';
import Collapse from '@mui/material/Collapse';
import Chip from '@mui/material/Chip';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
-import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
-import { createDraftSession, removeDraftSession, fetchSession, AgentMessage } from '@/shared/state/agentsSlice';
-import { createOutput, updateOutput, Output, executeOutput, OutputExecuteResult, autoRunOutput, autoRunAgentOutput, cleanupAutoRunAgent, AutoRunConfig, SERVE_BASE } from '@/shared/state/outputsSlice';
-import { createSessionWs } from '@/shared/ws/WebSocketManager';
+import { createDraftSession, removeDraftSession, fetchSession } from '@/shared/state/agentsSlice';
+import { createOutput, updateOutput, Output, executeOutput, OutputExecuteResult, SERVE_BASE } from '@/shared/state/outputsSlice';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import AgentChat from '../AgentChat/AgentChat';
-import ChatInput, { ChatInputHandle } from '../AgentChat/ChatInput';
import RefreshIcon from '@mui/icons-material/Refresh';
import ViewPreview, { ViewPreviewHandle } from './ViewPreview';
-import InputSchemaForm, { getDefault, getStubbed } from './InputSchemaForm';
-import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
+import { getDefault } from './InputSchemaForm';
import CodeEditor from './CodeEditor';
import { ElementSelectionProvider } from '@/app/components/ElementSelectionContext';
import { captureViewThumbnail } from './captureViewThumbnail';
@@ -107,160 +101,6 @@ function buildFileTree(filePaths: string[]): FileTreeNode[] {
return root;
}
-interface LogEntryProps {
- msg: AgentMessage;
- c: ReturnType;
-}
-
-const LogEntry: React.FC = ({ msg, c }) => {
- const [open, setOpen] = useState(false);
-
- if (msg.role === 'user') return null;
-
- if (msg.role === 'assistant') {
- const text = typeof msg.content === 'string'
- ? msg.content
- : Array.isArray(msg.content)
- ? msg.content.filter((b: any) => b.type === 'text').map((b: any) => b.text).join('')
- : JSON.stringify(msg.content);
- if (!text.trim()) return null;
- return (
-
- setOpen(!open)}
- sx={{ display: 'flex', alignItems: 'center', gap: 0.5, cursor: 'pointer', '&:hover': { opacity: 0.8 } }}
- >
-
-
- {text.slice(0, 120)}{text.length > 120 ? '…' : ''}
-
-
-
-
- {text}
-
-
-
- );
- }
-
- if (msg.role === 'tool_call') {
- const tc = typeof msg.content === 'object' ? msg.content as Record : {};
- return (
-
- setOpen(!open)}
- sx={{ display: 'flex', alignItems: 'center', gap: 0.5, cursor: 'pointer', '&:hover': { opacity: 0.8 } }}
- >
-
-
- {tc.input && (
-
- {JSON.stringify(tc.input).slice(0, 80)}…
-
- )}
-
-
-
-
- {JSON.stringify(tc.input, null, 2)}
-
-
-
-
- );
- }
-
- if (msg.role === 'tool_result') {
- const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
- return (
-
- setOpen(!open)}
- sx={{ display: 'flex', alignItems: 'center', gap: 0.5, cursor: 'pointer', '&:hover': { opacity: 0.8 } }}
- >
-
-
- result ({content.length > 60 ? `${content.length} chars` : content.slice(0, 60)})
-
-
-
-
- {content}
-
-
-
- );
- }
-
- if (msg.role === 'system') {
- const text = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
- return (
-
- {text}
-
- );
- }
-
- return null;
-};
-
-interface AutoRunLogProps {
- messages: AgentMessage[];
- status: string | null;
- logEndRef: React.RefObject;
- c: ReturnType;
-}
-
-const AutoRunLog: React.FC = ({ messages, status, logEndRef, c }) => {
- const isRunning = status === 'running' || status === 'waiting_approval';
- const isDone = status === 'completed' || status === 'stopped';
- const isError = status === 'error';
-
- return (
-
-
- {isRunning && }
- {isDone && }
- {isError && }
-
- {isRunning ? 'Agent running…' : isDone ? 'Agent completed' : isError ? 'Agent error' : 'Execution log'}
-
-
- {messages.length} messages
-
-
-
- {messages.map((msg) => (
-
- ))}
-
-
-
- );
-};
-
interface ConsoleEntry {
timestamp: number;
inputData: Record;
@@ -495,8 +335,6 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
const TAB_PREVIEW = 0;
const TAB_CODE = 1;
- const TAB_TEST_INPUT = 2;
- const TAB_AUTO_RUN = 3;
const TAB_CONSOLE = 4;
const [activeTab, setActiveTab] = useState(TAB_PREVIEW);
@@ -523,25 +361,8 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
const [consoleEntry, setConsoleEntry] = useState(null);
const [hasNewConsoleOutput, setHasNewConsoleOutput] = useState(false);
- const savedAutoRun = output?.auto_run_config;
- const [autoRunEnabled, setAutoRunEnabled] = useState(savedAutoRun?.enabled ?? false);
- const [autoRunMode, setAutoRunMode] = useState(savedAutoRun?.mode ?? 'agent');
- const [autoRunModel, setAutoRunModel] = useState(savedAutoRun?.model ?? 'sonnet');
- const [autoRunning, setAutoRunning] = useState(false);
- const autoRunInputRef = useRef(null);
- const autoRunInitialized = useRef(false);
const previewRef = useRef(null);
- const [autoRunSessionId, setAutoRunSessionId] = useState(null);
- const autoRunWsRef = useRef | null>(null);
- const autoRunLogEndRef = useRef(null);
-
- const autoRunSession = useAppSelector((state) =>
- autoRunSessionId ? state.agents.sessions[autoRunSessionId] : null
- );
- const autoRunMessages = autoRunSession?.messages ?? [];
- const autoRunSessionStatus = autoRunSession?.status ?? null;
-
const SIDEBAR_MIN = 280;
const SIDEBAR_MAX = 800;
const [sidebarWidth, setSidebarWidth] = useState(420);
@@ -798,41 +619,10 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
try { return JSON.parse(schemaText); } catch { return { type: 'object', properties: {} }; }
}, [schemaText]);
- const testInputDefault = useMemo(() => getDefault(parsedSchema), [parsedSchema]);
- const [testInput, setTestInput] = useState>(testInputDefault);
-
- useEffect(() => {
- setTestInput(getDefault(parsedSchema));
- }, [schemaText]);
-
- useEffect(() => {
- if (autoRunInitialized.current || !savedAutoRun) return;
- if (!autoRunEnabled) return;
- autoRunInitialized.current = true;
- const timer = setTimeout(() => {
- autoRunInputRef.current?.setContent(
- savedAutoRun.prompt || '',
- savedAutoRun.context_paths?.map((cp) => ({ path: cp.path, type: (cp.type as 'file' | 'directory') || 'file' })),
- savedAutoRun.forced_tools,
- );
- }, 100);
- return () => clearTimeout(timer);
- }, [savedAutoRun, autoRunEnabled]);
+ const testInput = useMemo>(() => getDefault(parsedSchema), [parsedSchema]);
const savedRef = useRef(!!output);
- const getAutoRunConfig = (): AutoRunConfig => {
- const config = autoRunInputRef.current?.getConfig();
- return {
- enabled: autoRunEnabled,
- prompt: config?.prompt ?? '',
- context_paths: config?.contextPaths?.map((cp) => ({ path: cp.path, type: cp.type })) ?? [],
- forced_tools: (config?.forcedTools ?? []).map(({ label, tools, iconKey }) => ({ label, tools, iconKey })),
- mode: autoRunMode,
- model: autoRunModel,
- };
- };
-
const buildBody = () => {
let schema: Record;
try { schema = JSON.parse(schemaText); } catch { schema = { type: 'object', properties: {} }; }
@@ -848,7 +638,6 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
icon: 'view_quilt',
input_schema: schema,
files: outputFiles,
- auto_run_config: getAutoRunConfig(),
};
};
@@ -944,137 +733,6 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
}
};
- const handleAutoRun = async () => {
- const config = autoRunInputRef.current?.getConfig();
- if (!config?.prompt?.trim()) return;
- setAutoRunning(true);
-
- let schema: Record;
- try { schema = JSON.parse(schemaText); } catch { schema = { type: 'object', properties: {} }; }
- const forcedToolNames = config.forcedTools.flatMap((ft) => ft.tools);
-
- const eid = output?.id ?? createdIdRef.current;
- if (forcedToolNames.length > 0 && eid) {
- try {
- const res = await dispatch(autoRunAgentOutput({
- prompt: config.prompt,
- input_schema: schema,
- output_id: eid,
- model: autoRunModel,
- forced_tools: forcedToolNames,
- context_paths: config.contextPaths.map((cp) => ({ path: cp.path, type: cp.type })),
- })).unwrap();
- setAutoRunSessionId(res.session_id);
- const ws = createSessionWs(res.session_id);
- ws.connect();
- autoRunWsRef.current = ws;
- } catch {
- setAutoRunning(false);
- }
- } else {
- try {
- const backendCode = files['backend.py'] ?? null;
- const res = await dispatch(autoRunOutput({
- prompt: config.prompt,
- input_schema: schema,
- backend_code: backendCode || undefined,
- context_paths: config.contextPaths.map((cp) => ({ path: cp.path, type: cp.type })),
- forced_tools: forcedToolNames.length > 0 ? forcedToolNames : undefined,
- model: autoRunModel,
- })).unwrap();
- if (res.input_data) {
- setTestInput(res.input_data);
- setExecuteResult({
- output_id: output?.id ?? createdIdRef.current ?? '',
- output_name: name,
- frontend_code: files['index.html'] ?? '',
- input_data: res.input_data,
- backend_result: res.backend_result,
- stdout: res.stdout ?? null,
- stderr: res.stderr ?? null,
- error: res.error,
- });
- setConsoleEntry({ timestamp: Date.now(), inputData: res.input_data, stdout: res.stdout ?? null, stderr: res.stderr ?? null, backendResult: res.backend_result, error: res.error, source: 'auto-run' });
- setHasNewConsoleOutput(true);
- setActiveTab(TAB_PREVIEW);
- }
- } catch {}
- setAutoRunning(false);
- }
- };
-
- useEffect(() => {
- if (!autoRunSessionId || !autoRunSessionStatus) return;
- if (autoRunSessionStatus !== 'completed' && autoRunSessionStatus !== 'error' && autoRunSessionStatus !== 'stopped') return;
-
- let extracted = false;
- for (const msg of autoRunMessages) {
- if (msg.role !== 'tool_call' || typeof msg.content !== 'object') continue;
- const tc = msg.content as { tool?: string; input?: Record };
- if (tc.tool !== 'RenderOutput' || !tc.input?.input_data) continue;
- setTestInput(tc.input.input_data);
- setExecuteResult({
- output_id: output?.id ?? createdIdRef.current ?? '',
- output_name: name,
- frontend_code: files['index.html'] ?? '',
- input_data: tc.input.input_data,
- backend_result: null,
- stdout: null,
- stderr: null,
- error: null,
- });
- setConsoleEntry({ timestamp: Date.now(), inputData: tc.input.input_data, stdout: null, stderr: null, backendResult: null, error: null, source: 'agent' });
- setHasNewConsoleOutput(true);
- setActiveTab(TAB_PREVIEW);
- extracted = true;
- break;
- }
-
- if (!extracted && autoRunSessionStatus === 'error') {
- const lastSys = [...autoRunMessages].reverse().find((m) => m.role === 'system');
- if (lastSys) {
- const errMsg = typeof lastSys.content === 'string' ? lastSys.content : JSON.stringify(lastSys.content);
- setExecuteResult({
- output_id: output?.id ?? createdIdRef.current ?? '',
- output_name: name,
- frontend_code: files['index.html'] ?? '',
- input_data: {},
- backend_result: null,
- stdout: null,
- stderr: null,
- error: errMsg,
- });
- setConsoleEntry({ timestamp: Date.now(), inputData: {}, stdout: null, stderr: null, backendResult: null, error: errMsg, source: 'agent' });
- setHasNewConsoleOutput(true);
- }
- }
-
- setAutoRunning(false);
-
- if (autoRunWsRef.current) {
- autoRunWsRef.current.disconnect();
- autoRunWsRef.current = null;
- }
- cleanupAutoRunAgent(autoRunSessionId).catch(() => {});
- setTimeout(() => setAutoRunSessionId(null), 300);
- }, [autoRunSessionId, autoRunSessionStatus]);
-
- useEffect(() => {
- autoRunLogEndRef.current?.scrollIntoView({ behavior: 'smooth' });
- }, [autoRunMessages.length]);
-
- useEffect(() => {
- return () => {
- if (autoRunWsRef.current) {
- autoRunWsRef.current.disconnect();
- autoRunWsRef.current = null;
- }
- if (autoRunSessionId) {
- cleanupAutoRunAgent(autoRunSessionId).catch(() => {});
- }
- };
- }, [autoRunSessionId]);
-
const workspaceServeUrl = workspaceId
? `${SERVE_BASE}/workspace/${workspaceId}/serve/index.html`
: undefined;
@@ -1164,7 +822,7 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
return () => {
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
};
- }, [files, name, description, autoRunEnabled, autoRunMode, autoRunModel]);
+ }, [files, name, description]);
useEffect(() => {
return () => {
@@ -1280,26 +938,6 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
}}
/>
- {autoRunEnabled && (
- : }
- onClick={handleAutoRun}
- disabled={autoRunning}
- size="small"
- sx={{
- borderColor: '#f59e0b40',
- color: '#f59e0b',
- textTransform: 'none',
- fontWeight: 500,
- fontSize: '0.8rem',
- px: 1.5,
- '&:hover': { borderColor: '#f59e0b', bgcolor: '#f59e0b10' },
- }}
- >
- {autoRunning ? 'Running…' : 'Auto Run'}
-
- )}
{saveStatus === 'unsaved' && (
Unsaved changes
@@ -1371,8 +1009,6 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
>
-
-
{showConsole && }
{activeTab === TAB_PREVIEW && (
@@ -1566,162 +1202,9 @@ const ViewEditor: React.FC = ({ output, onClose }) => {
)}
- {activeTab === TAB_TEST_INPUT && (
-
-
- }
- onClick={() => setTestInput(getStubbed(parsedSchema))}
- sx={{
- textTransform: 'none',
- fontSize: '0.78rem',
- color: c.accent.primary,
- fontWeight: 500,
- '&:hover': { bgcolor: c.bg.elevated },
- }}
- >
- Fill sample data
-
-
-
-
-
-
-
- )}
{activeTab === TAB_CONSOLE && (
)}
-
-
- setAutoRunEnabled(v)}
- size="small"
- sx={{
- '& .MuiSwitch-switchBase.Mui-checked': { color: '#f59e0b' },
- '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: '#f59e0b' },
- }}
- />
-
- {autoRunEnabled ? 'Auto Run enabled' : 'Auto Run disabled'}
-
-
- {autoRunEnabled && (
- : }
- onClick={handleAutoRun}
- disabled={autoRunning}
- sx={{
- textTransform: 'none',
- fontSize: '0.78rem',
- color: '#f59e0b',
- fontWeight: 500,
- '&:hover': { bgcolor: '#f59e0b10' },
- }}
- >
- {autoRunning ? 'Running…' : 'Run Now'}
-
- )}
-
-
- {autoRunEnabled ? (
-
-
- Describe what data to generate for this app. When triggered, an LLM will produce input data matching your schema and populate the preview.
-
-
- {}}
- mode={autoRunMode}
- onModeChange={setAutoRunMode}
- model={autoRunModel}
- onModelChange={setAutoRunModel}
- />
-
- : }
- onClick={() => handleSave(false)}
- disabled={saving || !name.trim()}
- size="small"
- sx={{
- alignSelf: 'flex-start',
- flexShrink: 0,
- bgcolor: c.accent.primary,
- textTransform: 'none',
- fontWeight: 500,
- fontSize: '0.8rem',
- px: 2,
- '&:hover': { bgcolor: c.accent.hover },
- }}
- >
- {saving ? 'Saving…' : 'Save'}
-
- {(autoRunSessionId || autoRunMessages.length > 0) && (
-
- )}
-
- ) : (
-
-
-
- Enable Auto Run to generate live data for this app
-
-
- Configure a prompt that describes what data to generate. An LLM will produce input matching your schema and populate the preview automatically.
-
-
- )}
-
-
diff --git a/frontend/src/shared/state/agentsSlice.ts b/frontend/src/shared/state/agentsSlice.ts
index 71cb3ea1..80cc7db1 100644
--- a/frontend/src/shared/state/agentsSlice.ts
+++ b/frontend/src/shared/state/agentsSlice.ts
@@ -103,7 +103,6 @@ export interface AgentSession {
context_overflow?: { reason: string; message: string; at: string } | null;
mcp_suggestions?: Array<{ id: string; title: string; description: string; reason?: string }>;
mcp_suggestions_is_vague?: boolean;
- active_outputs?: string[];
compacted_through_msg_id?: string | null;
// Transient frontend-only WS connection state. Independent of
// `status` (which describes the agent run itself). When the WS
diff --git a/frontend/src/shared/state/outputsSlice.ts b/frontend/src/shared/state/outputsSlice.ts
index 7a22c71f..5837208a 100644
--- a/frontend/src/shared/state/outputsSlice.ts
+++ b/frontend/src/shared/state/outputsSlice.ts
@@ -6,15 +6,6 @@ const OUTPUTS_API = `${API_BASE}/outputs`;
export const SERVE_BASE = `${API_BASE}/outputs`;
-export interface AutoRunConfig {
- enabled: boolean;
- prompt: string;
- context_paths: Array<{ path: string; type: string }>;
- forced_tools: Array<{ label: string; tools: string[]; iconKey?: string }>;
- mode: string;
- model: string;
-}
-
export interface Output {
id: string;
name: string;
@@ -23,7 +14,6 @@ export interface Output {
input_schema: Record;
files: Record;
permission: string;
- auto_run_config?: AutoRunConfig | null;
thumbnail?: string | null;
// Linkage so reopening App Builder reattaches to the in-progress session
// and reuses the on-disk workspace folder instead of seeding a fresh one.
@@ -135,57 +125,6 @@ export const executeOutput = createAsyncThunk(
}
);
-export interface AutoRunResult {
- input_data: Record | null;
- backend_result: Record | null;
- stdout: string | null;
- stderr: string | null;
- error: string | null;
-}
-
-export const autoRunOutput = createAsyncThunk(
- 'outputs/autoRun',
- // backend_code intentionally NOT in the request shape. The server endpoint
- // ignores it now (it was an unsandboxed-RCE primitive); callers that want
- // backend execution should chain executeOutput against a persisted Output.
- async (body: { prompt: string; input_schema: Record; context_paths?: Array<{ path: string; type: string }>; forced_tools?: string[]; model?: string }) => {
- const res = await fetch(`${OUTPUTS_API}/auto-run`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body),
- });
- return (await res.json()) as AutoRunResult;
- }
-);
-
-export interface AutoRunAgentResult {
- session_id: string;
-}
-
-export const autoRunAgentOutput = createAsyncThunk(
- 'outputs/autoRunAgent',
- async (body: {
- prompt: string;
- input_schema: Record;
- output_id: string;
- model?: string;
- forced_tools?: string[];
- context_paths?: Array<{ path: string; type: string }>;
- }) => {
- const res = await fetch(`${OUTPUTS_API}/auto-run-agent`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body),
- });
- if (!res.ok) throw new Error(`Auto-run agent launch failed: ${res.status}`);
- return (await res.json()) as AutoRunAgentResult;
- }
-);
-
-export async function cleanupAutoRunAgent(sessionId: string): Promise {
- await fetch(`${OUTPUTS_API}/auto-run-agent/${sessionId}`, { method: 'DELETE' });
-}
-
const outputsSlice = createSlice({
name: 'outputs',
initialState,