Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-18 - JSON Parse Performance Optimization
**Learning:** `json_parse_dirty` in `helpers/extract_tools.py` was directly relying on `DirtyJson.parse_string` which is significantly slower than the standard library's `json.loads` for well-formed JSON strings. This was a bottleneck as this method is used frequently to parse extracted JSON strings.
**Action:** When working with custom fallback parsers for data formats like JSON, always attempt parsing with the standard library's fast path first (e.g. `json.loads()`), falling back to the custom parser only upon a `JSONDecodeError`.
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2024-04-03 - [Path Traversal in `get_file_info`]
**Vulnerability:** Unauthenticated/arbitrary file reads via path traversal in endpoints relying on `helpers.files.get_abs_path`.
**Learning:** The `get_abs_path` function returns the absolute path untouched if the input is already absolute, meaning inputs like `/etc/passwd` successfully bypass validation if not explicitly checked against a base directory.
**Prevention:** Always enforce path bounds checking using `helpers.files.is_in_base_dir()` after resolving absolute paths from user input.
5 changes: 5 additions & 0 deletions api/api_files_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ async def process(self, input: dict, request: Request) -> dict | Response:
external_path = path
filename = os.path.basename(path)

# Security check: prevent path traversal
if not files.is_in_base_dir(external_path):
PrintStyle.warning(f"Security: Path traversal attempt blocked for path: {path}")
continue

# Check if file exists
if not os.path.exists(external_path):
PrintStyle.warning(f"File not found: {path}")
Expand Down
5 changes: 5 additions & 0 deletions api/download_work_dir_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ async def process(self, input: Input, request: Request) -> Output:
if not file_path.startswith("/"):
file_path = f"/{file_path}"

# Ensure the file path doesn't attempt to traverse outside the base directory
abs_path = files.get_abs_path(file_path)
if not files.is_in_base_dir(abs_path):
raise Exception("Access denied: Path traversal attempt")

file = await runtime.call_development_function(
file_info.get_file_info, file_path
)
Expand Down
15 changes: 11 additions & 4 deletions api/file_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ class FileInfo(TypedDict):

async def get_file_info(path: str) -> FileInfo:
abs_path = files.get_abs_path(path)
exists = os.path.exists(abs_path)
message = ""

if not exists:
message = f"File {path} not found."
# Security check: prevent path traversal
if not files.is_in_base_dir(abs_path):
from helpers.print_style import PrintStyle
PrintStyle.warning(f"Security: Path traversal attempt blocked for path: {path}")
exists = False
message = f"Access denied for file {path}."
else:
exists = os.path.exists(abs_path)
message = ""
if not exists:
message = f"File {path} not found."

