hermes-control-interface-dashboard

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hermes Control Interface Dashboard

Hermes控制界面仪表盘

Skill by ara.so — Hermes Skills collection.
A self-hosted web dashboard for managing the Hermes AI agent stack. Provides browser-based terminal, file explorer, session management, cron jobs, multi-agent gateway control, token analytics, and RBAC — all behind password authentication.
Stack: Vanilla JS + Vite, Node.js, Express, WebSocket, xterm.js
Port: 10272 (default)
Version: 3.5.0
ara.so提供的Skill — Hermes Skills合集。
一款用于管理Hermes AI Agent栈的自托管Web仪表盘。提供基于浏览器的终端、文件浏览器、会话管理、定时任务、多Agent网关控制、令牌分析和RBAC功能——所有功能均受密码认证保护。
技术栈: Vanilla JS + Vite, Node.js, Express, WebSocket, xterm.js
端口: 10272(默认)
版本: 3.5.0

Installation

安装

Prerequisites

前置要求

bash
undefined
bash
undefined

Required: Node.js 18+, build tools for node-pty

必需:Node.js 18+,node-pty的构建工具

sudo apt-get install -y python3 make g++ # Ubuntu/Debian
sudo apt-get install -y python3 make g++ # Ubuntu/Debian系统

OR

brew install python3 # macOS
undefined
brew install python3 # macOS系统
undefined

Manual Installation (Recommended)

手动安装(推荐)

bash
undefined
bash
undefined

Clone repository

克隆仓库

git clone https://github.com/xaspx/hermes-control-interface.git cd hermes-control-interface
git clone https://github.com/xaspx/hermes-control-interface.git cd hermes-control-interface

Install dependencies

安装依赖

npm install
npm install

Configure environment

配置环境

cp .env.example .env
cp .env.example .env

Generate secure secret

生成安全密钥

openssl rand -hex 32 # Copy output for HERMES_CONTROL_SECRET
openssl rand -hex 32 # 将输出内容复制到HERMES_CONTROL_SECRET中

Edit .env with your settings

编辑.env文件配置参数

nano .env
undefined
nano .env
undefined

Environment Configuration

环境配置

Minimal
.env
:
bash
undefined
最简
.env
配置:
bash
undefined

Required

必填项

HERMES_CONTROL_PASSWORD=your-secure-password-here HERMES_CONTROL_SECRET=<output-from-openssl-rand-hex-32>
HERMES_CONTROL_PASSWORD=your-secure-password-here HERMES_CONTROL_SECRET=<output-from-openssl-rand-hex-32>

Optional

可选项

PORT=10272 NODE_ENV=production HERMES_CONTROL_ROOTS=/.hermes,/Documents # File explorer roots
undefined
PORT=10272 NODE_ENV=production HERMES_CONTROL_ROOTS=/.hermes,/Documents # 文件浏览器根目录
undefined

Build and Run

构建与运行

bash
undefined
bash
undefined

Build frontend

构建前端

npm run build
npm run build

Start server

启动服务

npm start
npm start

Development mode (auto-reload)

开发模式(自动重载)

npm run dev
undefined
npm run dev
undefined

Production Deployment (Systemd)

生产部署(Systemd)

bash
undefined
bash
undefined

Create systemd service

创建systemd服务文件

sudo tee /etc/systemd/system/hermes-control.service > /dev/null <<EOF [Unit] Description=Hermes Control Interface After=network.target
[Service] Type=simple User=root WorkingDirectory=$(pwd) ExecStart=/usr/bin/node server.js Restart=always Environment=NODE_ENV=production
[Install] WantedBy=multi-user.target EOF
sudo tee /etc/systemd/system/hermes-control.service > /dev/null <<EOF [Unit] Description=Hermes Control Interface After=network.target
[Service] Type=simple User=root WorkingDirectory=$(pwd) ExecStart=/usr/bin/node server.js Restart=always Environment=NODE_ENV=production
[Install] WantedBy=multi-user.target EOF

Enable and start

启用并启动服务

sudo systemctl enable hermes-control sudo systemctl start hermes-control sudo systemctl status hermes-control
undefined
sudo systemctl enable hermes-control sudo systemctl start hermes-control sudo systemctl status hermes-control
undefined

Core Features

核心功能

Authentication & RBAC

认证与RBAC

HCI supports multi-user authentication with role-based access control:
Roles:
  • admin
    — Full access (all 20 permissions)
  • viewer
    — Read-only access
  • custom
    — Granular permission selection
Key Permissions:
  • agents:read
    ,
    agents:write
    ,
    agents:delete
  • chat:read
    ,
    chat:write
  • sessions:read
    ,
    sessions:write
    ,
    sessions:delete
  • config:read
    ,
    config:write
  • gateway:control
    ,
    gateway:logs
  • files:read
    ,
    files:write
  • terminal:exec
  • users:manage
  • system:maintain
  • cron:manage
