From ca16edf609fc824bba972af0310124e6dd53d93b Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 4 May 2026 11:56:37 +0800 Subject: [PATCH] feat: add models, CRUD UI, config functions and config model definitions - Add hermes_executions, hermes_tasks, hermes_workflows, task_dependencies models - Add harnessed_agent_config model and view CRUD JSON - Add config_functions.py for agent configuration - Add agent_config.ui and ios_design.css frontend files --- harnessed_agent/config_functions.py | 116 ++++++++++++++++++++ json/harnessed_agent_config_view.json | 21 ++++ models/harnessed_agent_config.json | 104 ++++++++++++++++++ models/hermes_executions.json | 133 +++++++++++++++++++++++ models/hermes_tasks.json | 147 ++++++++++++++++++++++++++ models/hermes_workflows.json | 112 ++++++++++++++++++++ models/task_dependencies.json | 98 +++++++++++++++++ wwwroot/agent_config.ui | 32 ++++++ wwwroot/ios_design.css | 114 ++++++++++++++++++++ 9 files changed, 877 insertions(+) create mode 100644 harnessed_agent/config_functions.py create mode 100644 json/harnessed_agent_config_view.json create mode 100644 models/harnessed_agent_config.json create mode 100644 models/hermes_executions.json create mode 100644 models/hermes_tasks.json create mode 100644 models/hermes_workflows.json create mode 100644 models/task_dependencies.json create mode 100644 wwwroot/agent_config.ui create mode 100644 wwwroot/ios_design.css diff --git a/harnessed_agent/config_functions.py b/harnessed_agent/config_functions.py new file mode 100644 index 0000000..62efe94 --- /dev/null +++ b/harnessed_agent/config_functions.py @@ -0,0 +1,116 @@ +from typing import Dict, Any +from datetime import datetime +import uuid + + +async def harnessed_get_agent_config(context: Dict[str, Any]) -> Dict[str, Any]: + """ + Get agent configuration for current user + + Args: + context: Request context containing user information + + Returns: + Configuration data for the current user + """ + from .core import HermesAgent + agent = HermesAgent() + user_id = agent._get_current_user_id(context) + + # Query database for user configuration + sql = """ + SELECT * FROM harnessed_agent_config + WHERE user_id = %(user_id)s + ORDER BY created_at DESC + LIMIT 1 + """ + try: + rows = await agent.db.sql(sql, {"user_id": user_id}) + if rows: + config = rows[0] + # Convert string booleans to actual booleans for UI + config["auto_cleanup_enabled"] = config["auto_cleanup_enabled"] == "1" + return {"success": True, "config": config} + else: + # Return default configuration + default_config = { + "id": f"default_{user_id}", + "user_id": user_id, + "work_dir": "./hermes_work", + "skills_path": "~/.hermes/skills", + "max_memory_tokens": 2000, + "default_priority": 50, + "high_priority_threshold": 70, + "low_priority_threshold": 30, + "auto_cleanup_enabled": True, + "min_retention_days": 30, + "created_at": datetime.now().isoformat(), + "updated_at": datetime.now().isoformat() + } + return {"success": True, "config": default_config} + except Exception as e: + return {"success": False, "error": str(e)} + + +async def harnessed_save_agent_config(context: Dict[str, Any], config_data: Dict[str, Any]) -> Dict[str, Any]: + """ + Save agent configuration for current user + + Args: + context: Request context containing user information + config_data: Configuration data to save + + Returns: + Success status + """ + from .core import HermesAgent + agent = HermesAgent() + user_id = agent._get_current_user_id(context) + + # Convert boolean to string for database storage + config_data["auto_cleanup_enabled"] = "1" if config_data.get("auto_cleanup_enabled") else "0" + config_data["user_id"] = user_id + config_data["updated_at"] = datetime.now().isoformat() + + # Check if config already exists + sql_check = "SELECT id FROM harnessed_agent_config WHERE user_id = %(user_id)s" + try: + rows = await agent.db.sql(sql_check, {"user_id": user_id}) + if rows: + # Update existing config + config_data["id"] = rows[0]["id"] + config_data["created_at"] = rows[0]["created_at"] # Keep original created_at + sql_update = """ + UPDATE harnessed_agent_config SET + work_dir = %(work_dir)s, + skills_path = %(skills_path)s, + max_memory_tokens = %(max_memory_tokens)s, + default_priority = %(default_priority)s, + high_priority_threshold = %(high_priority_threshold)s, + low_priority_threshold = %(low_priority_threshold)s, + auto_cleanup_enabled = %(auto_cleanup_enabled)s, + min_retention_days = %(min_retention_days)s, + updated_at = %(updated_at)s + WHERE id = %(id)s + """ + await agent.db.sql(sql_update, config_data) + else: + # Create new config + config_data["id"] = str(uuid.uuid4()).replace("-", "")[:32] + config_data["created_at"] = config_data["updated_at"] + sql_insert = """ + INSERT INTO harnessed_agent_config ( + id, user_id, work_dir, skills_path, max_memory_tokens, + default_priority, high_priority_threshold, low_priority_threshold, + auto_cleanup_enabled, min_retention_days, created_at, updated_at + ) VALUES ( + %(id)s, %(user_id)s, %(work_dir)s, %(skills_path)s, %(max_memory_tokens)s, + %(default_priority)s, %(high_priority_threshold)s, %(low_priority_threshold)s, + %(auto_cleanup_enabled)s, %(min_retention_days)s, %(created_at)s, %(updated_at)s + ) + """ + await agent.db.sql(sql_insert, config_data) + + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} diff --git a/json/harnessed_agent_config_view.json b/json/harnessed_agent_config_view.json new file mode 100644 index 0000000..c1de688 --- /dev/null +++ b/json/harnessed_agent_config_view.json @@ -0,0 +1,21 @@ +{ + "tblname": "harnessed_agent_config", + "alias": "harnessed_agent_config_view", + "title": "Agent Configuration", + "params": { + "logined_userid": "user_id", + "confidential_fields": [], + "browserfields": { + "alters": { + "auto_cleanup_enabled": { + "uitype": "code", + "data": [ + {"value": "1", "text": "Enabled"}, + {"value": "0", "text": "Disabled"} + ] + } + } + }, + "editexclouded": ["id", "user_id", "created_at"] + } +} \ No newline at end of file diff --git a/models/harnessed_agent_config.json b/models/harnessed_agent_config.json new file mode 100644 index 0000000..d7b05e1 --- /dev/null +++ b/models/harnessed_agent_config.json @@ -0,0 +1,104 @@ +{ + "summary": [ + { + "name": "harnessed_agent_config", + "title": "Hermes Agent module configuration settings", + "primary": "id" + } + ], + "fields": [ + { + "name": "id", + "title": "Unique configuration identifier", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "user_id", + "title": "User ID for multi-user isolation", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "work_dir", + "title": "Working directory path", + "type": "str", + "length": 255, + "nullable": "no", + "default": "./hermes_work" + }, + { + "name": "skills_path", + "title": "Skills directory path", + "type": "str", + "length": 255, + "nullable": "no", + "default": "~/.hermes/skills" + }, + { + "name": "max_memory_tokens", + "title": "Maximum tokens for memory context", + "type": "int", + "nullable": "no", + "default": "2000" + }, + { + "name": "default_priority", + "title": "Default priority for new memories (0-100)", + "type": "int", + "nullable": "no", + "default": "50" + }, + { + "name": "high_priority_threshold", + "title": "Threshold for high priority memories", + "type": "int", + "nullable": "no", + "default": "70" + }, + { + "name": "low_priority_threshold", + "title": "Threshold for low priority memories", + "type": "int", + "nullable": "no", + "default": "30" + }, + { + "name": "auto_cleanup_enabled", + "title": "Enable automatic memory cleanup", + "type": "str", + "length": "1", + "nullable": "no", + "default": "1" + }, + { + "name": "min_retention_days", + "title": "Minimum days to retain memories", + "type": "int", + "nullable": "no", + "default": "30" + }, + { + "name": "created_at", + "title": "Creation timestamp", + "type": "timestamp", + "nullable": "no" + }, + { + "name": "updated_at", + "title": "Last update timestamp", + "type": "timestamp", + "nullable": "no" + } + ], + "indexes": [ + { + "name": "idx_user_agent_config", + "idxtype": "index", + "idxfields": ["user_id"] + } + ], + "codes": [] +} \ No newline at end of file diff --git a/models/hermes_executions.json b/models/hermes_executions.json new file mode 100644 index 0000000..62bacf6 --- /dev/null +++ b/models/hermes_executions.json @@ -0,0 +1,133 @@ +{ + "summary": [ + { + "name": "hermes_executions", + "title": "Task execution records with user isolation", + "primary": "id", + "catelog": "relation" + } + ], + "fields": [ + { + "name": "id", + "title": "Execution ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Unique execution identifier" + }, + { + "name": "user_id", + "title": "User ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "User who owns this execution" + }, + { + "name": "workflow_id", + "title": "Workflow ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Parent workflow reference" + }, + { + "name": "task_id", + "title": "Task ID", + "type": "str", + "length": 32, + "nullable": "yes", + "comments": "Associated task reference" + }, + { + "name": "execution_status", + "title": "Execution Status", + "type": "str", + "length": 20, + "nullable": "no", + "default": "pending", + "comments": "pending, running, completed, failed, cancelled" + }, + { + "name": "start_time", + "title": "Start Time", + "type": "timestamp", + "nullable": "yes", + "comments": "Execution start timestamp" + }, + { + "name": "end_time", + "title": "End Time", + "type": "timestamp", + "nullable": "yes", + "comments": "Execution end timestamp" + }, + { + "name": "duration_seconds", + "title": "Duration Seconds", + "type": "long", + "nullable": "yes", + "comments": "Execution duration in seconds" + }, + { + "name": "result_json", + "title": "Result JSON", + "type": "text", + "nullable": "yes", + "comments": "JSON-encoded execution result" + }, + { + "name": "error_message", + "title": "Error Message", + "type": "text", + "nullable": "yes", + "comments": "Error message if execution failed" + }, + { + "name": "retry_count", + "title": "Retry Count", + "type": "long", + "nullable": "no", + "default": "0", + "comments": "Number of retries attempted" + }, + { + "name": "created_at", + "title": "Created At", + "type": "timestamp", + "nullable": "no", + "comments": "Creation timestamp" + }, + { + "name": "updated_at", + "title": "Updated At", + "type": "timestamp", + "nullable": "no", + "comments": "Last update timestamp" + } + ], + "indexes": [ + { + "name": "idx_hermes_executions_workflow", + "idxtype": "index", + "idxfields": ["workflow_id", "created_at"] + }, + { + "name": "idx_hermes_executions_task", + "idxtype": "index", + "idxfields": ["task_id", "created_at"] + }, + { + "name": "idx_hermes_executions_user", + "idxtype": "index", + "idxfields": ["user_id", "created_at"] + }, + { + "name": "idx_hermes_executions_status", + "idxtype": "index", + "idxfields": ["execution_status", "created_at"] + } + ], + "codes": [] +} diff --git a/models/hermes_tasks.json b/models/hermes_tasks.json new file mode 100644 index 0000000..7b0ae51 --- /dev/null +++ b/models/hermes_tasks.json @@ -0,0 +1,147 @@ +{ + "summary": [ + { + "name": "hermes_tasks", + "title": "Task definitions within workflows with user isolation", + "primary": "id", + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "Task ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Unique task identifier" + }, + { + "name": "user_id", + "title": "User ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "User who owns this task" + }, + { + "name": "workflow_id", + "title": "Workflow ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Parent workflow reference" + }, + { + "name": "task_name", + "title": "Task Name", + "type": "str", + "length": 200, + "nullable": "no", + "comments": "Human-readable task name" + }, + { + "name": "task_type", + "title": "Task Type", + "type": "str", + "length": 30, + "nullable": "no", + "comments": "skill, tool, memory, session_search, custom" + }, + { + "name": "skill_name", + "title": "Skill Name", + "type": "str", + "length": 100, + "nullable": "yes", + "comments": "Skill name for skill-type tasks" + }, + { + "name": "tool_name", + "title": "Tool Name", + "type": "str", + "length": 100, + "nullable": "yes", + "comments": "Tool name for tool-type tasks" + }, + { + "name": "parameters_json", + "title": "Parameters JSON", + "type": "text", + "nullable": "yes", + "comments": "JSON-encoded task parameters" + }, + { + "name": "depends_on", + "title": "Depends On", + "type": "str", + "length": 32, + "nullable": "yes", + "comments": "Task ID this task depends on" + }, + { + "name": "parallel_group", + "title": "Parallel Group", + "type": "str", + "length": 50, + "nullable": "yes", + "comments": "Group identifier for parallel execution" + }, + { + "name": "timeout_seconds", + "title": "Timeout Seconds", + "type": "long", + "nullable": "no", + "default": "300", + "comments": "Task timeout in seconds" + }, + { + "name": "retry_count", + "title": "Retry Count", + "type": "long", + "nullable": "no", + "default": "2", + "comments": "Task retry count" + }, + { + "name": "order_index", + "title": "Order Index", + "type": "long", + "nullable": "no", + "default": "0", + "comments": "Execution order within workflow" + }, + { + "name": "created_at", + "title": "Created At", + "type": "timestamp", + "nullable": "no", + "comments": "Creation timestamp" + }, + { + "name": "updated_at", + "title": "Updated At", + "type": "timestamp", + "nullable": "no", + "comments": "Last update timestamp" + } + ], + "indexes": [ + { + "name": "idx_hermes_tasks_workflow", + "idxtype": "index", + "idxfields": ["workflow_id", "order_index"] + }, + { + "name": "idx_hermes_tasks_user", + "idxtype": "index", + "idxfields": ["user_id", "workflow_id"] + }, + { + "name": "idx_hermes_tasks_depends", + "idxtype": "index", + "idxfields": ["depends_on"] + } + ], + "codes": [] +} diff --git a/models/hermes_workflows.json b/models/hermes_workflows.json new file mode 100644 index 0000000..0e489a1 --- /dev/null +++ b/models/hermes_workflows.json @@ -0,0 +1,112 @@ +{ + "summary": [ + { + "name": "hermes_workflows", + "title": "Workflow definitions with user isolation", + "primary": "id", + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "Workflow ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Unique workflow identifier" + }, + { + "name": "user_id", + "title": "User ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "User who owns this workflow" + }, + { + "name": "name", + "title": "Workflow Name", + "type": "str", + "length": 200, + "nullable": "no", + "comments": "Human-readable workflow name" + }, + { + "name": "description", + "title": "Description", + "type": "text", + "nullable": "yes", + "comments": "Workflow description" + }, + { + "name": "workflow_type", + "title": "Workflow Type", + "type": "str", + "length": 20, + "nullable": "no", + "default": "sequential", + "comments": "sequential, parallel, or hybrid" + }, + { + "name": "max_concurrent_tasks", + "title": "Max Concurrent Tasks", + "type": "long", + "nullable": "no", + "default": "3", + "comments": "Maximum concurrent tasks for parallel execution" + }, + { + "name": "timeout_seconds", + "title": "Timeout Seconds", + "type": "long", + "nullable": "no", + "default": "1800", + "comments": "Workflow timeout in seconds" + }, + { + "name": "retry_count", + "title": "Retry Count", + "type": "long", + "nullable": "no", + "default": "2", + "comments": "Default retry count for tasks" + }, + { + "name": "status", + "title": "Status", + "type": "str", + "length": 20, + "nullable": "no", + "default": "active", + "comments": "active, inactive, archived" + }, + { + "name": "created_at", + "title": "Created At", + "type": "timestamp", + "nullable": "no", + "comments": "Creation timestamp" + }, + { + "name": "updated_at", + "title": "Updated At", + "type": "timestamp", + "nullable": "no", + "comments": "Last update timestamp" + } + ], + "indexes": [ + { + "name": "idx_hermes_workflows_user", + "idxtype": "index", + "idxfields": ["user_id", "created_at"] + }, + { + "name": "idx_hermes_workflows_status", + "idxtype": "index", + "idxfields": ["user_id", "status"] + } + ], + "codes": [] +} diff --git a/models/task_dependencies.json b/models/task_dependencies.json new file mode 100644 index 0000000..ca7e728 --- /dev/null +++ b/models/task_dependencies.json @@ -0,0 +1,98 @@ +{ + "summary": [ + { + "name": "task_dependencies", + "title": "Task dependency relationships within workflows", + "primary": "id", + "catelog": "relation" + } + ], + "fields": [ + { + "name": "id", + "title": "Dependency ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Unique dependency identifier" + }, + { + "name": "user_id", + "title": "User ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "User who owns this dependency" + }, + { + "name": "workflow_id", + "title": "Workflow ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Parent workflow reference" + }, + { + "name": "dependent_task_id", + "title": "Dependent Task ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Task that depends on another" + }, + { + "name": "dependency_task_id", + "title": "Dependency Task ID", + "type": "str", + "length": 32, + "nullable": "no", + "comments": "Task that is depended upon" + }, + { + "name": "dependency_type", + "title": "Dependency Type", + "type": "str", + "length": 20, + "nullable": "no", + "default": "completion", + "comments": "completion, success, failure, data_available" + }, + { + "name": "created_at", + "title": "Created At", + "type": "timestamp", + "nullable": "no", + "comments": "Creation timestamp" + }, + { + "name": "updated_at", + "title": "Updated At", + "type": "timestamp", + "nullable": "no", + "comments": "Last update timestamp" + } + ], + "indexes": [ + { + "name": "idx_task_dep_workflow", + "idxtype": "index", + "idxfields": ["workflow_id"] + }, + { + "name": "idx_task_dep_dependent", + "idxtype": "index", + "idxfields": ["dependent_task_id"] + }, + { + "name": "idx_task_dep_dependency", + "idxtype": "index", + "idxfields": ["dependency_task_id"] + }, + { + "name": "idx_task_dep_unique", + "idxtype": "unique", + "idxfields": ["dependent_task_id", "dependency_task_id"] + } + ], + "codes": [] +} diff --git a/wwwroot/agent_config.ui b/wwwroot/agent_config.ui new file mode 100644 index 0000000..71944e8 --- /dev/null +++ b/wwwroot/agent_config.ui @@ -0,0 +1,32 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "css": "ios-scroll-area" + }, + "subwidgets": [ + { + "widgettype": "Toolbar", + "options": { + "css": "ios-navbar", + "items": [ + { + "text": "Configuration", + "icon": "settings", + "css": "ios-navbar-title", + "disabled": true + } + ] + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('harnessed_agent_config_view')}}", + "width": "100%", + "height": "100%" + } + } + ] +} diff --git a/wwwroot/ios_design.css b/wwwroot/ios_design.css new file mode 100644 index 0000000..2e24515 --- /dev/null +++ b/wwwroot/ios_design.css @@ -0,0 +1,114 @@ +/* + * iOS Design System for Hermes Modules + * iPhone-style UI with Apple HIG principles + * Applied via inline options and direct element styling + */ + +/* === iOS Design Tokens === */ +body { + background-color: #F2F2F7 !important; + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', sans-serif !important; + -webkit-font-smoothing: antialiased; + margin: 0; + padding: 0; +} + +/* === iOS Navigation Bar === */ +.ios-navbar { + background-color: rgba(249, 249, 249, 0.94) !important; + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 0.5px solid rgba(60, 60, 67, 0.3) !important; +} + +.ios-navbar-title { + font-size: 34px !important; + font-weight: 700 !important; + color: #000000 !important; + letter-spacing: 0.37px; +} + +.ios-navbar-subtitle { + font-size: 13px !important; + font-weight: 400 !important; + color: rgba(60, 60, 67, 0.6) !important; +} + +/* === iOS Card === */ +.ios-card { + background-color: #FFFFFF !important; + border-radius: 12px !important; + padding: 16px !important; + margin: 8px 16px !important; + box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.06) !important; + border: none !important; +} + +/* === iOS Scroll Area === */ +.ios-scroll-area { + -webkit-overflow-scrolling: touch; +} + +/* === iOS Tab Bar === */ +.ios-tabbar { + background-color: rgba(249, 249, 249, 0.94) !important; + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-top: 0.5px solid rgba(60, 60, 67, 0.3) !important; +} + +/* === iOS Stat Card === */ +.ios-stat-card { + background: linear-gradient(135deg, #007AFF, #5856D6) !important; + border-radius: 16px !important; + color: #FFFFFF !important; +} + +.ios-stat-value { + font-size: 34px !important; + font-weight: 700 !important; +} + +.ios-stat-label { + font-size: 13px !important; + font-weight: 500 !important; + opacity: 0.8; +} + +/* === iOS Group === */ +.ios-group { + background-color: #FFFFFF !important; + border-radius: 12px !important; + margin: 8px 16px !important; + overflow: hidden; +} + +/* === iOS Button === */ +.ios-btn { + background-color: #007AFF !important; + color: #FFFFFF !important; + border: none !important; + border-radius: 12px !important; + font-size: 17px !important; + font-weight: 600 !important; +} + +.ios-btn-secondary { + background-color: rgba(0, 122, 255, 0.12) !important; + color: #007AFF !important; +} + +/* === iOS Badge === */ +.ios-badge { + background-color: #FF3B30 !important; + color: #FFFFFF !important; + border-radius: 10px !important; + font-size: 12px !important; + font-weight: 600 !important; +} + +/* === iOS Status Dots === */ +.ios-status-active { color: #34C759 !important; } +.ios-status-pending { color: #FF9500 !important; } +.ios-status-failed { color: #FF3B30 !important; } +.ios-status-inactive { color: #8E8E93 !important; }