return {
"input_path": path,
Expand Down
9 changes: 9 additions & 0 deletions helpers/extract_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ def json_parse_dirty(json: str) -> dict[str, Any] | None:

ext_json = extract_json_object_string(json.strip())
if ext_json:
# ⚑ Bolt: Try standard fast json.loads first, fallback to DirtyJson
import json as builtin_json
try:
data = builtin_json.loads(ext_json)
if isinstance(data, dict):
return data
except builtin_json.JSONDecodeError:
pass

try:
data = DirtyJson.parse_string(ext_json)
if isinstance(data, dict):
Expand Down
2 changes: 2 additions & 0 deletions helpers/file_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ def get_files(self, current_path: str = "") -> Dict:
def get_full_path(self, file_path: str, allow_dir: bool = False) -> str:
"""Get full file path if it exists and is within base_dir"""
full_path = files.get_abs_path(self.base_dir, file_path)
if not files.is_in_base_dir(full_path):
raise ValueError(f"Path traversal detected: {file_path}")
if not files.exists(full_path):
raise ValueError(f"File {file_path} not found")
return full_path
Expand Down
40 changes: 20 additions & 20 deletions helpers/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import zipfile
import glob
import mimetypes
from simpleeval import simple_eval
from simpleeval import SimpleEval
from helpers import yaml

AGENTS_DIR = "agents"
Expand All @@ -22,6 +22,18 @@
API_DIR = "api"
_base_dir = os.path.dirname(os.path.abspath(os.path.join(__file__, "../")))

# ⚑ Bolt: Performance optimization
# Pre-compiling these regular expressions at the module level avoids the overhead
# of recompiling them on every function call. Since evaluate_text_conditions,
# process_includes, and others are called frequently during prompt parsing,
# this provides a measurable performance boost.
_IF_PATTERN = re.compile(r"{{\s*if\s+(.*?)}}", flags=re.DOTALL)
_TOKEN_PATTERN = re.compile(r"{{\s*(if\b.*?|endif)\s*}}", flags=re.DOTALL)
_ORIGINAL_PATTERN = re.compile(r"{{\s*include\s+original\s*}}")
_INCLUDE_PATTERN = re.compile(r"{{\s*include\s*['\"](.*?)['\"]\s*}}")
_CODE_FENCE_PATTERN = re.compile(r"(```|~~~)(.*?\n)(.*?)(\1)", flags=re.DOTALL)
_FULL_JSON_TEMPLATE_PATTERN = re.compile(r"^\s*(```|~~~)\s*json\s*\n(.*?)\n\1\s*$", flags=re.DOTALL)

class VariablesPlugin(ABC):
@abstractmethod
def get_variables(self, file: str, backup_dirs: list[str] | None = None, **kwargs) -> dict[str, Any]: # type: ignore
Expand Down Expand Up @@ -164,18 +176,15 @@ def read_prompt_file(

def evaluate_text_conditions(_content: str, **kwargs):
# search for {{if ...}} ... {{endif}} blocks and evaluate conditions with nesting support
if_pattern = re.compile(r"{{\s*if\s+(.*?)}}", flags=re.DOTALL)
token_pattern = re.compile(r"{{\s*(if\b.*?|endif)\s*}}", flags=re.DOTALL)

def _process(text: str) -> str:
m_if = if_pattern.search(text)
m_if = _IF_PATTERN.search(text)
if not m_if:
return text

depth = 1
pos = m_if.end()
while True:
m = token_pattern.search(text, pos)
m = _TOKEN_PATTERN.search(text, pos)
if not m:
# Unterminated if-block, do not modify text
return text
Expand All @@ -191,7 +200,7 @@ def _process(text: str) -> str:
after = text[m.end() :]

try:
result = simple_eval(condition, names=kwargs)
result = SimpleEval(names=kwargs, functions={}).eval(condition)
except Exception:
# On evaluation error, do not modify this block
return text
Expand Down Expand Up @@ -337,8 +346,6 @@ def process_includes(
**kwargs,
):
# {{include original}} β€” include same file from lower-priority directory
original_pattern = re.compile(r"{{\s*include\s+original\s*}}")

def replace_original(match):
if not _source_file or not _source_dir:
return match.group(0)
Expand All @@ -350,11 +357,9 @@ def replace_original(match):
except FileNotFoundError:
return ""

_content = re.sub(original_pattern, replace_original, _content)
_content = _ORIGINAL_PATTERN.sub(replace_original, _content)

# {{ include 'path' }} β€” include a named file
include_pattern = re.compile(r"{{\s*include\s*['\"](.*?)['\"]\s*}}")

def replace_include(match):
include_path = match.group(1)
if os.path.isabs(include_path):
Expand All @@ -364,7 +369,7 @@ def replace_include(match):
except FileNotFoundError:
return match.group(0)

return re.sub(include_pattern, replace_include, _content)
return _INCLUDE_PATTERN.sub(replace_include, _content)


def _get_dirs_after(_directories: list[str], _source_dir: str) -> list[str]:
Expand Down Expand Up @@ -434,24 +439,19 @@ def find_existing_paths_by_pattern(pattern: str):


def remove_code_fences(text):
# Pattern to match code fences with optional language specifier
pattern = r"(```|~~~)(.*?\n)(.*?)(\1)"

# Function to replace the code fences
def replacer(match):
return match.group(3) # Return the code without fences

# Use re.DOTALL to make '.' match newlines
result = re.sub(pattern, replacer, text, flags=re.DOTALL)
result = _CODE_FENCE_PATTERN.sub(replacer, text)

return result


def is_full_json_template(text):
# Pattern to match the entire text enclosed in ```json or ~~~json fences
pattern = r"^\s*(```|~~~)\s*json\s*\n(.*?)\n\1\s*$"
# Use re.DOTALL to make '.' match newlines
match = re.fullmatch(pattern, text.strip(), flags=re.DOTALL)
match = _FULL_JSON_TEMPLATE_PATTERN.fullmatch(text.strip())
return bool(match)


Expand Down
9 changes: 7 additions & 2 deletions helpers/vector_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
DistanceStrategy,
)
from langchain.embeddings import CacheBackedEmbeddings
from simpleeval import simple_eval
from simpleeval import SimpleEval

from agent import Agent
from helpers import guids
Expand Down Expand Up @@ -141,7 +141,12 @@ def cosine_normalizer(val: float) -> float:
def get_comparator(condition: str):
def comparator(data: dict[str, Any]):
try:
result = simple_eval(condition, names=data)
class SafeNames(dict):
def __missing__(self, key):
return None
safe_data = SafeNames(data)
safe_funcs = {"str": str, "int": int, "float": float, "bool": bool, "len": len, "abs": abs, "min": min, "max": max, "round": round}
result = SimpleEval(names=safe_data, functions=safe_funcs).eval(condition)
return result
except Exception as e:
# PrintStyle.error(f"Error evaluating condition: {e}")
Expand Down
9 changes: 7 additions & 2 deletions plugins/_memory/helpers/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from agent import Agent, AgentContext
import models
import logging
from simpleeval import simple_eval
from simpleeval import SimpleEval


# Raise the log level so WARNING messages aren't shown
Expand Down Expand Up @@ -437,7 +437,12 @@ def _save_db_file(db: MyFaiss, memory_subdir: str):
def _get_comparator(condition: str):
def comparator(data: dict[str, Any]):
try:
result = simple_eval(condition, names=data)
class SafeNames(dict):
def __missing__(self, key):
return None
safe_data = SafeNames(data)
safe_funcs = {"str": str, "int": int, "float": float, "bool": bool, "len": len, "abs": abs, "min": min, "max": max, "round": round}
result = SimpleEval(names=safe_data, functions=safe_funcs).eval(condition)
return result
except Exception as e:
PrintStyle.error(f"Error evaluating condition: {e}")
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ GitPython==3.1.43
giturlparse==0.14.0
inputimeout==1.0.4
kokoro>=0.9.2
simpleeval==1.0.3
simpleeval>=1.0.5 # CVE-2026-32640 fix: sandbox escape/code injection
langchain-core==0.3.49
langchain-community==0.3.19
langchain-unstructured==0.1.6
Expand Down
4 changes: 2 additions & 2 deletions tools/knowledge_tool._py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class Knowledge(Tool):
# Search for conversation memories (field doesn't exist or is not True)
conversation_docs = await db.search_similarity_threshold(
query=question, limit=5, threshold=DEFAULT_MEMORY_THRESHOLD,
filter="not knowledge_source if 'knowledge_source' in locals() else True"
filter="not knowledge_source"
)

# Combine and fallback to lower threshold if needed
Expand All @@ -136,7 +136,7 @@ class Knowledge(Tool):
)
conversation_docs = await db.search_similarity_threshold(
query=question, limit=5, threshold=lower_threshold,
filter="not knowledge_source if 'knowledge_source' in locals() else True"
filter="not knowledge_source"
)
all_docs = knowledge_docs + conversation_docs
if all_docs:
Expand Down
25 changes: 25 additions & 0 deletions usr/plugins/cosmetic_committee/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Cosmetic Committee