Creating Users (via Maintenance → Users):
javascript
// API endpoint: POST /api/users
{
  "username": "alice",
  "password": "secure-password",
  "role": "viewer"
}
HCI支持基于角色的访问控制(RBAC)多用户认证:
角色:
  • admin
    — 完全访问权限(全部20项权限)
  • viewer
    — 只读访问权限
  • custom
    — 细粒度权限选择
关键权限:
  • agents:read
    ,
    agents:write
    ,
    agents:delete
  • chat:read
    ,
    chat:write
  • sessions:read
    ,
    sessions:write
    ,
    sessions:delete
  • config:read
    ,
    config:write
  • gateway:control
    ,
    gateway:logs
  • files:read
    ,
    files:write
  • terminal:exec
  • users:manage
  • system:maintain
  • cron:manage
创建用户(通过维护 → 用户页面):
javascript
// API端点: POST /api/users
{
  "username": "alice",
  "password": "secure-password",
  "role": "viewer"
}

Multi-Agent Gateway Management

多Agent网关管理

Profile Structure:
~/.hermes/
  ├── profiles/
  │   ├── default/
  │   │   └── config.yaml
  │   ├── production/
  │   │   └── config.yaml
  │   └── testing/
  │       └── config.yaml
API: List Profiles
javascript
// GET /api/agents/profiles
fetch('/api/agents/profiles', {
  headers: { 
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken 
  }
})
.then(res => res.json())
.then(data => {
  // data.profiles = [{ name: 'default', isDefault: true, status: 'running', model: 'hermes-3' }]
});
API: Start/Stop Gateway
javascript
// POST /api/agents/gateway/start
fetch('/api/agents/gateway/start', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({ profile: 'production' })
})
.then(res => res.json())
.then(data => console.log(data.message)); // "Gateway started for production"
API: Create Profile
javascript
// POST /api/agents/profiles
fetch('/api/agents/profiles', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    name: 'staging',
    cloneFrom: 'default'  // Optional: clone existing config
  })
});
配置文件结构:
~/.hermes/
  ├── profiles/
  │   ├── default/
  │   │   └── config.yaml
  │   ├── production/
  │   │   └── config.yaml
  │   └── testing/
  │       └── config.yaml
API: 列出配置文件
javascript
// GET /api/agents/profiles
fetch('/api/agents/profiles', {
  headers: { 
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken 
  }
})
.then(res => res.json())
.then(data => {
  // data.profiles = [{ name: 'default', isDefault: true, status: 'running', model: 'hermes-3' }]
});
API: 启动/停止网关
javascript
// POST /api/agents/gateway/start
fetch('/api/agents/gateway/start', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({ profile: 'production' })
})
.then(res => res.json())
.then(data => console.log(data.message)); // "Gateway started for production"
API: 创建配置文件
javascript
// POST /api/agents/profiles
fetch('/api/agents/profiles', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    name: 'staging',
    cloneFrom: 'default'  // 可选:克隆现有配置
  })
});

Chat Interface

聊天界面

Session Management:
javascript
// POST /api/chat/send
const sendMessage = async (message, sessionId = null) => {
  const args = sessionId 
    ? ['--continue', sessionId, message]
    : ['--continue', '', message];  // Empty string creates new session

  const response = await fetch('/api/chat/send', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${sessionToken}`,
      'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify({
      profile: 'default',
      args: args,
      suppressBanner: true  // Use -Q flag for clean output
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    const chunk = decoder.decode(value);
    const lines = chunk.split('\n');
    
    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));
        
        if (data.type === 'session_id') {
          console.log('Session ID:', data.sessionId);
        } else if (data.type === 'tool_call') {
          console.log('Tool:', data.tool, 'Status:', data.status);
        } else if (data.type === 'output') {
          console.log('Output:', data.text);
        }
      }
    }
  }
};

// Usage
await sendMessage('What is the weather?');  // New session
await sendMessage('And tomorrow?', 'abc123');  // Continue session
List Sessions:
javascript
// GET /api/chat/sessions?profile=default
fetch('/api/chat/sessions?profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  // data.sessions = [{ id: 'abc123', title: 'Weather discussion', timestamp: '2026-05-17...' }]
});
Rename Session:
javascript
// PUT /api/chat/sessions/:sessionId
fetch('/api/chat/sessions/abc123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({ title: 'Weather Research' })
});
会话管理:
javascript
// POST /api/chat/send
const sendMessage = async (message, sessionId = null) => {
  const args = sessionId 
    ? ['--continue', sessionId, message]
    : ['--continue', '', message];  // 空字符串表示创建新会话

  const response = await fetch('/api/chat/send', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${sessionToken}`,
      'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify({
      profile: 'default',
      args: args,
      suppressBanner: true  // 使用-Q标志获取简洁输出
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    const chunk = decoder.decode(value);
    const lines = chunk.split('\n');
    
    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));
        
        if (data.type === 'session_id') {
          console.log('会话ID:', data.sessionId);
        } else if (data.type === 'tool_call') {
          console.log('工具:', data.tool, '状态:', data.status);
        } else if (data.type === 'output') {
          console.log('输出:', data.text);
        }
      }
    }
  }
};

