diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go
index c927976..41ca30d 100644
--- a/cmd/clawgo/cmd_gateway.go
+++ b/cmd/clawgo/cmd_gateway.go
@@ -129,6 +129,8 @@ func gatewayCmd() {
}
registryServer := api.NewServer(cfg.Gateway.Host, cfg.Gateway.Port, cfg.Gateway.Token, nodes.DefaultManager())
+ registryServer.SetGatewayVersion(version)
+ registryServer.SetWebUIVersion(version)
registryServer.SetConfigPath(getConfigPath())
registryServer.SetWorkspacePath(cfg.WorkspacePath())
registryServer.SetLogFilePath(cfg.LogFilePath())
diff --git a/cmd/clawgo/main.go b/cmd/clawgo/main.go
index c536ee0..27a2f5d 100644
--- a/cmd/clawgo/main.go
+++ b/cmd/clawgo/main.go
@@ -19,7 +19,7 @@ import (
//go:embed workspace
var embeddedFiles embed.FS
-var version = "0.1.0"
+var version = "dev"
var buildTime = "unknown"
const logo = "🦞"
diff --git a/config.example.json b/config.example.json
index 0414940..0b4d71c 100644
--- a/config.example.json
+++ b/config.example.json
@@ -73,6 +73,7 @@
"main": {
"enabled": true,
"type": "router",
+ "notify_main_policy": "final_only",
"display_name": "Main Agent",
"role": "orchestrator",
"system_prompt_file": "agents/main/AGENT.md",
@@ -85,7 +86,6 @@
"runtime": {
"proxy": "proxy",
"temperature": 0.2,
- "timeout_sec": 900,
"max_retries": 1,
"retry_backoff_ms": 1000,
"max_parallel_runs": 4
@@ -94,6 +94,7 @@
"coder": {
"enabled": true,
"type": "worker",
+ "notify_main_policy": "final_only",
"display_name": "Code Agent",
"role": "code",
"system_prompt_file": "agents/coder/AGENT.md",
@@ -106,7 +107,6 @@
"runtime": {
"proxy": "proxy",
"temperature": 0.2,
- "timeout_sec": 1200,
"max_retries": 1,
"retry_backoff_ms": 1000,
"max_task_chars": 20000,
@@ -117,6 +117,7 @@
"tester": {
"enabled": true,
"type": "worker",
+ "notify_main_policy": "on_blocked",
"display_name": "Test Agent",
"role": "test",
"system_prompt_file": "agents/tester/AGENT.md",
@@ -129,7 +130,6 @@
"runtime": {
"proxy": "proxy",
"temperature": 0.1,
- "timeout_sec": 1200,
"max_retries": 1,
"retry_backoff_ms": 1000,
"max_parallel_runs": 2
@@ -141,11 +141,11 @@
"transport": "node",
"node_id": "edge-dev",
"parent_agent_id": "main",
+ "notify_main_policy": "internal_only",
"display_name": "Edge Dev Main Agent",
"role": "remote_main",
"memory_namespace": "node.edge-dev.main",
"runtime": {
- "timeout_sec": 1200,
"max_retries": 1,
"retry_backoff_ms": 1000,
"max_parallel_runs": 1
diff --git a/pkg/api/server.go b/pkg/api/server.go
index 1edbd5f..b1fac5f 100644
--- a/pkg/api/server.go
+++ b/pkg/api/server.go
@@ -32,24 +32,26 @@ import (
)
type Server struct {
- addr string
- token string
- mgr *nodes.Manager
- server *http.Server
- configPath string
- workspacePath string
- logFilePath string
- onChat func(ctx context.Context, sessionKey, content string) (string, error)
- onChatHistory func(sessionKey string) []map[string]interface{}
- onConfigAfter func()
- onCron func(action string, args map[string]interface{}) (interface{}, error)
- onSubagents func(ctx context.Context, action string, args map[string]interface{}) (interface{}, error)
- webUIDir string
- ekgCacheMu sync.Mutex
- ekgCachePath string
- ekgCacheStamp time.Time
- ekgCacheSize int64
- ekgCacheRows []map[string]interface{}
+ addr string
+ token string
+ mgr *nodes.Manager
+ server *http.Server
+ gatewayVersion string
+ webuiVersion string
+ configPath string
+ workspacePath string
+ logFilePath string
+ onChat func(ctx context.Context, sessionKey, content string) (string, error)
+ onChatHistory func(sessionKey string) []map[string]interface{}
+ onConfigAfter func()
+ onCron func(action string, args map[string]interface{}) (interface{}, error)
+ onSubagents func(ctx context.Context, action string, args map[string]interface{}) (interface{}, error)
+ webUIDir string
+ ekgCacheMu sync.Mutex
+ ekgCachePath string
+ ekgCacheStamp time.Time
+ ekgCacheSize int64
+ ekgCacheRows []map[string]interface{}
}
func NewServer(host string, port int, token string, mgr *nodes.Manager) *Server {
@@ -79,7 +81,9 @@ func (s *Server) SetCronHandler(fn func(action string, args map[string]interface
func (s *Server) SetSubagentHandler(fn func(ctx context.Context, action string, args map[string]interface{}) (interface{}, error)) {
s.onSubagents = fn
}
-func (s *Server) SetWebUIDir(dir string) { s.webUIDir = strings.TrimSpace(dir) }
+func (s *Server) SetWebUIDir(dir string) { s.webUIDir = strings.TrimSpace(dir) }
+func (s *Server) SetGatewayVersion(v string) { s.gatewayVersion = strings.TrimSpace(v) }
+func (s *Server) SetWebUIVersion(v string) { s.webuiVersion = strings.TrimSpace(v) }
func (s *Server) Start(ctx context.Context) error {
if s.mgr == nil {
@@ -583,8 +587,8 @@ func (s *Server) handleWebUIVersion(w http.ResponseWriter, r *http.Request) {
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
- "gateway_version": gatewayBuildVersion(),
- "webui_version": detectWebUIVersion(strings.TrimSpace(s.webUIDir)),
+ "gateway_version": firstNonEmptyString(s.gatewayVersion, gatewayBuildVersion()),
+ "webui_version": firstNonEmptyString(s.webuiVersion, detectWebUIVersion(strings.TrimSpace(s.webUIDir))),
})
}
@@ -1334,24 +1338,17 @@ func gatewayBuildVersion() string {
}
func detectWebUIVersion(webUIDir string) string {
- if strings.TrimSpace(webUIDir) == "" {
- return "unknown"
- }
- assets := filepath.Join(webUIDir, "assets")
- entries, err := os.ReadDir(assets)
- if err != nil {
- return "unknown"
- }
- for _, e := range entries {
- name := e.Name()
- if strings.HasPrefix(name, "index-") && strings.HasSuffix(name, ".js") {
- mid := strings.TrimSuffix(strings.TrimPrefix(name, "index-"), ".js")
- if mid != "" {
- return mid
- }
+ _ = webUIDir
+ return "dev"
+}
+
+func firstNonEmptyString(values ...string) string {
+ for _, v := range values {
+ if strings.TrimSpace(v) != "" {
+ return strings.TrimSpace(v)
}
}
- return "unknown"
+ return ""
}
func detectLocalIP() string {
@@ -2715,6 +2712,9 @@ func (s *Server) handleWebUIEKGStats(w http.ResponseWriter, r *http.Request) {
errSig := strings.TrimSpace(fmt.Sprintf("%v", row["errsig"]))
source := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", row["source"])))
channel := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", row["channel"])))
+ if source == "heartbeat" {
+ continue
+ }
if source == "" {
source = "unknown"
}
@@ -2723,33 +2723,22 @@ func (s *Server) handleWebUIEKGStats(w http.ResponseWriter, r *http.Request) {
}
sourceStats[source]++
channelStats[channel]++
- isHeartbeat := source == "heartbeat"
if provider != "" {
switch status {
case "success":
providerScore[provider] += 1
- if !isHeartbeat {
- providerScoreWorkload[provider] += 1
- }
+ providerScoreWorkload[provider] += 1
case "suppressed":
providerScore[provider] += 0.2
- if !isHeartbeat {
- providerScoreWorkload[provider] += 0.2
- }
+ providerScoreWorkload[provider] += 0.2
case "error":
providerScore[provider] -= 1
- if !isHeartbeat {
- providerScoreWorkload[provider] -= 1
- }
+ providerScoreWorkload[provider] -= 1
}
}
if errSig != "" && status == "error" {
errSigCount[errSig]++
- if isHeartbeat {
- errSigHeartbeat[errSig]++
- } else {
- errSigWorkload[errSig]++
- }
+ errSigWorkload[errSig]++
}
}
toTopScore := func(m map[string]float64, n int) []kv {
diff --git a/webui/src/components/Header.tsx b/webui/src/components/Header.tsx
index 0066378..9c9fbde 100644
--- a/webui/src/components/Header.tsx
+++ b/webui/src/components/Header.tsx
@@ -5,7 +5,7 @@ import { useAppContext } from '../context/AppContext';
const Header: React.FC = () => {
const { t, i18n } = useTranslation();
- const { isGatewayOnline, setSidebarOpen } = useAppContext();
+ const { isGatewayOnline, setSidebarOpen, sidebarCollapsed } = useAppContext();
const toggleLang = () => {
const nextLang = i18n.language === 'en' ? 'zh' : 'en';
@@ -18,10 +18,18 @@ const Header: React.FC = () => {
-
-
+
+
+
+
+ {!sidebarCollapsed && (
+
{t('appName')}
+ )}
-
{t('appName')}
+
+
+
+
{t('appName')}
diff --git a/webui/src/components/Layout.tsx b/webui/src/components/Layout.tsx
index f25206f..e107459 100644
--- a/webui/src/components/Layout.tsx
+++ b/webui/src/components/Layout.tsx
@@ -25,7 +25,7 @@ const Layout: React.FC = () => {
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
- className="absolute inset-0 overflow-y-auto"
+ className="absolute inset-0 overflow-y-auto overflow-x-hidden"
>
diff --git a/webui/src/components/NavItem.tsx b/webui/src/components/NavItem.tsx
index 0d58f0d..6044b38 100644
--- a/webui/src/components/NavItem.tsx
+++ b/webui/src/components/NavItem.tsx
@@ -5,19 +5,21 @@ interface NavItemProps {
icon: React.ReactNode;
label: string;
to: string;
+ collapsed?: boolean;
}
-const NavItem: React.FC
= ({ icon, label, to }) => (
+const NavItem: React.FC = ({ icon, label, to, collapsed = false }) => (
`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 ${
+ title={collapsed ? label : undefined}
+ className={({ isActive }) => `w-full flex items-center ${collapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 ${
isActive
? 'bg-indigo-500/15 text-indigo-300 border border-indigo-500/30'
: 'text-zinc-400 hover:bg-zinc-800/60 hover:text-zinc-200 border border-transparent'
}`}
>
{icon}
- {label}
+ {!collapsed && label}
);
diff --git a/webui/src/components/Sidebar.tsx b/webui/src/components/Sidebar.tsx
index fcdb5c1..ee323c1 100644
--- a/webui/src/components/Sidebar.tsx
+++ b/webui/src/components/Sidebar.tsx
@@ -1,72 +1,92 @@
import React from 'react';
-import { LayoutDashboard, MessageSquare, Settings, Clock, Terminal, Zap, FolderOpen, ClipboardList, BrainCircuit, Hash, Bot, Boxes } from 'lucide-react';
+import { LayoutDashboard, MessageSquare, Settings, Clock, Terminal, Zap, FolderOpen, ClipboardList, BrainCircuit, Hash, Bot, Boxes, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
import NavItem from './NavItem';
const Sidebar: React.FC = () => {
const { t } = useTranslation();
- const { token, setToken, sidebarOpen } = useAppContext();
+ const { token, setToken, sidebarOpen, sidebarCollapsed, setSidebarCollapsed } = useAppContext();
const sections = [
{
- title: t('sidebarCore'),
+ title: t('sidebarMain'),
items: [
{ icon: , label: t('dashboard'), to: '/' },
{ icon: , label: t('chat'), to: '/chat' },
{ icon: , label: t('subagentsRuntime'), to: '/subagents' },
- { icon: , label: t('logs'), to: '/logs' },
- { icon: , label: t('logCodes'), to: '/log-codes' },
- { icon: , label: t('skills'), to: '/skills' },
],
},
{
- title: t('sidebarSystem'),
- items: [
- { icon: , label: t('config'), to: '/config' },
- { icon: , label: t('cronJobs'), to: '/cron' },
- { icon: , label: t('memory'), to: '/memory' },
- { icon: , label: t('subagentProfiles'), to: '/subagent-profiles' },
- ],
- },
- {
- title: t('sidebarOps'),
+ title: t('sidebarRuntime'),
items: [
{ icon: , label: t('taskAudit'), to: '/task-audit' },
+ { icon: , label: t('logs'), to: '/logs' },
+ { icon: , label: t('ekg'), to: '/ekg' },
],
},
{
- title: t('sidebarInsights'),
+ title: t('sidebarConfig'),
items: [
- { icon: , label: t('ekg'), to: '/ekg' },
+ { icon: , label: t('config'), to: '/config' },
+ { icon: , label: t('subagentProfiles'), to: '/subagent-profiles' },
+ { icon: , label: t('cronJobs'), to: '/cron' },
+ ],
+ },
+ {
+ title: t('sidebarKnowledge'),
+ items: [
+ { icon: , label: t('memory'), to: '/memory' },
+ { icon: , label: t('skills'), to: '/skills' },
+ { icon: , label: t('logCodes'), to: '/log-codes' },
],
},
];
return (
-