diff --git a/models/harnessed_agent_config.json b/models/harnessed_agent_config.json index d7b05e1..d779df3 100644 --- a/models/harnessed_agent_config.json +++ b/models/harnessed_agent_config.json @@ -80,6 +80,29 @@ "nullable": "no", "default": "30" }, + { + "name": "default_model", + "title": "Default LLM model for agent tasks", + "type": "str", + "length": 64, + "nullable": "yes", + "default": "qwen3-max" + }, + { + "name": "default_temperature", + "title": "Default temperature for LLM calls", + "type": "float", + "nullable": "no", + "default": "0.7" + }, + { + "name": "enable_streaming", + "title": "Enable streaming response for LLM calls", + "type": "str", + "length": "1", + "nullable": "no", + "default": "1" + }, { "name": "created_at", "title": "Creation timestamp", diff --git a/wwwroot/agent_config.ui b/wwwroot/agent_config.ui index 71944e8..25ce677 100644 --- a/wwwroot/agent_config.ui +++ b/wwwroot/agent_config.ui @@ -12,10 +12,14 @@ "css": "ios-navbar", "items": [ { - "text": "Configuration", - "icon": "settings", - "css": "ios-navbar-title", - "disabled": true + "widgettype": "Text", + "options": { + "text": "Configuration", + "css": "ios-navbar-title", + "fontSize": "18px", + "fontWeight": "bold", + "color": "#059669" + } } ] } @@ -23,7 +27,7 @@ { "widgettype": "urlwidget", "options": { - "url": "{{entire_url('harnessed_agent_config_view')}}", + "url": "{{entire_url('agent_config_form.ui')}}", "width": "100%", "height": "100%" } diff --git a/wwwroot/agent_config_form.ui b/wwwroot/agent_config_form.ui new file mode 100644 index 0000000..cccdcc2 --- /dev/null +++ b/wwwroot/agent_config_form.ui @@ -0,0 +1,114 @@ +{ + "widgettype": "TabPanel", + "options": { + "tab_pos": "top", + "items": [ + { + "name": "agent_config", + "label": "代理配置", + "icon": "settings", + "content": { + "widgettype": "VBox", + "options": { + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Form", + "id": "agent_config_form", + "options": { + "data_url": "{{entire_url('../api/agent_config_get.dspy')}}", + "data_method": "GET", + "submit_url": "{{entire_url('../api/agent_config_save.dspy')}}", + "method": "POST", + "layout": "vertical", + "fields": [ + { + "name": "work_dir", + "label": "工作目录", + "uitype": "text", + "required": true + }, + { + "name": "skills_path", + "label": "技能路径", + "uitype": "text", + "required": true + }, + { + "name": "max_memory_tokens", + "label": "最大记忆 token 数", + "uitype": "int", + "required": true + }, + { + "name": "default_priority", + "label": "默认优先级", + "uitype": "int", + "required": true + }, + { + "name": "high_priority_threshold", + "label": "高优先级阈值", + "uitype": "int", + "required": true + }, + { + "name": "low_priority_threshold", + "label": "低优先级阈值", + "uitype": "int", + "required": true + }, + { + "name": "auto_cleanup_enabled", + "label": "启用自动清理", + "uitype": "check" + }, + { + "name": "min_retention_days", + "label": "最小保留天数", + "uitype": "int", + "required": true + }, + { + "name": "default_model", + "label": "默认模型", + "uitype": "text", + "placeholder": "qwen3-max" + }, + { + "name": "default_temperature", + "label": "默认温度", + "uitype": "float", + "required": true + }, + { + "name": "enable_streaming", + "label": "启用流式输出", + "uitype": "check" + } + ], + "buttons": [ + { + "type": "submit", + "label": "保存代理配置", + "variant": "primary" + } + ], + "maxWidth": "500px" + }, + "binds": [ + { + "wid": "self", + "event": "submited", + "actiontype": "script", + "script": "await bricks.show_resp_message_or_error(event.params)" + } + ] + } + ] + } + } + ] + } +} diff --git a/wwwroot/agent_console.ui b/wwwroot/agent_console.ui new file mode 100644 index 0000000..cddef35 --- /dev/null +++ b/wwwroot/agent_console.ui @@ -0,0 +1,88 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "16px", + "spacing": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "代理执行控制台", + "fontSize": "20px", + "fontWeight": "bold", + "color": "#059669" + } + }, + { + "widgettype": "Form", + "id": "agent_execute_form", + "options": { + "submit_url": "{{entire_url('api/agent_execute.dspy')}}", + "method": "POST", + "layout": "vertical", + "fields": [ + { + "name": "tool_name", + "label": "工具名称", + "uitype": "text", + "required": true, + "placeholder": "例如: memory, skill_view, terminal, read_file" + }, + { + "name": "parameters", + "label": "工具参数 (JSON)", + "uitype": "textarea", + "rows": 6, + "placeholder": "{\"key\": \"value\"}" + } + ], + "buttons": [ + { + "type": "submit", + "label": "执行工具", + "variant": "primary" + } + ], + "maxWidth": "700px" + }, + "binds": [ + { + "wid": "self", + "event": "submited", + "actiontype": "script", + "script": "await bricks.show_resp_message_or_error(event.params)" + } + ] + }, + { + "widgettype": "VBox", + "options": { + "width": "100%", + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "可用工具列表", + "fontSize": "16px", + "fontWeight": "bold", + "color": "#374151", + "marginBottom": "8px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "memory: 保存和检索记忆 | skill_view: 查看技能 | skill_manage: 管理技能 | session_search: 搜索历史会话 | delegate_task: 委托任务 | execute_code: 执行代码 | terminal: 运行命令 | read_file: 读取文件 | write_file: 写入文件 | search_files: 搜索文件 | patch: 修改文件 | vision_analyze: 图像分析 | cronjob: 定时任务 | todo: 任务管理", + "fontSize": "12px", + "color": "#6B7280" + } + } + ] + } + ] +} diff --git a/wwwroot/api/agent_config_get.dspy b/wwwroot/api/agent_config_get.dspy new file mode 100644 index 0000000..f12661a --- /dev/null +++ b/wwwroot/api/agent_config_get.dspy @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Get agent configuration for current user""" +import json + +result = {'success': False, 'config': {}} + +try: + dbname = get_module_dbname('harnessed_agent') + user_id = await get_user() + + sql = """SELECT * FROM harnessed_agent_config + WHERE user_id = ${user_id}$ + ORDER BY updated_at DESC + LIMIT 1""" + + async with DBPools().sqlorContext(dbname) as sor: + rows = await sor.sqlExe(sql, {'user_id': user_id}) + if rows and len(rows) > 0: + config = dict(rows[0]) + config['auto_cleanup_enabled'] = config.get('auto_cleanup_enabled', '1') == '1' + config['enable_streaming'] = config.get('enable_streaming', '1') == '1' + result['config'] = config + else: + result['config'] = { + '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, + 'default_model': 'qwen3-max', + 'default_temperature': 0.7, + 'enable_streaming': True + } + result['success'] = True + +except Exception as e: + result['error'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str) diff --git a/wwwroot/api/agent_config_save.dspy b/wwwroot/api/agent_config_save.dspy new file mode 100644 index 0000000..1c3e51c --- /dev/null +++ b/wwwroot/api/agent_config_save.dspy @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Save agent configuration for current user""" +import json, uuid, time + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + dbname = get_module_dbname('harnessed_agent') + user_id = await get_user() + now = time.strftime('%Y-%m-%d %H:%M:%S') + + work_dir = params_kw.get('work_dir', './hermes_work').strip() + skills_path = params_kw.get('skills_path', '~/.hermes/skills').strip() + max_memory_tokens = int(params_kw.get('max_memory_tokens', 2000)) + default_priority = int(params_kw.get('default_priority', 50)) + high_priority_threshold = int(params_kw.get('high_priority_threshold', 70)) + low_priority_threshold = int(params_kw.get('low_priority_threshold', 30)) + auto_cleanup_enabled = '1' if params_kw.get('auto_cleanup_enabled') == '1' else '0' + min_retention_days = int(params_kw.get('min_retention_days', 30)) + default_model = params_kw.get('default_model', 'qwen3-max').strip() + default_temperature = float(params_kw.get('default_temperature', 0.7)) + enable_streaming = '1' if params_kw.get('enable_streaming') == '1' else '0' + + async with DBPools().sqlorContext(dbname) as sor: + rows = await sor.sqlExe("SELECT id FROM harnessed_agent_config WHERE user_id = ${user_id}$", {'user_id': user_id}) + + if rows and len(rows) > 0: + config_id = rows[0]['id'] + await sor.sqlExe("""UPDATE harnessed_agent_config SET + work_dir = ${work_dir}$, + skills_path = ${skills_path}$, + max_memory_tokens = ${max_memory_tokens}$, + default_priority = ${default_priority}$, + high_priority_threshold = ${high_priority_threshold}$, + low_priority_threshold = ${low_priority_threshold}$, + auto_cleanup_enabled = ${auto_cleanup_enabled}$, + min_retention_days = ${min_retention_days}$, + default_model = ${default_model}$, + default_temperature = ${default_temperature}$, + enable_streaming = ${enable_streaming}$, + updated_at = ${updated_at}$ + WHERE id = ${id}$""", { + 'id': config_id, + 'work_dir': work_dir, + 'skills_path': skills_path, + 'max_memory_tokens': max_memory_tokens, + 'default_priority': default_priority, + 'high_priority_threshold': high_priority_threshold, + 'low_priority_threshold': low_priority_threshold, + 'auto_cleanup_enabled': auto_cleanup_enabled, + 'min_retention_days': min_retention_days, + 'default_model': default_model, + 'default_temperature': default_temperature, + 'enable_streaming': enable_streaming, + 'updated_at': now + }) + else: + config_id = str(uuid.uuid4()).replace('-', '')[:32] + await sor.sqlExe("""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, default_model, + default_temperature, enable_streaming, created_at, updated_at) + VALUES (${id}$, ${user_id}$, ${work_dir}$, ${skills_path}$, ${max_memory_tokens}$, + ${default_priority}$, ${high_priority_threshold}$, ${low_priority_threshold}$, + ${auto_cleanup_enabled}$, ${min_retention_days}$, ${default_model}$, + ${default_temperature}$, ${enable_streaming}$, ${created_at}$, ${updated_at}$)""", { + 'id': config_id, + 'user_id': user_id, + 'work_dir': work_dir, + 'skills_path': skills_path, + 'max_memory_tokens': max_memory_tokens, + 'default_priority': default_priority, + 'high_priority_threshold': high_priority_threshold, + 'low_priority_threshold': low_priority_threshold, + 'auto_cleanup_enabled': auto_cleanup_enabled, + 'min_retention_days': min_retention_days, + 'default_model': default_model, + 'default_temperature': default_temperature, + 'enable_streaming': enable_streaming, + 'created_at': now, + 'updated_at': now + }) + + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '配置保存成功', 'type': 'success'}} + +except Exception as e: + result['options'] = {'title': 'Error', 'message': '保存失败: ' + str(e), 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/agent_execute.dspy b/wwwroot/api/agent_execute.dspy new file mode 100644 index 0000000..52215b9 --- /dev/null +++ b/wwwroot/api/agent_execute.dspy @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Execute agent task - call tool/skill with given parameters""" +import json, time + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + tool_name = params_kw.get('tool_name', '').strip() + parameters = params_kw.get('parameters', '{}').strip() + + if not tool_name: + result['options'] = {'title': 'Error', 'message': '请指定工具名称', 'type': 'error'} + else: + user_id = await get_user() + + try: + tool_params = json.loads(parameters) + except: + tool_params = {} + + context = {'user_id': user_id} + + # Call agent tool execution + exec_result = await harnessed_execute_tool( + tool_name=tool_name, + parameters=tool_params, + context=context + ) + + if exec_result.get('success'): + output = json.dumps(exec_result, ensure_ascii=False, indent=2) + result = { + 'widgettype': 'Message', + 'options': { + 'title': '执行成功', + 'message': f'工具: {tool_name}\n\n结果:\n{output}', + 'type': 'success' + } + } + else: + result['options'] = { + 'title': '执行失败', + 'message': exec_result.get('error', '未知错误'), + 'type': 'error' + } + +except Exception as e: + result['options'] = {'title': 'Error', 'message': '执行异常: ' + str(e), 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui index 37581a1..fe05cae 100644 --- a/wwwroot/menu.ui +++ b/wwwroot/menu.ui @@ -10,6 +10,21 @@ "cwidth":10, "items":[ {% if get_user() %} + { + "name":"hermes_agent", + "label":"代理控制台", + "url":"{{entire_url('/harnessed_agent/hermes_agent.ui')}}" + }, + { + "name":"agent_console", + "label":"代理执行", + "url":"{{entire_url('/harnessed_agent/agent_console.ui')}}" + }, + { + "name":"agent_config", + "label":"代理配置", + "url":"{{entire_url('/harnessed_agent/agent_config.ui')}}" + }, { "name":"sessions", "label":"会话管理", @@ -25,31 +40,21 @@ "label":"记忆管理", "url":"{{entire_url('/harnessed_agent/hermes_memory')}}" }, - { - "name":"tasks", - "label":"任务管理", - "url":"{{entire_url('/harnessed_agent/hermes_tasks')}}" - }, { "name":"workflows", "label":"工作流管理", "url":"{{entire_url('/harnessed_agent/hermes_workflows')}}" }, + { + "name":"tasks", + "label":"任务管理", + "url":"{{entire_url('/harnessed_agent/hermes_tasks')}}" + }, { "name":"remote_skills", "label":"远程技能", "url":"{{entire_url('/harnessed_agent/harnessed_remote_skills')}}" }, - { - "name":"agent_config", - "label":"代理配置", - "url":"{{entire_url('/harnessed_agent/harnessed_agent_config_view')}}" - }, - { - "name":"hermes_agent", - "label":"代理控制台", - "url":"{{entire_url('/harnessed_agent/hermes_agent.ui')}}" - }, { "name":"tools", "label":"工具管理",