// 使用示例
await sendMessage('今天天气如何?');  // 创建新会话
await sendMessage('明天呢?', 'abc123');  // 继续现有会话
列出会话:
javascript
// GET /api/chat/sessions?profile=default
fetch('/api/chat/sessions?profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  // data.sessions = [{ id: 'abc123', title: 'Weather discussion', timestamp: '2026-05-17...' }]
});
重命名会话:
javascript
// PUT /api/chat/sessions/:sessionId
fetch('/api/chat/sessions/abc123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({ title: '天气研究' })
});

Token Analytics

令牌分析

Get Usage Stats:
javascript
// GET /api/usage/stats?range=7d&profile=default
fetch('/api/usage/stats?range=7d&profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('Sessions:', data.totalSessions);
  console.log('Tokens:', data.totalTokens);
  console.log('Cost:', data.estimatedCost);
  console.log('Models:', data.modelBreakdown);
  // modelBreakdown: [{ model: 'hermes-3', sessions: 42, tokens: 150000, avgTokens: 3571 }]
  console.log('Platforms:', data.platformBreakdown);
  // platformBreakdown: [{ platform: 'CLI', count: 30 }, { platform: 'Telegram', count: 12 }]
  console.log('Top Tools:', data.topTools);
  // topTools: [{ tool: 'web_search', calls: 15, success_rate: 0.93 }]
});
Query Ranges:
today
,
7d
,
30d
,
90d
获取使用统计:
javascript
// GET /api/usage/stats?range=7d&profile=default
fetch('/api/usage/stats?range=7d&profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('会话数:', data.totalSessions);
  console.log('令牌数:', data.totalTokens);
  console.log('预估成本:', data.estimatedCost);
  console.log('模型分布:', data.modelBreakdown);
  // modelBreakdown: [{ model: 'hermes-3', sessions: 42, tokens: 150000, avgTokens: 3571 }]
  console.log('平台分布:', data.platformBreakdown);
  // platformBreakdown: [{ platform: 'CLI', count: 30 }, { platform: 'Telegram', count: 12 }]
  console.log('高频工具:', data.topTools);
  // topTools: [{ tool: 'web_search', calls: 15, success_rate: 0.93 }]
});
查询时间范围:
today
,
7d
,
30d
,
90d

Configuration Management

配置管理

Get Config:
javascript
// GET /api/agents/config?profile=default
fetch('/api/agents/config?profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('Config YAML:', data.config);
  console.log('Categories:', data.categories);
  // categories: ['llm', 'platforms', 'memory', 'tools', 'prompts', ...]
});
Update Config:
javascript
// PUT /api/agents/config
fetch('/api/agents/config', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    config: `
llm:
  model: hermes-3
  provider: openrouter
  temperature: 0.7
platforms:
  telegram:
    enabled: true
    token: \${TELEGRAM_BOT_TOKEN}
memory:
  provider: honcho
  honcho_url: http://localhost:8000
`
  })
});
Reset Category to Defaults:
javascript
// POST /api/agents/config/reset
fetch('/api/agents/config/reset', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    category: 'llm'
  })
});
获取配置:
javascript
// GET /api/agents/config?profile=default
fetch('/api/agents/config?profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('配置YAML:', data.config);
  console.log('配置分类:', data.categories);
  // categories: ['llm', 'platforms', 'memory', 'tools', 'prompts', ...]
});
更新配置:
javascript
// PUT /api/agents/config
fetch('/api/agents/config', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    config: `
llm:
  model: hermes-3
  provider: openrouter
  temperature: 0.7
platforms:
  telegram:
    enabled: true
    token: \${TELEGRAM_BOT_TOKEN}
memory:
  provider: honcho
  honcho_url: http://localhost:8000
`
  })
});
重置分类为默认配置:
javascript
// POST /api/agents/config/reset
fetch('/api/agents/config/reset', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    category: 'llm'
  })
});

Cron Job Management

定时任务管理

List Cron Jobs:
javascript
// GET /api/cron?profile=default
fetch('/api/cron?profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  // data.jobs = [{ id: 'job1', schedule: '0 9 * * *', command: 'hermes chat "Daily summary"', enabled: true, nextRun: '...' }]
});
Create Cron Job:
javascript
// POST /api/cron
fetch('/api/cron', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    schedule: '0 */6 * * *',  // Every 6 hours
    command: 'hermes chat "Check system health"',
    enabled: true
  })
});
Run Job Immediately:
javascript
// POST /api/cron/:jobId/run
fetch('/api/cron/job1/run', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({ profile: 'default' })
});
列出定时任务:
javascript
// GET /api/cron?profile=default
fetch('/api/cron?profile=default', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  // data.jobs = [{ id: 'job1', schedule: '0 9 * * *', command: 'hermes chat "Daily summary"', enabled: true, nextRun: '...' }]
});
创建定时任务:
javascript
// POST /api/cron
fetch('/api/cron', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    schedule: '0 */6 * * *',  // 每6小时执行一次
    command: 'hermes chat "检查系统健康状态"',
    enabled: true
  })
});
立即执行任务:
javascript
// POST /api/cron/:jobId/run
fetch('/api/cron/job1/run', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({ profile: 'default' })
});

