make bridge worker transport configurable (#1106)

This commit is contained in:
ekko
2026-05-28 22:33:33 +08:00
committed by GitHub
parent 74d0c3509b
commit 14466dfc9f
9 changed files with 234 additions and 21 deletions
+38 -11
View File
@@ -12,6 +12,9 @@ const PREVIEW_HOME_DIR_NAME = 'hermes-web-ui-pereview-home'
const PREVIEW_BACKEND_PORT = 8650
const PREVIEW_FRONTEND_PORT = 8651
const PREVIEW_AGENT_BRIDGE_PORT = 18650
const PREVIEW_AGENT_BRIDGE_WORKER_PORT_BASE = 19650
const PREVIEW_AGENT_BRIDGE_ENDPOINT_ENV = 'HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_ENDPOINT'
const PREVIEW_AGENT_BRIDGE_TRANSPORT_ENV = 'HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_TRANSPORT'
const PREVIEW_FRONTEND_URL = `http://localhost:${PREVIEW_FRONTEND_PORT}`
const PREVIEW_TAG_REF_PATTERN = /^[A-Za-z0-9._/-]+$/
const PREVIEW_MAIN_REF = 'main'
@@ -207,12 +210,43 @@ function getPreviewHomeDir() {
return join(getWebUiHome(), PREVIEW_HOME_DIR_NAME)
}
function normalizePreviewAgentBridgeTransport(value: string | undefined) {
const transport = value?.trim().toLowerCase()
return transport && ['tcp', 'ipc', 'unix'].includes(transport) ? transport : ''
}
function getPreviewAgentBridgeEndpoint() {
return process.platform === 'win32'
const configured = process.env[PREVIEW_AGENT_BRIDGE_ENDPOINT_ENV]?.trim()
if (configured) return configured
const transport = normalizePreviewAgentBridgeTransport(process.env[PREVIEW_AGENT_BRIDGE_TRANSPORT_ENV])
|| normalizePreviewAgentBridgeTransport(process.env.HERMES_AGENT_BRIDGE_WORKER_TRANSPORT)
const useTcp = transport ? transport === 'tcp' : process.platform === 'win32'
return useTcp
? `tcp://127.0.0.1:${PREVIEW_AGENT_BRIDGE_PORT}`
: `ipc://${join(getPreviewHomeDir(), 'agent-bridge.sock')}`
}
function getTcpEndpointPort(endpoint: string): number | null {
try {
const url = new URL(endpoint)
if (url.protocol !== 'tcp:') return null
const port = Number(url.port)
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null
} catch {
return null
}
}
function getPreviewListeningPorts() {
const agentBridgePort = getTcpEndpointPort(getPreviewAgentBridgeEndpoint())
return [
PREVIEW_BACKEND_PORT,
PREVIEW_FRONTEND_PORT,
...(agentBridgePort ? [agentBridgePort] : []),
]
}
function getPreviewPackagePath() {
return join(getPreviewDir(), 'package.json')
}
@@ -315,11 +349,7 @@ function parsePidLines(output: string): number[] {
}
function getPreviewListeningPids(): number[] {
const ports = [
PREVIEW_BACKEND_PORT,
PREVIEW_FRONTEND_PORT,
...(process.platform === 'win32' ? [PREVIEW_AGENT_BRIDGE_PORT] : []),
]
const ports = getPreviewListeningPorts()
const pids = new Set<number>()
if (process.platform === 'win32') {
@@ -367,11 +397,7 @@ function getUnixProcessGroupId(pid: number): number | null {
}
async function assertPreviewPortsAvailable() {
const ports = [
PREVIEW_BACKEND_PORT,
PREVIEW_FRONTEND_PORT,
...(process.platform === 'win32' ? [PREVIEW_AGENT_BRIDGE_PORT] : []),
]
const ports = getPreviewListeningPorts()
const checks = await Promise.all(ports.map(port => isPortAvailable(port)))
const busy = ports.filter((_, index) => !checks[index])
@@ -964,6 +990,7 @@ export async function startPreview(ctx: any) {
HERMES_WEB_UI_HOME: getPreviewHomeDir(),
HERMES_WEBUI_STATE_DIR: getPreviewHomeDir(),
HERMES_AGENT_BRIDGE_ENDPOINT: getPreviewAgentBridgeEndpoint(),
HERMES_AGENT_BRIDGE_WORKER_PORT_BASE: String(PREVIEW_AGENT_BRIDGE_WORKER_PORT_BASE),
AUTH_TOKEN: '',
HERMES_WEB_UI_BACKEND_PORT: String(PREVIEW_BACKEND_PORT),
HERMES_WEB_UI_FRONTEND_PORT: String(PREVIEW_FRONTEND_PORT),
@@ -30,6 +30,13 @@ Override with:
HERMES_AGENT_BRIDGE_ENDPOINT=tcp://127.0.0.1:8765 python packages/server/src/services/hermes/agent-bridge/hermes_bridge.py
```
Profile workers use the same platform defaults: TCP on Windows and IPC on
macOS/Linux. Override worker transport with:
```bash
HERMES_AGENT_BRIDGE_WORKER_TRANSPORT=tcp HERMES_AGENT_BRIDGE_WORKER_PORT_BASE=18780 python packages/server/src/services/hermes/agent-bridge/hermes_bridge.py
```
The service discovers Hermes Agent in this order:
1. `--agent-root`
@@ -2416,7 +2416,9 @@ class WorkerProcess:
def _worker_endpoint(key: str, namespace: str | None = None) -> str:
namespace_key = f"{namespace or ''}\0{key}"
safe = hashlib.sha256(namespace_key.encode("utf-8")).hexdigest()[:16]
if os.name == "nt":
transport = os.environ.get("HERMES_AGENT_BRIDGE_WORKER_TRANSPORT", "").strip().lower()
use_tcp = transport == "tcp" or (transport not in {"ipc", "unix"} and os.name == "nt")
if use_tcp:
port_base = int(os.environ.get("HERMES_AGENT_BRIDGE_WORKER_PORT_BASE", "18780"))
return f"tcp://127.0.0.1:{port_base + int(safe[:4], 16) % 1000}"
root = Path(tempfile.gettempdir()) / "hermes-agent-bridge-workers"
+42 -3
View File
@@ -133,17 +133,56 @@ export default {
envVars: {
title: 'Environment Variables',
rows: [
['AUTH_TOKEN', 'Custom auth token (overrides auto-generated)'],
['PORT', 'Server listen port (default: 8648)'],
['BIND_HOST', 'Server bind host (default: 0.0.0.0). Set :: explicitly to enable IPv6 listening.'],
['HERMES_WEB_UI_HOME', 'Web UI data home for auth token, credentials, logs, DB, and default uploads'],
['HERMES_WEBUI_STATE_DIR', 'Compatibility alias for HERMES_WEB_UI_HOME'],
['UPLOAD_DIR', 'Custom upload root. Uploaded files are stored below profile-scoped subdirectories.'],
['CORS_ORIGINS', 'CORS origin config (default: *)'],
['HERMES_BIN', 'Custom path to hermes CLI binary'],
['AUTH_TOKEN', 'Custom bearer token; overrides the auto-generated token'],
['AUTH_JWT_SECRET', 'JWT signing secret override for username/password sessions'],
['PROFILE', 'Startup/default Hermes profile'],
['LOG_LEVEL', 'Server log level'],
['BRIDGE_LOG_LEVEL', 'Bridge log level'],
['MAX_DOWNLOAD_SIZE', 'Maximum file download size'],
['MAX_EDIT_SIZE', 'Maximum editable file size'],
['WORKSPACE_BASE', 'Base directory for workspace browsing'],
['HERMES_HOME', 'Hermes data home'],
['HERMES_BIN', 'Custom Hermes CLI binary path'],
['HERMES_AGENT_ROOT', 'Hermes Agent source checkout containing run_agent.py'],
['HERMES_AGENT_BRIDGE_PYTHON', 'Python interpreter used to launch the agent bridge'],
['HERMES_AGENT_BRIDGE_UV', 'uv executable used to launch the agent bridge when available'],
['UV', 'Fallback uv executable path'],
['PYTHON', 'Fallback Python executable for the agent bridge'],
['HERMES_AGENT_BRIDGE_ENDPOINT', 'Agent bridge broker endpoint. Windows defaults to tcp://127.0.0.1:18765; macOS/Linux defaults to ipc:///tmp/hermes-agent-bridge.sock'],
['HERMES_AGENT_BRIDGE_TIMEOUT_MS', 'Timeout for Node requests to the bridge broker'],
['HERMES_AGENT_BRIDGE_CONNECT_RETRY_MS', 'Short retry window for connecting to the bridge socket'],
['HERMES_AGENT_BRIDGE_STARTUP_TIMEOUT_MS', 'Timeout while waiting for the Python bridge to become ready'],
['HERMES_AGENT_BRIDGE_AUTO_RESTART', 'Auto-restart the bridge broker after unexpected exit; set 0/false/no/off to disable'],
['HERMES_AGENT_BRIDGE_RESTART_DELAY_MS', 'Base delay for bridge auto-restart backoff'],
['HERMES_AGENT_BRIDGE_PLATFORM', 'Platform identity passed to Hermes Agent'],
['HERMES_AGENT_BRIDGE_WORKER_TRANSPORT', 'Profile worker endpoint transport. Set tcp for loopback TCP, or ipc/unix for Unix domain sockets; defaults to Windows TCP and macOS/Linux IPC'],
['HERMES_AGENT_BRIDGE_WORKER_PORT_BASE', 'Base port for TCP worker endpoints (default: 18780). Version Preview uses an isolated 19650 port range'],
['HERMES_BRIDGE_PROVIDER', 'Provider override for bridge runs'],
['HERMES_BRIDGE_TOOLSETS', 'Toolset override for bridge runs'],
['HERMES_BRIDGE_MAX_TURNS', 'Maximum turn override for bridge runs'],
['HERMES_BRIDGE_SUPPRESS_PLATFORM_HINT', 'Controls bridge platform hint suppression passed to Hermes Agent'],
['HERMES_OPENROUTER_APP_REFERER', 'OpenRouter attribution referer sent by bridge runs'],
['HERMES_OPENROUTER_APP_TITLE', 'OpenRouter attribution title sent by bridge runs'],
['HERMES_OPENROUTER_APP_CATEGORIES', 'OpenRouter attribution categories sent by bridge runs'],
['HERMES_WEB_UI_MANAGED_GATEWAY', 'Force managed legacy gateway process handling'],
['HERMES_WEB_UI_STOP_GATEWAYS_ON_SHUTDOWN', 'Controls whether Web UI shutdown also stops managed gateway processes'],
['GATEWAY_HOST', 'Default gateway host written into profile config for legacy gateway compatibility'],
['HERMES_WEB_UI_PREVIEW_REPO', 'GitHub repository used by Version Preview'],
['HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_TRANSPORT', 'Version Preview broker endpoint transport. Set tcp to use loopback TCP for Preview on macOS/Linux; when unset, Preview follows HERMES_AGENT_BRIDGE_WORKER_TRANSPORT=tcp'],
['HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_ENDPOINT', 'Directly overrides the Version Preview broker endpoint for deployments that need a fully custom Preview bridge address'],
['HERMES_WEB_UI_BACKEND_PORT', 'Backend port used by the Vite dev proxy'],
['HERMES_WEB_UI_FRONTEND_PORT', 'Frontend Vite dev server port'],
],
},
gateway: {
title: 'Agent Bridge Runtime',
content: 'Chat runs are handled through the Hermes agent bridge, which runs alongside the Web UI server and talks directly to the Hermes Agent runtime. Switching the frontend Hermes Profile changes later request context only; it does not restart the bridge or clear other running tasks.',
content: 'Chat runs are handled through the Hermes agent bridge, which runs alongside the Web UI server and talks directly to the Hermes Agent runtime. HERMES_AGENT_BRIDGE_ENDPOINT controls the Node-to-broker address, while HERMES_AGENT_BRIDGE_WORKER_TRANSPORT controls the broker-to-profile-worker transport. Switching the frontend Hermes Profile changes later request context only; it does not restart the bridge or clear other running tasks.',
},
profiles: {
title: 'Profiles',
+42 -3
View File
@@ -133,17 +133,56 @@ export default {
envVars: {
title: '环境变量',
rows: [
['AUTH_TOKEN', '自定义认证令牌(覆盖自动生成的令牌)'],
['PORT', '服务器监听端口(默认:8648'],
['BIND_HOST', '服务器绑定地址(默认:0.0.0.0)。如需 IPv6,请显式设置为 ::。'],
['HERMES_WEB_UI_HOME', 'Web UI 数据目录,用于认证 token、登录凭据、日志、数据库和默认上传目录'],
['HERMES_WEBUI_STATE_DIR', 'HERMES_WEB_UI_HOME 的兼容别名'],
['UPLOAD_DIR', '自定义上传根目录。文件会保存在按 Profile 隔离的子目录下'],
['CORS_ORIGINS', 'CORS 来源配置(默认:*'],
['HERMES_BIN', '自定义 hermes CLI 二进制路径'],
['AUTH_TOKEN', '自定义 bearer token,会覆盖自动生成的 token'],
['AUTH_JWT_SECRET', '用户名/密码会话的 JWT 签名密钥覆盖'],
['PROFILE', '启动/默认 Hermes profile'],
['LOG_LEVEL', 'Server 日志级别'],
['BRIDGE_LOG_LEVEL', 'Bridge 日志级别'],
['MAX_DOWNLOAD_SIZE', '最大文件下载大小'],
['MAX_EDIT_SIZE', '最大可编辑文件大小'],
['WORKSPACE_BASE', 'Workspace 浏览根目录'],
['HERMES_HOME', 'Hermes 数据目录'],
['HERMES_BIN', '自定义 Hermes CLI 二进制路径'],
['HERMES_AGENT_ROOT', '包含 run_agent.py 的 Hermes Agent 源码目录'],
['HERMES_AGENT_BRIDGE_PYTHON', '用于启动 agent bridge 的 Python 解释器'],
['HERMES_AGENT_BRIDGE_UV', '可用时用于启动 agent bridge 的 uv 可执行文件'],
['UV', 'uv 可执行文件 fallback'],
['PYTHON', 'agent bridge 的 Python 可执行文件 fallback'],
['HERMES_AGENT_BRIDGE_ENDPOINT', 'Agent bridge broker endpoint。Windows 默认 tcp://127.0.0.1:18765macOS/Linux 默认 ipc:///tmp/hermes-agent-bridge.sock'],
['HERMES_AGENT_BRIDGE_TIMEOUT_MS', 'Node 请求 bridge broker 的响应超时'],
['HERMES_AGENT_BRIDGE_CONNECT_RETRY_MS', '连接 bridge socket 失败时的短重试窗口'],
['HERMES_AGENT_BRIDGE_STARTUP_TIMEOUT_MS', '等待 Python bridge ready 的超时'],
['HERMES_AGENT_BRIDGE_AUTO_RESTART', 'bridge broker 意外退出后是否自动重启;设为 0/false/no/off 可关闭'],
['HERMES_AGENT_BRIDGE_RESTART_DELAY_MS', 'bridge 自动重启退避的基础延迟'],
['HERMES_AGENT_BRIDGE_PLATFORM', '传给 Hermes Agent 的 platform 标识'],
['HERMES_AGENT_BRIDGE_WORKER_TRANSPORT', 'profile worker endpoint transport。设为 tcp 使用 loopback TCP;设为 ipc/unix 使用 Unix domain socket;默认 Windows TCP、macOS/Linux IPC'],
['HERMES_AGENT_BRIDGE_WORKER_PORT_BASE', 'TCP worker endpoint 起始端口(默认:18780)。Version Preview 会使用独立端口段 19650'],
['HERMES_BRIDGE_PROVIDER', 'bridge 运行时的 provider 覆盖'],
['HERMES_BRIDGE_TOOLSETS', 'bridge 运行时的 toolset 覆盖'],
['HERMES_BRIDGE_MAX_TURNS', 'bridge 运行时的最大轮数覆盖'],
['HERMES_BRIDGE_SUPPRESS_PLATFORM_HINT', '控制传给 Hermes Agent 的 bridge platform hint suppression'],
['HERMES_OPENROUTER_APP_REFERER', 'bridge 运行发送给 OpenRouter 的 attribution referer'],
['HERMES_OPENROUTER_APP_TITLE', 'bridge 运行发送给 OpenRouter 的 attribution title'],
['HERMES_OPENROUTER_APP_CATEGORIES', 'bridge 运行发送给 OpenRouter 的 attribution categories'],
['HERMES_WEB_UI_MANAGED_GATEWAY', '强制启用旧 gateway 进程托管'],
['HERMES_WEB_UI_STOP_GATEWAYS_ON_SHUTDOWN', 'Web UI 关闭时是否同时停止托管的 gateway 进程'],
['GATEWAY_HOST', '旧 gateway 兼容配置中写入 profile 的默认 gateway host'],
['HERMES_WEB_UI_PREVIEW_REPO', 'Version Preview 使用的 GitHub 仓库'],
['HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_TRANSPORT', 'Version Preview 的 broker endpoint transport。设为 tcp 可让预览环境在 macOS/Linux 上也使用 loopback TCP;未设置时会跟随 HERMES_AGENT_BRIDGE_WORKER_TRANSPORT=tcp'],
['HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_ENDPOINT', '直接覆盖 Version Preview 的 broker endpoint;用于需要完全自定义预览 bridge 地址的部署'],
['HERMES_WEB_UI_BACKEND_PORT', 'Vite dev proxy 使用的后端端口'],
['HERMES_WEB_UI_FRONTEND_PORT', '前端 Vite dev server 端口'],
],
},
gateway: {
title: 'Agent Bridge 运行时',
content: '聊天运行通过 Hermes agent bridge 处理。它随 Web UI 服务一起运行,并直接连接 Hermes Agent runtime。前端切换 Hermes Profile 只影响后续请求上下文,不会重启 bridge 或清理其他正在运行的任务。',
content: '聊天运行通过 Hermes agent bridge 处理。它随 Web UI 服务一起运行,并直接连接 Hermes Agent runtime。HERMES_AGENT_BRIDGE_ENDPOINT 控制 Node 与 bridge broker 的连接地址;HERMES_AGENT_BRIDGE_WORKER_TRANSPORT 控制 broker 与各 Profile worker 的连接方式。前端切换 Hermes Profile 只影响后续请求上下文,不会重启 bridge 或清理其他正在运行的任务。',
},
profiles: {
title: '配置文件',