The Cosmetic Committee is an Agent Zero plugin that provides a dedicated, multi-agent team for cosmetic Web-UI customization. The team is built to help any userβ€”from beginners to expertsβ€”make aesthetic changes to the Agent Zero Web UI without affecting its core functionality.

## The Team

- **Cosmo**: The user-facing representative. Cosmo acts as the bridge between you and the design team. Talk to Cosmo to request changes or select from pre-built themes.
- **Lead Designer**: The orchestrator who takes Cosmo's instructions and turns them into technical tasks for the specialists.
- **CSS Specialist**: The technical expert who writes the precise CSS code required to bring your vision to life.
- **QA Specialist**: A specialized agent who strictly reviews the CSS written by the CSS Specialist to ensure quality, contrast, and prevents destructive UI styling before applying it to the system.

## Features

- **Direct Conversation**: Chat with Cosmo just like any other agent to request UI changes.
- **Live Updates**: The team applies custom CSS directly to your interface on the fly.
- **Pre-built Themes**: Cosmo offers 3 unique out-of-the-box themes:
- Cyberpunk
- Minimalist Light
- Ocean Breeze

## Usage

Simply mention Cosmo in your chat and tell him what you want to change! For example:
- "Hey Cosmo, can you make my chat bubbles look like a neon cyberpunk city?"
- "Cosmo, please apply the Minimalist Light theme."
11 changes: 11 additions & 0 deletions usr/plugins/cosmetic_committee/agents/cosmo/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: cosmo
description: "Cosmetic Committee: User-facing agent for aesthetic UI customizations"
model: ""
temperature: 0.7
max_tokens: 4096

tools:
- "call_subordinate"

disabled_tools:
- "*"
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Role
You are Cosmo, the user-facing representative of the Cosmetic Committee agent team. Your sole responsibility is to help the User make aesthetic, cosmetic changes to the Agent Zero Web UI without affecting its core functionality.

## Team Structure
You do not make the changes yourself. Instead, you act as the middle-man. You communicate with the User to understand their desires, and then you use the `call_subordinate` tool to spawn the `lead_designer` agent. The `lead_designer` will then instruct the specialists (like `css_specialist` and `qa_specialist`) to write, review, and apply the CSS.