File Explorer

文件浏览器

List Directory:
javascript
// GET /api/files?path=~/.hermes
fetch('/api/files?path=~/.hermes', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('Files:', data.files);
  // files: [{ name: 'config.yaml', type: 'file', size: 1024 }, { name: 'sessions', type: 'directory' }]
});
Read File:
javascript
// GET /api/files/read?path=~/.hermes/config.yaml
fetch('/api/files/read?path=~/.hermes/config.yaml', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('Content:', data.content);
});
Write File:
javascript
// POST /api/files/write
fetch('/api/files/write', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    path: '~/.hermes/custom-prompt.txt',
    content: 'You are a helpful assistant specializing in DevOps.'
  })
});
Security Note: All paths are validated and scoped to
HERMES_CONTROL_ROOTS
(default:
~/.hermes
). Path traversal attacks are prevented.
列出目录:
javascript
// GET /api/files?path=~/.hermes
fetch('/api/files?path=~/.hermes', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('文件列表:', data.files);
  // files: [{ name: 'config.yaml', type: 'file', size: 1024 }, { name: 'sessions', type: 'directory' }]
});
读取文件:
javascript
// GET /api/files/read?path=~/.hermes/config.yaml
fetch('/api/files/read?path=~/.hermes/config.yaml', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('文件内容:', data.content);
});
写入文件:
javascript
// POST /api/files/write
fetch('/api/files/write', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    path: '~/.hermes/custom-prompt.txt',
    content: '你是专注于DevOps领域的得力助手。'
  })
});
安全说明: 所有路径均经过验证,并限制在
HERMES_CONTROL_ROOTS
配置的目录内(默认:
~/.hermes
),可防止路径遍历攻击。

Terminal (WebSocket)

终端(WebSocket)

Connect to Terminal:
javascript
const ws = new WebSocket(`ws://${location.host}/terminal`);

ws.onopen = () => {
  console.log('Terminal connected');
  ws.send(JSON.stringify({ 
    type: 'auth', 
    token: sessionToken 
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'output') {
    console.log('Output:', data.data);
  } else if (data.type === 'error') {
    console.error('Error:', data.message);
  }
};

// Send command
ws.send(JSON.stringify({
  type: 'input',
  data: 'hermes doctor\n'
}));

// Resize PTY
ws.send(JSON.stringify({
  type: 'resize',
  cols: 80,
  rows: 24
}));
Rate Limiting: 30 commands per minute per IP.
连接终端:
javascript
const ws = new WebSocket(`ws://${location.host}/terminal`);

ws.onopen = () => {
  console.log('终端已连接');
  ws.send(JSON.stringify({ 
    type: 'auth', 
    token: sessionToken 
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'output') {
    console.log('输出:', data.data);
  } else if (data.type === 'error') {
    console.error('错误:', data.message);
  }
};

// 发送命令
ws.send(JSON.stringify({
  type: 'input',
  data: 'hermes doctor\n'
}));

// 调整PTY窗口大小
ws.send(JSON.stringify({
  type: 'resize',
  cols: 80,
  rows: 24
}));
速率限制: 每个IP每分钟最多30条命令。

System Maintenance

系统维护

Run Doctor (Diagnostics):
javascript
// POST /api/maintenance/doctor
fetch('/api/maintenance/doctor', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  }
})
.then(res => res.json())
.then(data => {
  console.log('Issues found:', data.issues);
  console.log('Fixes applied:', data.fixes);
});
Generate Debug Dump:
javascript
// GET /api/maintenance/dump
fetch('/api/maintenance/dump', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('System info:', data.system);
  console.log('Config:', data.config);
  console.log('Logs:', data.logs);
});
Backup System:
javascript
// GET /api/maintenance/backup
// Returns a zip file download
window.location.href = '/api/maintenance/backup?token=' + sessionToken;
Restart HCI Server:
javascript
// POST /api/maintenance/restart-hci
fetch('/api/maintenance/restart-hci', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  }
});
// Server will restart in 2 seconds
运行诊断工具:
javascript
// POST /api/maintenance/doctor
fetch('/api/maintenance/doctor', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  }
})
.then(res => res.json())
.then(data => {
  console.log('发现问题:', data.issues);
  console.log('已应用修复:', data.fixes);
});
生成调试快照:
javascript
// GET /api/maintenance/dump
fetch('/api/maintenance/dump', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
  console.log('系统信息:', data.system);
  console.log('配置信息:', data.config);
  console.log('日志信息:', data.logs);
});
备份系统:
javascript
// GET /api/maintenance/backup
// 返回zip文件下载
window.location.href = '/api/maintenance/backup?token=' + sessionToken;
重启HCI服务:
javascript
// POST /api/maintenance/restart-hci
fetch('/api/maintenance/restart-hci', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  }
});
// 服务将在2秒后重启

Common Patterns

常用模式

Programmatic Profile Switching

程序化切换配置文件

javascript
class HermesProfileManager {
  constructor(baseUrl, token, csrfToken) {
    this.baseUrl = baseUrl;
    this.token = token;
    this.csrfToken = csrfToken;
  }

