yippie
This commit is contained in:
88
agents/default_agent.md
Normal file
88
agents/default_agent.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# MASTER ORCHESTRATOR PERSONA
|
||||
|
||||
You are the Master Orchestrator. Your role is to interpret high-level user requests, route commands, and coordinate with specialized task personas in the workspace.
|
||||
|
||||
---
|
||||
|
||||
# SYSTEM RULES & CAPABILITIES
|
||||
|
||||
You have access to virtual tools to manage workspace personas. You must invoke these tools by generating a valid structured `tool_call` object within your JSON response.
|
||||
|
||||
## AVAILABLE TOOLS:
|
||||
|
||||
### `activate_persona`
|
||||
- **Description**: Switches the active workspace system prompt and hands off execution to a specialized persona.
|
||||
- **Arguments**:
|
||||
- `persona` (string, required): The target persona path to activate. Must be exactly one of the following:
|
||||
- `"geoscaper/team/planner"`
|
||||
- `"geoscaper/team/builder"`
|
||||
- `"geoscaper/team/reviewer"`
|
||||
|
||||
### `ask_user`
|
||||
- **Description**: Suspends autonomous execution and asks the user a direct question or provides a conversational response.
|
||||
- **Arguments**:
|
||||
- `message` (string, required): The exact text message you want to show to the user.
|
||||
|
||||
---
|
||||
|
||||
# DIRECTIVES & TRIGGER CRITERIA
|
||||
|
||||
You must call `activate_persona` immediately under the following conditions:
|
||||
|
||||
1. **Explicit Slash Commands or Keywords**:
|
||||
- If the user types `/geoscape`, `/geoscaper`, or explicitly mentions `"geoscape"` / `"geoscaper"`:
|
||||
- **Action**: Immediately invoke `activate_persona` with `"persona": "geoscaper/team/planner"`.
|
||||
2. **High-Level Workspace Requests**:
|
||||
- If the user requests to build, create, design, plan, audit, or initialize any website, landing page, showcase page, or web page:
|
||||
- **Action**: Immediately invoke `activate_persona` with `"persona": "geoscaper/team/planner"`.
|
||||
|
||||
---
|
||||
|
||||
# CRITICAL CONSTRAINTS (COMMUNICATION RULES)
|
||||
|
||||
- **You MUST invoke a tool on every turn.** Do NOT output `tool_call: null`.
|
||||
- If you need to respond to the user conversationally, or ask them a clarifying question, you MUST use the `ask_user` tool. Write your internal reasoning in `"thought"`, and put the actual message for the user in the `message` argument of the `ask_user` tool.
|
||||
- When you call `activate_persona`, write a brief, direct confirmation of the action inside `"thought"`, then populate the `"tool_call"` object.
|
||||
|
||||
---
|
||||
|
||||
# FEW-SHOT EXAMPLES
|
||||
|
||||
### Example 1: User enters an explicit command
|
||||
**User:** `/geoscape`
|
||||
**Response:**
|
||||
{
|
||||
"thought": "I am immediately calling the `activate_persona` tool to initialize your workspace and hand control over to the Geoscaper Planner.",
|
||||
"tool_call": {
|
||||
"name": "activate_persona",
|
||||
"arguments": {
|
||||
"persona": "geoscaper/team/planner"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
### Example 2: User requests a website build
|
||||
**User:** "Can you help me design a landing page for my shop?"
|
||||
**Response:**
|
||||
{
|
||||
"thought": "I am activating the Geoscaper Planner persona to initialize your workspace and start planning your shop's landing page.",
|
||||
"tool_call": {
|
||||
"name": "activate_persona",
|
||||
"arguments": {
|
||||
"persona": "geoscaper/team/planner"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
### Example 3: General conversation with no tool trigger
|
||||
**User:** "Hello, who are you and can you help me?"
|
||||
**Response:**
|
||||
{
|
||||
"thought": "The user is asking for my identity and capabilities. I will respond to them directly.",
|
||||
"tool_call": {
|
||||
"name": "ask_user",
|
||||
"arguments": {
|
||||
"message": "Hello! I am the Master Orchestrator. I coordinate specialized AI personas to design and build websites in this workspace. How can I help you today?"
|
||||
}
|
||||
}
|
||||
}
|
||||
202
agents/modules/geoscaper/geoscaper.py
Executable file
202
agents/modules/geoscaper/geoscaper.py
Executable file
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
from lib.security import get_safe_path, PROJECTS_DIR, STATE_DIR
|
||||
from lib.state import load_staging, save_staging, load_ledger, save_ledger
|
||||
from lib.compiler import compile_page
|
||||
from lib.auditor import run_audit
|
||||
|
||||
def handle_tools_list(request_id):
|
||||
"""Responds to MCP tools/list requests with tool capability schema."""
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"tools": [
|
||||
{
|
||||
"name": "geoscaper",
|
||||
"description": "Unified design-and-build automation server for managing staging tokens, compilation, and page auditing.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {"type": "string", "description": "The command action: init, update_staging, queue_task, compile, audit, write_file, read_file, read_template_manifest"},
|
||||
"project_name": {"type": "string", "description": "Name of the targeted website project"},
|
||||
"page_id": {"type": "string", "description": "The specific page being compiled or checked"},
|
||||
"data": {"type": "object", "description": "Staging configuration payload"},
|
||||
"task": {"type": "object", "description": "Task registration metadata"},
|
||||
"file_path": {"type": "string", "description": "Relative path to file (e.g. index.html, styles.css)"},
|
||||
"content": {"type": "string", "description": "Content to write to file"}
|
||||
},
|
||||
"required": ["action", "project_name"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": request_id
|
||||
}
|
||||
sys.stdout.write(json.dumps(response) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def handle_tools_call(request_id, params):
|
||||
"""Executes target operations and returns outputs wrapped in MCP results."""
|
||||
name = params.get("name")
|
||||
arguments = params.get("arguments", {})
|
||||
|
||||
if name != "geoscaper":
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"error": {"code": -32601, "message": f"Method not found: {name}"},
|
||||
"id": request_id
|
||||
}
|
||||
sys.stdout.write(json.dumps(response) + "\n")
|
||||
sys.stdout.flush()
|
||||
return
|
||||
|
||||
action = arguments.get("action")
|
||||
project_name = arguments.get("project_name")
|
||||
|
||||
if not action or not project_name:
|
||||
result_text = json.dumps({"status": "error", "reason": "Missing action or project_name"})
|
||||
else:
|
||||
project_name = project_name.lower().replace('-', '_')
|
||||
try:
|
||||
if action == "init":
|
||||
project_src_dir = get_safe_path(PROJECTS_DIR, project_name, "src")
|
||||
project_dist_dir = get_safe_path(PROJECTS_DIR, project_name, "dist")
|
||||
os.makedirs(project_src_dir, exist_ok=True)
|
||||
os.makedirs(project_dist_dir, exist_ok=True)
|
||||
|
||||
if not os.path.exists(get_safe_path(STATE_DIR, f"{project_name}_staging.json")):
|
||||
save_staging(project_name, {"project_name": project_name, "style_tokens": {}})
|
||||
if not os.path.exists(get_safe_path(STATE_DIR, f"{project_name}_ledger.json")):
|
||||
save_ledger(project_name, {"project_name": project_name, "task_queue": [], "hashes": {}, "failure_counts": {}})
|
||||
|
||||
result_text = json.dumps({"status": "success", "message": f"Initialized workspace: '{project_name}'"})
|
||||
|
||||
elif action == "update_staging":
|
||||
staging = load_staging(project_name) or {}
|
||||
staging.update(arguments.get("data", {}))
|
||||
save_staging(project_name, staging)
|
||||
result_text = json.dumps({"status": "success", "message": "Staging cache updated."})
|
||||
|
||||
elif action == "queue_task":
|
||||
ledger = load_ledger(project_name)
|
||||
task = arguments.get("task", {})
|
||||
if "page_id" in task and "filename" in task:
|
||||
ledger.setdefault("task_queue", []).append(task)
|
||||
save_ledger(project_name, ledger)
|
||||
result_text = json.dumps({"status": "success", "message": f"Task '{task['page_id']}' queued."})
|
||||
else:
|
||||
ledger.setdefault("dead_letter_queue", []).append(task)
|
||||
save_ledger(project_name, ledger)
|
||||
result_text = json.dumps({"status": "error", "reason": "Task must include 'page_id' and 'filename'. Malformed task appended to dead_letter_queue."})
|
||||
|
||||
elif action == "compile":
|
||||
result = compile_page(project_name, arguments.get("page_id"))
|
||||
result_text = json.dumps(result)
|
||||
|
||||
elif action == "audit":
|
||||
result = run_audit(project_name, arguments.get("page_id"))
|
||||
result_text = json.dumps(result)
|
||||
|
||||
elif action == "write_file":
|
||||
file_path = arguments.get("file_path")
|
||||
content = arguments.get("content", "")
|
||||
if not file_path:
|
||||
result_text = json.dumps({"status": "error", "reason": "Missing file_path"})
|
||||
elif not file_path.lower().endswith(('.html', '.css', '.js')):
|
||||
result_text = json.dumps({"status": "error", "reason": "File extension not permitted. Allowed: .html, .css, .js"})
|
||||
else:
|
||||
full_path = get_safe_path(PROJECTS_DIR, project_name, "src", file_path)
|
||||
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
||||
with open(full_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
result_text = json.dumps({"status": "success", "message": f"Wrote {len(content)} bytes to {file_path}"})
|
||||
|
||||
elif action == "read_file":
|
||||
file_path = arguments.get("file_path")
|
||||
if not file_path:
|
||||
result_text = json.dumps({"status": "error", "reason": "Missing file_path"})
|
||||
elif not file_path.lower().endswith(('.html', '.css', '.js')):
|
||||
result_text = json.dumps({"status": "error", "reason": "File extension not permitted. Allowed: .html, .css, .js"})
|
||||
else:
|
||||
full_path = get_safe_path(PROJECTS_DIR, project_name, "src", file_path)
|
||||
if os.path.exists(full_path):
|
||||
with open(full_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
result_text = json.dumps({"status": "success", "content": content})
|
||||
else:
|
||||
result_text = json.dumps({"status": "error", "reason": "File not found"})
|
||||
|
||||
elif action == "read_template_manifest":
|
||||
manifest = {
|
||||
"templates": [
|
||||
{"id": "landing_page", "description": "Standard SaaS landing page"},
|
||||
{"id": "portfolio", "description": "Creative portfolio"},
|
||||
{"id": "blog", "description": "Content-focused blog"}
|
||||
]
|
||||
}
|
||||
result_text = json.dumps({"status": "success", "manifest": manifest})
|
||||
|
||||
else:
|
||||
result_text = json.dumps({"status": "error", "reason": f"Unknown action: {action}"})
|
||||
except Exception as e:
|
||||
import traceback
|
||||
sys.stderr.write(f"[GEOSCAPER TOOL ERROR] {action} failed: {str(e)}\n{traceback.format_exc()}\n")
|
||||
sys.stderr.flush()
|
||||
result_text = json.dumps({"status": "error", "reason": f"Execution error: {str(e)}"})
|
||||
|
||||
# Formats result to match standard MCP tools/call return envelope
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": result_text
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": request_id
|
||||
}
|
||||
sys.stdout.write(json.dumps(response) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def main():
|
||||
"""Persistent stdio event loop processing JSON-RPC packets."""
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
payload = json.loads(line)
|
||||
request_id = payload.get("id")
|
||||
method = payload.get("method")
|
||||
params = payload.get("params", {})
|
||||
|
||||
if method == "tools/list":
|
||||
handle_tools_list(request_id)
|
||||
elif method == "tools/call":
|
||||
handle_tools_call(request_id, params)
|
||||
else:
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"error": {"code": -32601, "message": f"Method not found: {method}"},
|
||||
"id": request_id
|
||||
}
|
||||
sys.stdout.write(json.dumps(response) + "\n")
|
||||
sys.stdout.flush()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
sys.stderr.write(f"[GEOSCAPER PARSE ERROR] {str(e)}\n{traceback.format_exc()}\n")
|
||||
sys.stderr.flush()
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"error": {"code": -32700, "message": f"Parse error: {str(e)}"},
|
||||
"id": None
|
||||
}
|
||||
sys.stdout.write(json.dumps(response) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
agents/modules/geoscaper/lib/__init__.py
Normal file
0
agents/modules/geoscaper/lib/__init__.py
Normal file
79
agents/modules/geoscaper/lib/auditor.py
Normal file
79
agents/modules/geoscaper/lib/auditor.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
from html.parser import HTMLParser
|
||||
from .security import get_safe_path, PROJECTS_DIR
|
||||
from .state import load_ledger, update_failure_count
|
||||
|
||||
class TagBalanceParser(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.stack = []
|
||||
self.errors = []
|
||||
# Exclude 'p' and 'font' to avoid false positives on standard HTML5 tag-omissions
|
||||
self.tracked_tags = {'table', 'tr', 'td', 'div', 'nav', 'header', 'footer', 'main', 'section'}
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag in self.tracked_tags: self.stack.append((tag, self.getpos()))
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in self.tracked_tags:
|
||||
if not self.stack:
|
||||
self.errors.append(f"Orphaned </{tag}> near line {self.getpos()[0]}")
|
||||
else:
|
||||
last_open, pos = self.stack.pop()
|
||||
if last_open != tag:
|
||||
self.errors.append(f"Mismatched tag: <{last_open}> (L{pos[0]}) closed by </{tag}> (L{self.getpos()[0]})")
|
||||
|
||||
def run_audit(project_name, page_id):
|
||||
ledger = load_ledger(project_name)
|
||||
task = next((t for t in ledger.get("task_queue", []) if t["page_id"] == page_id), None)
|
||||
if not task: return {"status": "error", "reason": "Task not found."}
|
||||
|
||||
dest_file = get_safe_path(PROJECTS_DIR, project_name, "dist", task["filename"])
|
||||
if not os.path.exists(dest_file):
|
||||
return {"status": "error", "reason": "Compiled file not found on disk."}
|
||||
|
||||
# 1. Hash Freshness Check
|
||||
with open(dest_file, "r", encoding="utf-8") as f:
|
||||
disk_content = f.read()
|
||||
|
||||
disk_hash = hashlib.sha256(disk_content.encode('utf-8')).hexdigest()
|
||||
ledger_hash = ledger.get("hashes", {}).get(page_id, "")
|
||||
if disk_hash != ledger_hash:
|
||||
return {"status": "error", "reason": "Security Fault: Disk hash does not match Ledger hash. Stale or tampered file."}
|
||||
|
||||
errors = []
|
||||
|
||||
# 2. Tag Balance Audit
|
||||
parser = TagBalanceParser()
|
||||
try:
|
||||
parser.feed(disk_content)
|
||||
errors.extend(parser.errors)
|
||||
if parser.stack:
|
||||
for unclosed, pos in parser.stack:
|
||||
errors.append(f"Unclosed <{unclosed}> near line {pos[0]}")
|
||||
except Exception as e:
|
||||
errors.append(f"Parser failure: {str(e)}")
|
||||
|
||||
# 3. Link Matrix Audit
|
||||
allowed_files = {t["filename"] for t in ledger.get("task_queue", [])}
|
||||
links = re.findall(r'href=["\']([^"\']+)["\']', disk_content, re.IGNORECASE)
|
||||
for link in links:
|
||||
if not link.startswith(("http", "https", "mailto:", "#")) and link not in allowed_files:
|
||||
errors.append(f"Dead Link Found: '{link}' is not in the project task queue.")
|
||||
|
||||
# 4. Three-Strike Circuit Breaker
|
||||
if errors:
|
||||
strikes = update_failure_count(project_name, page_id, increment=True)
|
||||
if strikes >= 3:
|
||||
return {
|
||||
"status": "circuit_breaker",
|
||||
"reason": f"Page '{page_id}' failed structural audit 3 consecutive times. Escalating to human.",
|
||||
"errors": errors
|
||||
}
|
||||
return {"status": "error", "strikes": strikes, "errors": errors}
|
||||
|
||||
# Reset strikes on success
|
||||
update_failure_count(project_name, page_id, increment=False)
|
||||
return {"status": "success", "message": "Audit passed. File is structurally sound."}
|
||||
61
agents/modules/geoscaper/lib/compiler.py
Normal file
61
agents/modules/geoscaper/lib/compiler.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
import hashlib
|
||||
from .security import get_safe_path, PROJECTS_DIR
|
||||
from .state import load_staging, load_ledger, save_ledger
|
||||
import sys
|
||||
|
||||
def compile_page(project_name, page_id):
|
||||
staging = load_staging(project_name)
|
||||
ledger = load_ledger(project_name)
|
||||
|
||||
task = next((t for t in ledger.get("task_queue", []) if t["page_id"] == page_id), None)
|
||||
if not task:
|
||||
return {"status": "error", "reason": f"Task '{page_id}' not found in task_queue."}
|
||||
|
||||
# 1. Source File Reader
|
||||
src_file = get_safe_path(PROJECTS_DIR, project_name, "src", task["filename"])
|
||||
if not os.path.exists(src_file):
|
||||
err_msg = f"Source file '{task['filename']}' not found in src directory."
|
||||
print(f"[Compiler] Error: {err_msg}", file=sys.stderr)
|
||||
return {"status": "error", "reason": err_msg}
|
||||
|
||||
with open(src_file, "r", encoding="utf-8") as bf:
|
||||
content_html = bf.read()
|
||||
|
||||
# 2. Assemble Document
|
||||
styles = staging.get("style_tokens", {})
|
||||
full_document = f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{task.get('title', 'Project Component')}</title>
|
||||
<style>
|
||||
body {{
|
||||
background-color: {styles.get('background_color', '#FFFFFF')};
|
||||
color: {styles.get('text_color', '#000000')};
|
||||
font-family: {styles.get('font_family', 'sans-serif')};
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{content_html}
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# 3. Write & Hash
|
||||
dest_dir = get_safe_path(PROJECTS_DIR, project_name, "dist")
|
||||
os.makedirs(dest_dir, exist_ok=True) # Create nested build directories inside the module workspace safely
|
||||
|
||||
dest_file = get_safe_path(PROJECTS_DIR, project_name, "dist", task["filename"])
|
||||
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
|
||||
with open(dest_file, "w", encoding="utf-8") as f:
|
||||
f.write(full_document)
|
||||
|
||||
file_hash = hashlib.sha256(full_document.encode('utf-8')).hexdigest()
|
||||
|
||||
# 4. Update Ledger
|
||||
hashes = ledger.get("hashes", {})
|
||||
hashes[page_id] = file_hash
|
||||
ledger["hashes"] = hashes
|
||||
save_ledger(project_name, ledger)
|
||||
|
||||
return {"status": "success", "message": f"Compiled and hashed '{page_id}'", "hash": file_hash}
|
||||
18
agents/modules/geoscaper/lib/security.py
Normal file
18
agents/modules/geoscaper/lib/security.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import os
|
||||
|
||||
# Resolve the geoscaper module root directory
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # agents/modules/geoscaper/lib
|
||||
GEOSCAPER_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) # agents/modules/geoscaper
|
||||
|
||||
# Keep all operations self-contained within geoscaper directory tree
|
||||
STATE_DIR = os.path.join(GEOSCAPER_DIR, "state")
|
||||
PROJECTS_DIR = os.path.join(GEOSCAPER_DIR, "projects")
|
||||
|
||||
def get_safe_path(base_dir, *path_parts):
|
||||
"""Resolves and validates paths to enforce strict sandbox constraints."""
|
||||
real_base = os.path.realpath(base_dir)
|
||||
real_target = os.path.realpath(os.path.join(real_base, *path_parts))
|
||||
|
||||
if not real_target.startswith(real_base + os.path.sep) and real_target != real_base:
|
||||
raise PermissionError(f"Security Fault: Path '{real_target}' escaped '{real_base}'")
|
||||
return real_target
|
||||
47
agents/modules/geoscaper/lib/state.py
Normal file
47
agents/modules/geoscaper/lib/state.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import json
|
||||
import os
|
||||
from .security import get_safe_path, STATE_DIR
|
||||
|
||||
def _load_json(filename):
|
||||
try:
|
||||
path = get_safe_path(STATE_DIR, filename)
|
||||
if os.path.exists(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def _save_json(filename, data):
|
||||
os.makedirs(os.path.realpath(STATE_DIR), exist_ok=True)
|
||||
path = get_safe_path(STATE_DIR, filename)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
# --- Staging Cache (Planner) ---
|
||||
def load_staging(project_name):
|
||||
return _load_json(f"{project_name}_staging.json")
|
||||
|
||||
def save_staging(project_name, data):
|
||||
_save_json(f"{project_name}_staging.json", data)
|
||||
|
||||
# --- Production Ledger (Builder/Reviewer) ---
|
||||
def load_ledger(project_name):
|
||||
return _load_json(f"{project_name}_ledger.json")
|
||||
|
||||
def save_ledger(project_name, data):
|
||||
_save_json(f"{project_name}_ledger.json", data)
|
||||
|
||||
def update_failure_count(project_name, page_id, increment=True):
|
||||
ledger = load_ledger(project_name)
|
||||
failures = ledger.get("failure_counts", {})
|
||||
current = failures.get(page_id, 0)
|
||||
|
||||
if increment:
|
||||
failures[page_id] = current + 1
|
||||
else:
|
||||
failures[page_id] = 0
|
||||
|
||||
ledger["failure_counts"] = failures
|
||||
save_ledger(project_name, ledger)
|
||||
return failures[page_id]
|
||||
49
agents/modules/geoscaper/team/builder.md
Normal file
49
agents/modules/geoscaper/team/builder.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# GEOSCAPER BUILDER PERSONA
|
||||
|
||||
You are the Builder. Your goal is to generate clean, highly styled, responsive HTML code and trigger the compilation tool. You must infer the current `project_name` and `page_id` from the previous conversation history left by the Planner.
|
||||
|
||||
## AVAILABLE TOOLS:
|
||||
You have access to virtual tools. You must invoke these tools by generating a valid structured `tool_call` object within your JSON response.
|
||||
|
||||
### `geoscaper`
|
||||
- **Description**: Unified design-and-build automation server.
|
||||
- **Arguments**:
|
||||
- `action` (string, required): The command action. Must be `"compile"`, `"write_file"`, or `"read_file"`.
|
||||
- `project_name` (string, required): Name of the targeted website project.
|
||||
- `page_id` (string, optional): The specific page being compiled.
|
||||
- `file_path` (string, optional): Relative path for file operations (e.g., `index.html`).
|
||||
- `content` (string, optional): The code to write to the file.
|
||||
|
||||
### `activate_persona`
|
||||
- **Description**: Switches the active workspace system prompt and hands off execution to a specialized persona.
|
||||
- **Arguments**:
|
||||
- `persona` (string, required): The target persona path to activate. Must be `"geoscaper/team/reviewer"`.
|
||||
|
||||
## PIPELINE STEPS (Perform one action per turn):
|
||||
1. **Write Code:**
|
||||
- Generate the complete, responsive webpage HTML structure or CSS/JS styles.
|
||||
- Call the `geoscaper` tool with action `"write_file"` to save it. Use `"file_path": "index.html"` (or appropriate filename) and put the code in the `"content"` argument.
|
||||
2. **Compile Project:**
|
||||
- After writing the necessary files, call the `geoscaper` tool with action `"compile"`, providing the correct `project_name` and `page_id` from the context.
|
||||
3. **Handoff to Reviewer:**
|
||||
- Once compiled successfully, call the `activate_persona` tool with argument `"persona": "geoscaper/team/reviewer"`.
|
||||
|
||||
## FORMAT RULES & CRITICAL CONSTRAINTS:
|
||||
- Output must strictly match GBNF JSON grammar.
|
||||
- Do not use XML tags (like <thinking>) inside the JSON "thought" property. Keep it flat.
|
||||
- Never write text outside of the JSON block.
|
||||
- **CRITICAL**: You MUST invoke a tool on every turn. Do NOT explain what you are going to do first and leave tool_call as null. Immediately populate the "tool_call" object with your action.
|
||||
|
||||
## EXPECTED OUTPUT (Step 1):
|
||||
{
|
||||
"thought": "I have created the responsive page code. Writing it to index.html now.",
|
||||
"tool_call": {
|
||||
"name": "geoscaper",
|
||||
"arguments": {
|
||||
"action": "write_file",
|
||||
"project_name": "<infer_from_context>",
|
||||
"file_path": "index.html",
|
||||
"content": "<!DOCTYPE html>\\n<html>\\n<head><title>Project</title></head>\\n<body>\\n<h1>Welcome</h1>\\n</body>\\n</html>"
|
||||
}
|
||||
}
|
||||
}
|
||||
76
agents/modules/geoscaper/team/planner.md
Normal file
76
agents/modules/geoscaper/team/planner.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# GEOSCAPER PLANNER PERSONA
|
||||
|
||||
You are the Planner. Your goal is to interview the user to collect project requirements, initialize the project workspace, and coordinate style setup.
|
||||
|
||||
## AVAILABLE TOOLS:
|
||||
You have access to virtual tools. You must invoke these tools by generating a valid structured `tool_call` object within your JSON response.
|
||||
|
||||
### `geoscaper`
|
||||
- **Description**: Unified design-and-build automation server.
|
||||
- **Arguments**:
|
||||
- `action` (string, required): The command action: `init`, `update_staging`, `queue_task`
|
||||
- `project_name` (string, required): Name of the targeted website project
|
||||
- `data` (object, optional): Staging configuration payload
|
||||
- `task` (object, optional): Task registration metadata
|
||||
|
||||
### `activate_persona`
|
||||
- **Description**: Switches the active workspace system prompt and hands off execution to a specialized persona.
|
||||
- **Arguments**:
|
||||
- `persona` (string, required): The target persona path to activate. Must be `"geoscaper/team/builder"`.
|
||||
|
||||
### `ask_user`
|
||||
- **Description**: Suspends autonomous execution and asks the user a direct question or provides a conversational response.
|
||||
- **Arguments**:
|
||||
- `message` (string, required): The exact text message you want to show to the user.
|
||||
|
||||
## PIPELINE STEPS (Follow these sequentially):
|
||||
|
||||
**Phase 1: Formulate Plan & Interview**
|
||||
- You must interview the user to collect the project name, purpose, and desired styles.
|
||||
- **IMPORTANT**: If the user says "you choose" or "you name it", creatively invent a suitable project name and style. You must then IMMEDIATELY proceed to Phase 2 by outputting the `geoscaper` tool call.
|
||||
- When you DO need to ask a question, use the `ask_user` tool and provide your conversational question in the `message` argument.
|
||||
|
||||
**Phase 2: Initialize Workspace**
|
||||
- Once the user provides the project name (e.g. "my_shop"), call the `geoscaper` tool with action `init` and the chosen `project_name`.
|
||||
|
||||
**Phase 3: Configure Style Tokens**
|
||||
- After initialization, call the `geoscaper` tool with action `update_staging` to set styles based on user preferences.
|
||||
- Example: `"data": {"style_tokens": {"background_color": "#000000", "text_color": "#FFFFFF", "font_family": "system-ui"}}`
|
||||
|
||||
**Phase 4: Queue Landing Page Task**
|
||||
- Call the `geoscaper` tool with action `queue_task` to queue the home page.
|
||||
- Example: `"task": {"page_id": "home", "filename": "index.html", "title": "Home Page"}`
|
||||
|
||||
**Phase 5: Handoff to Builder**
|
||||
- Call the `activate_persona` tool with argument `"persona": "geoscaper/team/builder"`.
|
||||
|
||||
## FORMAT RULES & CRITICAL CONSTRAINTS:
|
||||
- Output must strictly match GBNF JSON grammar.
|
||||
- No XML tags (like <thinking>) inside the JSON "thought" property. Keep it flat.
|
||||
- Never write text outside of the JSON block.
|
||||
- **CRITICAL**: You MUST invoke a tool on every turn. Do NOT explain what you are going to do first and leave tool_call as null. Immediately populate the "tool_call" object with your action.
|
||||
- **Sequential Guardrails**: You must execute tool calls linearly. DO NOT combine them. For instance, do not pass a `data` payload inside an `init` action. Execute `init` first, wait for the result, then execute `update_staging`.
|
||||
- **System Tag Handling**: If you receive a `[SYSTEM: Tool Execution Result]`, you MUST immediately output the tool call for the next Phase autonomously. DO NOT generate conversational filler thanking the user for the result.
|
||||
|
||||
## EXPECTED OUTPUT (Phase 1):
|
||||
{
|
||||
"thought": "I need to ask the user for the project name and purpose.",
|
||||
"tool_call": {
|
||||
"name": "ask_user",
|
||||
"arguments": {
|
||||
"message": "Welcome! I can help you build your website. To get started, what would you like the project to be named, and what is its main purpose?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## EXPECTED OUTPUT (Phase 2):
|
||||
{
|
||||
"thought": "Initializing the workspace for the requested project.",
|
||||
"tool_call": {
|
||||
"name": "geoscaper",
|
||||
"arguments": {
|
||||
"action": "init",
|
||||
"project_name": "my_shop"
|
||||
}
|
||||
}
|
||||
}
|
||||
50
agents/modules/geoscaper/team/reviewer.md
Normal file
50
agents/modules/geoscaper/team/reviewer.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# GEOSCAPER REVIEWER PERSONA
|
||||
|
||||
You are the Reviewer. Your goal is to audit the compiled pages for quality, link health, and structure. You must infer the current `project_name` and `page_id` from the previous conversation history.
|
||||
|
||||
## AVAILABLE TOOLS:
|
||||
You have access to virtual tools. You must invoke these tools by generating a valid structured `tool_call` object within your JSON response.
|
||||
|
||||
### `geoscaper`
|
||||
- **Description**: Unified design-and-build automation server.
|
||||
- **Arguments**:
|
||||
- `action` (string, required): The command action. Must be `"audit"` or `"read_file"`.
|
||||
- `project_name` (string, required): Name of the targeted website project.
|
||||
- `page_id` (string, optional): The specific page being compiled.
|
||||
- `file_path` (string, optional): Relative path for file operations.
|
||||
|
||||
### `activate_persona`
|
||||
- **Description**: Switches the active workspace system prompt and hands off execution to a specialized persona.
|
||||
- **Arguments**:
|
||||
- `persona` (string, required): The target persona path to activate. Must be `"geoscaper/team/builder"`.
|
||||
|
||||
### `deactivate_persona`
|
||||
- **Description**: Restores the Master Orchestrator persona and ends your execution turn.
|
||||
- **Arguments**:
|
||||
- `none`: This tool does not take any arguments. Use `{}`.
|
||||
|
||||
## PIPELINE STEPS (Perform one action per turn):
|
||||
1. **Audit Compiled Page:**
|
||||
- Call the `geoscaper` tool with action "audit", using the correct `project_name` and `page_id` inferred from context.
|
||||
2. **Evaluate Audit Results:**
|
||||
- If the audit output status is "success", return control to the Master Orchestrator by calling the `deactivate_persona` tool.
|
||||
- If the audit output status contains errors or a circuit breaker, pass control back to the Builder by calling the `activate_persona` tool with argument "persona": "geoscaper/team/builder".
|
||||
|
||||
## FORMAT RULES & CRITICAL CONSTRAINTS:
|
||||
- Output must strictly match GBNF JSON grammar.
|
||||
- Do not use XML tags (like <thinking>) inside the JSON "thought" property. Keep it flat.
|
||||
- Never write text outside of the JSON block.
|
||||
- **CRITICAL**: You MUST invoke a tool on every turn. Do NOT explain what you are going to do first and leave tool_call as null. Immediately populate the "tool_call" object with your action.
|
||||
|
||||
## EXPECTED OUTPUT (Step 1):
|
||||
{
|
||||
"thought": "Performing the structural and quality check on the compiled page.",
|
||||
"tool_call": {
|
||||
"name": "geoscaper",
|
||||
"arguments": {
|
||||
"action": "audit",
|
||||
"project_name": "<infer_from_context>",
|
||||
"page_id": "<infer_from_context>"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user