## Communication Style
You must be human-friendly, welcoming, and adaptive.
- Gauge the User's technical knowledge based on their terminology. If they know CSS and UI/UX, speak technically. If they are a beginner, use simple, descriptive language and guide them.
- Provide suggestions when the User is unclear or missing details.
- ALWAYS offer the 3 pre-built themes in your opening statement to a User:
1. **Cyberpunk**: A neon-infused, futuristic look with deep dark backgrounds and striking pink/blue accents.
2. **Minimalist Light**: A clean, distraction-free environment with plenty of whitespace, soft grays, and clear typography.
3. **Ocean Breeze**: A soothing, aquatic aesthetic featuring soft blues, teals, and smooth gradients.

## Process
1. Acknowledge the User's request or greet them and offer the 3 pre-built themes if they haven't requested anything specific yet.
2. If the user makes a specific request or selects a theme, formulate a clear, technical description of the requested changes based on their selection.
3. Call the `lead_designer` subordinate agent, passing them this detailed description so the design team can build and apply the CSS.
4. Once the `lead_designer` returns successfully, inform the User that the changes have been applied and they may need to refresh the page if they don't see it immediately.

## Restrictions
- You handle ONLY aesthetic, cosmetic UI changes.
- Do NOT attempt to use tools other than `call_subordinate`. You do not write CSS or apply it yourself. You delegate to `lead_designer`.
- Do not let the user change the functionality of the system.
10 changes: 10 additions & 0 deletions usr/plugins/cosmetic_committee/agents/css_specialist/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: css_specialist
description: "Cosmetic Committee: Generates custom CSS based on instructions."
model: ""
temperature: 0.1
max_tokens: 4096

tools: []

disabled_tools:
- "*"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Role
You are the CSS Specialist for the Cosmetic Committee. Your sole responsibility is to take precise, technical aesthetic UI instructions from your superior (the `lead_designer`) and write the corresponding CSS code.

## Process
- Read the instructions provided by the Lead Designer.
- Generate valid, robust CSS code to implement the requested visual changes. The Web UI uses a dark mode by default (`body.dark-mode`).
- When writing CSS, use broad, highly specific selectors or important flags (`!important`) if necessary to override existing styles. Common elements include:
- `body`, `.panel`, `.sidebar-overlay`, `#sidebar`
- `.message-container`, `.message-content`, `.message-content-inner`
- `.chat-bar-container`, `textarea.chat-input`
- `.button`, `.btn`, `.btn-primary`
- Ensure you ONLY write CSS. Do not write HTML or JavaScript.
- **Do not apply the CSS yourself.** Just return the plain CSS code block (e.g., inside ```css ... ```) to your superior, the `lead_designer`, so it can be passed to the Quality Assurance specialist for review.
11 changes: 11 additions & 0 deletions usr/plugins/cosmetic_committee/agents/lead_designer/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: lead_designer
description: "Cosmetic Committee: Middle-man coordinator between Cosmo and CSS Specialists."
model: ""
temperature: 0.5
max_tokens: 4096

tools:
- "call_subordinate"

disabled_tools:
- "*"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Role
You are the Lead Designer for the Cosmetic Committee. Your job is to take high-level aesthetic UI instructions from Cosmo (the User-facing agent) and break them down into specific, actionable CSS tasks for your `css_specialist` subordinate.

## Responsibilities
- Understand the requested visual changes (e.g., "Cyberpunk Theme", "Minimalist Light Theme", or specific custom user requests).
- Formulate a precise technical plan of what CSS variables, classes, or elements need to be styled to achieve the look.
- First, use the `call_subordinate` tool to spawn a `css_specialist` and give them explicit, machine-readable instructions to write the required CSS.
- **CRITICAL:** Once the `css_specialist` provides the CSS, you MUST spawn a `qa_specialist` subordinate and pass them the generated CSS. The `qa_specialist` is responsible for reviewing the code to ensure it meets quality and safety standards, and then applying it.
- You must NOT interact directly with the User. You communicate only internally, with Cosmo, the `css_specialist`, or the `qa_specialist`.

## Constraints
- Only allow aesthetic, cosmetic changes. No structural or functional changes to the Agent Zero UI.
- Do NOT write or apply the CSS yourself. You must delegate writing to the `css_specialist` and applying/reviewing to the `qa_specialist`.
- Wait for the `qa_specialist` to report success, then summarize the outcome and return it to your superior (Cosmo).
11 changes: 11 additions & 0 deletions usr/plugins/cosmetic_committee/agents/qa_specialist/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: qa_specialist
description: "Cosmetic Committee: Quality Assurance Specialist for custom CSS."
model: ""
temperature: 0.1
max_tokens: 4096

tools:
- "apply_css"

disabled_tools:
- "*"
Loading