  async switchProfile(from, to) {
    // Stop current profile gateway
    await fetch(`${this.baseUrl}/api/agents/gateway/stop`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ profile: from })
    });

    // Start new profile gateway
    await fetch(`${this.baseUrl}/api/agents/gateway/start`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ profile: to })
    });

    console.log(`Switched from ${from} to ${to}`);
  }

  async createTestProfile(name, baseProfile = 'default') {
    // Clone base profile
    await fetch(`${this.baseUrl}/api/agents/profiles`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ name, cloneFrom: baseProfile })
    });

    // Update config for testing (lower temperature, etc.)
    const config = await fetch(`${this.baseUrl}/api/agents/config?profile=${name}`, {
      headers: { 'Authorization': `Bearer ${this.token}` }
    }).then(res => res.json());

    const modifiedConfig = config.config.replace(/temperature: \d\.\d+/, 'temperature: 0.3');

    await fetch(`${this.baseUrl}/api/agents/config`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ profile: name, config: modifiedConfig })
    });

    console.log(`Test profile ${name} created`);
  }
}

// Usage
const manager = new HermesProfileManager('http://localhost:10272', sessionToken, csrfToken);
await manager.createTestProfile('experiment-1');
await manager.switchProfile('default', 'experiment-1');
javascript
class HermesProfileManager {
  constructor(baseUrl, token, csrfToken) {
    this.baseUrl = baseUrl;
    this.token = token;
    this.csrfToken = csrfToken;
  }

  async switchProfile(from, to) {
    // 停止当前配置文件的网关
    await fetch(`${this.baseUrl}/api/agents/gateway/stop`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ profile: from })
    });

    // 启动新配置文件的网关
    await fetch(`${this.baseUrl}/api/agents/gateway/start`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ profile: to })
    });

    console.log(`已从${from}切换到${to}`);
  }

  async createTestProfile(name, baseProfile = 'default') {
    // 克隆基础配置文件
    await fetch(`${this.baseUrl}/api/agents/profiles`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ name, cloneFrom: baseProfile })
    });

    // 更新测试配置(降低温度等)
    const config = await fetch(`${this.baseUrl}/api/agents/config?profile=${name}`, {
      headers: { 'Authorization': `Bearer ${this.token}` }
    }).then(res => res.json());

    const modifiedConfig = config.config.replace(/temperature: \d\.\d+/, 'temperature: 0.3');

    await fetch(`${this.baseUrl}/api/agents/config`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ profile: name, config: modifiedConfig })
    });

    console.log(`已创建测试配置文件${name}`);
  }
}

// 使用示例
const manager = new HermesProfileManager('http://localhost:10272', sessionToken, csrfToken);
await manager.createTestProfile('experiment-1');
await manager.switchProfile('default', 'experiment-1');

Bulk Session Export

批量导出会话

javascript
async function exportAllSessions(profile) {
  const sessions = await fetch(`/api/chat/sessions?profile=${profile}`, {
    headers: { 'Authorization': `Bearer ${sessionToken}` }
  }).then(res => res.json());

  const exports = [];
  
  for (const session of sessions.sessions) {
    const data = await fetch(`/api/chat/sessions/${session.id}/export?profile=${profile}`, {
      headers: { 'Authorization': `Bearer ${sessionToken}` }
    }).then(res => res.json());
    
    exports.push({
      id: session.id,
      title: session.title,
      messages: data.messages
    });
  }

  // Save to file
  const blob = new Blob([JSON.stringify(exports, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `hermes-sessions-${profile}-${Date.now()}.json`;
  a.click();
}

// Usage
await exportAllSessions('production');
javascript
async function exportAllSessions(profile) {
  const sessions = await fetch(`/api/chat/sessions?profile=${profile}`, {
    headers: { 'Authorization': `Bearer ${sessionToken}` }
  }).then(res => res.json());

  const exports = [];
  
  for (const session of sessions.sessions) {
    const data = await fetch(`/api/chat/sessions/${session.id}/export?profile=${profile}`, {
      headers: { 'Authorization': `Bearer ${sessionToken}` }
    }).then(res => res.json());
    
    exports.push({
      id: session.id,
      title: session.title,
      messages: data.messages
    });
  }

  // 保存到文件
  const blob = new Blob([JSON.stringify(exports, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `hermes-sessions-${profile}-${Date.now()}.json`;
  a.click();
}

// 使用示例
await exportAllSessions('production');

Custom Token Usage Reporter

自定义令牌使用报告器

javascript
class TokenUsageReporter {
  constructor(baseUrl, token) {
    this.baseUrl = baseUrl;
    this.token = token;
  }

  async generateReport(profile, range = '30d') {
    const stats = await fetch(
      `${this.baseUrl}/api/usage/stats?range=${range}&profile=${profile}`,
      { headers: { 'Authorization': `Bearer ${this.token}` } }
    ).then(res => res.json());

    const report = {
      period: range,
      profile: profile,
      summary: {
        totalSessions: stats.totalSessions,
        totalMessages: stats.totalMessages,
        totalTokens: stats.totalTokens,
        estimatedCost: stats.estimatedCost,
        avgTokensPerSession: Math.round(stats.totalTokens / stats.totalSessions)
      },
      topModels: stats.modelBreakdown.slice(0, 5),
      topTools: stats.topTools.slice(0, 10),
      platformDistribution: stats.platformBreakdown
    };

    console.table(report.topModels);
    console.table(report.topTools);
    
    return report;
  }

  async compareProfiles(profileA, profileB, range = '7d') {
    const [statsA, statsB] = await Promise.all([
      fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileA}`,
        { headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json()),
      fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileB}`,
        { headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json())
    ]);

    return {
      profileA: { name: profileA, cost: statsA.estimatedCost, tokens: statsA.totalTokens },
      profileB: { name: profileB, cost: statsB.estimatedCost, tokens: statsB.totalTokens },
      costDiff: statsA.estimatedCost - statsB.estimatedCost,
      tokenDiff: statsA.totalTokens - statsB.totalTokens
    };
  }
}

// Usage
const reporter = new TokenUsageReporter('http://localhost:10272', sessionToken);
const report = await reporter.generateReport('production', '30d');
console.log('Monthly cost:', report.summary.estimatedCost);

const comparison = await reporter.compareProfiles('production', 'staging', '7d');
console.log('Cost difference:', comparison.costDiff);
javascript
class TokenUsageReporter {
  constructor(baseUrl, token) {
    this.baseUrl = baseUrl;
    this.token = token;
  }

  async generateReport(profile, range = '30d') {
    const stats = await fetch(
      `${this.baseUrl}/api/usage/stats?range=${range}&profile=${profile}`,
      { headers: { 'Authorization': `Bearer ${this.token}` } }
    ).then(res => res.json());

    const report = {
      period: range,
      profile: profile,
      summary: {
        totalSessions: stats.totalSessions,
        totalMessages: stats.totalMessages,
        totalTokens: stats.totalTokens,
        estimatedCost: stats.estimatedCost,
        avgTokensPerSession: Math.round(stats.totalTokens / stats.totalSessions)
      },
      topModels: stats.modelBreakdown.slice(0, 5),
      topTools: stats.topTools.slice(0, 10),
      platformDistribution: stats.platformBreakdown
    };

    console.table(report.topModels);
    console.table(report.topTools);
    
    return report;
  }

  async compareProfiles(profileA, profileB, range = '7d') {
    const [statsA, statsB] = await Promise.all([
      fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileA}`,
        { headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json()),
      fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileB}`,
        { headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json())
    ]);

    return {
      profileA: { name: profileA, cost: statsA.estimatedCost, tokens: statsA.totalTokens },
      profileB: { name: profileB, cost: statsB.estimatedCost, tokens: statsB.totalTokens },
      costDiff: statsA.estimatedCost - statsB.estimatedCost,
      tokenDiff: statsA.totalTokens - statsB.totalTokens
    };
  }
}

// 使用示例
const reporter = new TokenUsageReporter('http://localhost:10272', sessionToken);
const report = await reporter.generateReport('production', '30d');
console.log('月度成本:', report.summary.estimatedCost);

const comparison = await reporter.compareProfiles('production', 'staging', '7d');
console.log('成本差异:', comparison.costDiff);

Configuration Examples

配置示例

Multi-Platform Setup

多平台配置

~/.hermes/profiles/production/config.yaml:
yaml
llm:
  model: hermes-3
  provider: openrouter
  api_key: ${OPENROUTER_API_KEY}
  temperature: 0.7
  max_tokens: 4000

platforms:
  telegram:
    enabled: true
    token: ${TELEGRAM_BOT_TOKEN}
  
  whatsapp:
    enabled: true
    account_sid: ${TWILIO_ACCOUNT_SID}
    auth_token: ${TWILIO_AUTH_TOKEN}
    from_number: ${TWILIO_PHONE_NUMBER}
  
  slack:
    enabled: true
    bot_token: ${SLACK_BOT_TOKEN}
    app_token: ${SLACK_APP_TOKEN}

memory:
  provider: honcho
  honcho_url: http://localhost:8000
  honcho_app_name: hermes-prod

tools:
  web_search:
    enabled: true
    provider: tavily
    api_key: ${TAVILY_API_KEY}
  
  code_execution:
    enabled: true
    sandbox: docker
    timeout: 30

prompts:
  system: |
    You are Hermes, a production AI assistant for the DevOps team.
    Focus on accuracy and provide detailed technical responses.
~/.hermes/profiles/production/config.yaml:
yaml
llm:
  model: hermes-3
  provider: openrouter
  api_key: ${OPENROUTER_API_KEY}
  temperature: 0.7
  max_tokens: 4000

platforms:
  telegram:
    enabled: true
    token: ${TELEGRAM_BOT_TOKEN}
  
  whatsapp:
    enabled: true
    account_sid: ${TWILIO_ACCOUNT_SID}
    auth_token: ${TWILIO_AUTH_TOKEN}
    from_number: ${TWILIO_PHONE_NUMBER}
  
  slack:
    enabled: true
    bot_token: ${SLACK_BOT_TOKEN}
    app_token: ${SLACK_APP_TOKEN}

memory:
  provider: honcho
  honcho_url: http://localhost:8000
  honcho_app_name: hermes-prod

tools:
  web_search:
    enabled: true
    provider: tavily
    api_key: ${TAVILY_API_KEY}
  
  code_execution:
    enabled: true
    sandbox: docker
    timeout: 30

prompts:
  system: |
    你是Hermes,DevOps团队的生产级AI助手。
    专注于准确性,提供详细的技术响应。

Custom RBAC Setup

自定义RBAC配置

Create a "Analyst" role (read-only + chat):
javascript
// Via Maintenance → Users UI or API
const analystPermissions = [
  'agents:read',
  'chat:read',
  'chat:write',
  'sessions:read',
  'config:read',
  'gateway:logs'
];

await fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${adminToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    username: 'analyst1',
    password: 'secure-password',
    role: 'custom',
    permissions: analystPermissions
  })
});
创建“分析师”角色(只读+聊天权限):
javascript
// 通过维护 → 用户UI或API创建
const analystPermissions = [
  'agents:read',
  'chat:read',
  'chat:write',
  'sessions:read',
  'config:read',
  'gateway:logs'
];

await fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${adminToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    username: 'analyst1',
    password: 'secure-password',
    role: 'custom',
    permissions: analystPermissions
  })
});

Scheduled Maintenance Cron

定时维护任务

javascript
// Daily health check at 3 AM
await fetch('/api/cron', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    schedule: '0 3 * * *',
    command: 'hermes doctor --auto-fix && hermes chat "Daily system report"',
    enabled: true
  })
});

// Weekly backup every Sunday at 2 AM
await fetch('/api/cron', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    schedule: '0 2 * * 0',
    command: 'cd ~/.hermes && tar -czf backup-$(date +%Y%m%d).tar.gz .',
    enabled: true
  })
});
javascript
// 每日凌晨3点执行健康检查
await fetch('/api/cron', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    schedule: '0 3 * * *',
    command: 'hermes doctor --auto-fix && hermes chat "每日系统报告"',
    enabled: true
  })
});

// 每周日凌晨2点执行备份
await fetch('/api/cron', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${sessionToken}`,
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({
    profile: 'default',
    schedule: '0 2 * * 0',
    command: 'cd ~/.hermes && tar -czf backup-$(date +%Y%m%d).tar.gz .',
    enabled: true
  })
});

Troubleshooting

故障排查

Gateway Won't Start

网关无法启动

Symptom: "Failed to start gateway" error
Solutions:
bash
undefined
症状: 出现“Failed to start gateway”错误
解决方案:
bash
undefined

1. Check if port is already in use

1. 检查端口是否被占用

sudo lsof -i :8000 # Default gateway port
sudo lsof -i :8000 # 默认网关端口

2. View gateway logs

2. 查看网关日志

journalctl -u hermes-gateway-default -n 50
journalctl -u hermes-gateway-default -n 50

3. Check systemd service status

3. 检查systemd服务状态

systemctl status hermes-gateway-default
systemctl status hermes-gateway-default

4. Manually test gateway

4. 手动测试网关

hermes gateway start --profile default --debug
hermes gateway start --profile default --debug

5. Check config.yaml syntax

5. 检查config.yaml语法

hermes config validate --profile default
undefined
hermes config validate --profile default
undefined

WebSocket Connection Fails

WebSocket连接失败

Symptom: Terminal or chat streaming doesn't work
Solutions:
javascript
// 1. Check WebSocket origin in server.js
// Ensure your domain is allowed in WebSocket verification

// 2. If behind reverse proxy, configure headers:
// Nginx example:
/*
location /terminal {
    proxy_pass http://localhost:10272;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}
*/

// 3. Check browser console for CORS errors
// Update .env if needed:
// HERMES_CONTROL_CORS_ORIGIN=https://yourdomain.com
症状: 终端或聊天流无法正常工作
解决方案:
javascript
// 1. 检查server.js中的WebSocket源配置
// 确保你的域名在WebSocket验证白名单中

// 2. 如果使用反向代理,配置相应头信息:
// Nginx示例:
/*
location /terminal {
    proxy_pass http://localhost:10272;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}
*/

// 3. 检查浏览器控制台是否有CORS错误
// 如有需要更新.env配置:
// HERMES_CONTROL_CORS_ORIGIN=https://yourdomain.com

Session Not Resuming

会话无法继续

Symptom:
--continue <session-id>
creates new session instead
Solutions:
bash
undefined
症状: 使用
--continue <session-id>
时创建了新会话而非继续原有会话
解决方案:
bash
undefined

1. Verify session exists

1. 验证会话是否存在

ls -la ~/.hermes/sessions/
ls -la ~/.hermes/sessions/

2. Check session ID format

2. 检查会话ID格式

New format: abc123

新格式: abc123

Legacy format: Session_abc123

旧格式: Session_abc123

3. Test CLI directly

3. 直接通过CLI测试

hermes chat --continue abc123 "test message"
hermes chat --continue abc123 "测试消息"

4. Check session file permissions

4. 检查会话文件权限

chmod 644 ~/.hermes/sessions/abc123.json
chmod 644 ~/.hermes/sessions/abc123.json

5. Review chat logs in HCI

5. 在HCI中查看聊天日志

Navigate to Agents → [Profile] → Sessions tab

导航至Agent → [配置文件] → 会话标签页

undefined
undefined

Token Analytics Not Showing

令牌分析无数据

Symptom: Usage page shows zero data
Solutions:
bash
undefined
症状: 使用页面显示数据为零
解决方案:
bash
undefined

1. Verify session log files exist

1. 验证会话日志文件是否存在

ls -la ~/.hermes/sessions/*.json
ls -la ~/.hermes/sessions/*.json

2. Check token tracking is enabled in config

2. 检查配置中是否启用了令牌追踪

cat ~/.hermes/profiles/default/config.yaml | grep -A5 analytics
cat ~/.hermes/profiles/default/config.yaml | grep -A5 analytics

3. Manually trigger analytics rebuild

3. 手动触发分析数据重建

hermes analytics rebuild
hermes analytics rebuild

4. Check for parsing errors in logs

4. 检查日志中的解析错误

tail -f ~/.hermes/hci.log | grep analytics
undefined
tail -f ~/.hermes/hci.log | grep analytics
undefined

Permission Denied Errors

权限拒绝错误

Symptom: "Permission denied" when accessing files/terminals
Solutions:
bash
undefined
症状: 访问文件/终端时出现“Permission denied”错误
解决方案:
bash
undefined

1. Check user permissions on ~/.hermes

1. 检查~/.hermes目录的用户权限

ls -ld ~/.hermes chmod 755 ~/.hermes
ls -ld ~/.hermes chmod 755 ~/.hermes

2. If running as non-root, ensure RBAC role allows action

2. 如果以非root用户运行,确保RBAC角色允许该操作

Check Maintenance → Users → [Your User] → Permissions

检查维护 → 用户 → [你的用户] → 权限设置

3. For terminal: verify user has shell access

3. 对于终端: 验证用户是否具备Shell访问权限

echo $SHELL
echo $SHELL

4. For files: check HERMES_CONTROL_ROOTS in .env

4. 对于文件: 检查.env中的HERMES_CONTROL_ROOTS配置

Default: ~/.hermes (user must have read/write access)

默认值: ~/.hermes(用户需具备读写权限)

undefined
undefined

High Memory Usage

内存占用过高

Symptom: HCI process consuming excessive RAM
Solutions:
bash
undefined
症状: HCI进程消耗过多内存
解决方案:
bash
undefined

1. Check active WebSocket connections

1. 检查活跃WebSocket连接数

Navigate to browser DevTools → Network → WS tab

打开浏览器开发者工具 → 网络 → WS标签页

2. Restart HCI to clear stale connections

2. 重启HCI以清除过期连接

systemctl restart hermes-control
systemctl restart hermes-control

OR via UI: Maintenance → HCI Restart

或通过UI操作: 维护 → 重启HCI

3. Limit concurrent sessions

3. 限制并发会话数

Edit server.js to add connection limits if needed

如有需要编辑server.js添加连接限制

4. Check for memory leaks in logs

4. 检查日志中的内存泄漏信息

node --expose-gc server.js
undefined
node --expose-gc server.js
undefined

CSRF Token Mismatch

CSRF令牌不匹配

Symptom: "Invalid CSRF token" on form submissions
Solutions:
javascript
// 1. Ensure CSRF token is included in request headers
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

fetch('/api/agents/profiles', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': csrfToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'test' })
});

// 2. Check session hasn't expired
// Default: 24 hour session timeout

// 3. Clear cookies and re-login
document.cookie.split(";").forEach(c => {
  document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
});
症状: 表单提交时出现“Invalid CSRF token”错误
解决方案:
javascript
// 1. 确保请求头中包含CSRF令牌
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

fetch('/api/agents/profiles', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': csrfToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'test' })
});

// 2. 检查会话是否已过期

Gateway Logs Not Streaming

默认会话超时时间: 24小时

3. 清除Cookie并重新登录

Symptom: Gateway log viewer shows "Connecting..." indefinitely
Solutions:
bash
undefined
document.cookie.split(";").forEach(c => { document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); });
undefined

1. Verify gateway is actually running

网关日志无法流式显示

systemctl status hermes-gateway-default
症状: 网关日志查看器一直显示“Connecting...”
解决方案:
bash
undefined

2. Check systemd journal accessibility

1. 验证网关是否真的在运行

sudo journalctl -u hermes-gateway-default --no-pager -n 10
systemctl status hermes-gateway-default

3. Grant user access to systemd journal (if non-root)

2. 检查systemd日志的可访问性

sudo user
sudo journalctl -u hermes-gateway-default --no-pager -n 10

3. 授予用户访问systemd日志的权限(如果是非root用户)

sudo user
undefined