feat: 灵犀 Studio Web UI 定制版
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,472 @@
|
||||
# CLI/Bridge Chat Sessions 实现文档
|
||||
|
||||
> 状态:本文档描述当前 `main` 中 Web UI 聊天会话的 API Server / Bridge(beta) 双路径实现。
|
||||
|
||||
## 概述
|
||||
|
||||
当前实现把原来的聊天通道统一到 Socket.IO namespace `/chat-run`。前端仍使用同一套 `ChatPanel + MessageList + ChatInput`,通过会话的 `source` 字段区分运行方式:
|
||||
|
||||
| source | 运行路径 | 说明 |
|
||||
|--------|----------|------|
|
||||
| `api_server` | Web UI Server → Hermes Gateway `/v1/responses` | 默认聊天路径 |
|
||||
| `cli` | Web UI Server → Python agent bridge → `AIAgent` | Bridge(beta),在 Web UI 服务端子进程里直接运行 Hermes Agent |
|
||||
|
||||
Bridge 会话不是一个独立 UI 面板,而是普通会话的一种来源。用户通过“新建聊天”下拉菜单选择 `API` 或 `Bridge (beta)`。
|
||||
|
||||
Bridge 模式支持:
|
||||
|
||||
- 流式文本输出
|
||||
- reasoning/thinking 增量
|
||||
- tool started/completed 事件
|
||||
- 工具审批请求与响应
|
||||
- abort 中断
|
||||
- per-session 队列
|
||||
- profile 隔离
|
||||
- 从 DB resume 会话
|
||||
- 与 API Server 路径共用上下文压缩逻辑
|
||||
|
||||
当前不再支持旧文档里的独立 `/cli-chat-run` namespace、`CliChatPanel.vue`、`cli-chat.ts` 和独立 `command` / `steer` socket 事件。CLI/Bridge 会话中的 slash command 现在通过统一 `/chat-run` 的 `run` payload 进入后端解析;当前支持 `/usage`、`/status`、`/abort`、`/queue`、`/clear`、`/title`、`/compress`、`/steer`、`/destroy`。
|
||||
|
||||
---
|
||||
|
||||
## 整体架构
|
||||
|
||||
```text
|
||||
ChatPanel.vue
|
||||
├─ MessageList.vue
|
||||
└─ ChatInput.vue
|
||||
│
|
||||
│ Socket.IO /chat-run
|
||||
▼
|
||||
ChatRunSocket (Node.js)
|
||||
├─ source=api_server → Hermes Gateway /v1/responses
|
||||
└─ source=cli → AgentBridgeClient
|
||||
│ TCP/Unix socket, newline JSON
|
||||
▼
|
||||
hermes_bridge.py
|
||||
│ in-process import
|
||||
▼
|
||||
AIAgent (hermes-agent)
|
||||
```
|
||||
|
||||
### 分流规则
|
||||
|
||||
`ChatRunSocket.resolveRunSource()` 决定本轮运行走哪个后端:
|
||||
|
||||
1. `run` payload 中 `source === 'cli'` 时走 bridge。
|
||||
2. `source === 'api_server'` 时走 gateway。
|
||||
3. 未显式传 `source` 时,如果 DB 中已有 session 的 `source` 是 `cli`,继续走 bridge。
|
||||
4. 其他情况默认走 `api_server`。
|
||||
|
||||
---
|
||||
|
||||
## 主要文件
|
||||
|
||||
### 前端
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `packages/client/src/components/hermes/chat/ChatPanel.vue` | 统一聊天面板;新建菜单包含 `API` 和 `Bridge (beta)`;渲染审批条 |
|
||||
| `packages/client/src/components/hermes/chat/MessageList.vue` | 统一消息列表;展示文本、reasoning、tool 消息等 |
|
||||
| `packages/client/src/components/hermes/chat/ChatInput.vue` | 统一输入框;发送、停止、附件上传入口 |
|
||||
| `packages/client/src/api/hermes/chat.ts` | `/chat-run` Socket.IO 客户端;注册 session 事件处理器;发送 run/abort/approval |
|
||||
| `packages/client/src/stores/hermes/chat.ts` | 会话状态、发送流程、resume、队列、审批、消息映射 |
|
||||
|
||||
### 后端
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `packages/server/src/services/hermes/run-chat/index.ts` | `/chat-run` Socket.IO 入口;按 `source` 分流 API Server 与 Bridge 运行 |
|
||||
| `packages/server/src/services/hermes/run-chat/handle-api-run.ts` | API Server 路径;调用 Hermes Gateway `/v1/responses` 并消费流式响应 |
|
||||
| `packages/server/src/services/hermes/run-chat/handle-bridge-run.ts` | Bridge 路径;调用 Agent Bridge 并写入本地会话库 |
|
||||
| `packages/server/src/services/hermes/run-chat/session-command.ts` | CLI/Bridge slash command 解析与处理 |
|
||||
| `packages/server/src/services/hermes/agent-bridge/client.ts` | Node 端 bridge 客户端;通过 socket 请求 Python bridge |
|
||||
| `packages/server/src/services/hermes/agent-bridge/manager.ts` | Python bridge 子进程生命周期管理 |
|
||||
| `packages/server/src/services/hermes/agent-bridge/hermes_bridge.py` | Python bridge 服务;创建并复用 `AIAgent` 实例 |
|
||||
| `packages/server/src/services/hermes/agent-bridge/index.ts` | bridge 模块导出 |
|
||||
| `packages/server/src/index.ts` | 启动 `AgentBridgeManager` 和 `ChatRunSocket` |
|
||||
| `packages/server/src/services/shutdown.ts` | 关闭时停止 chat socket 和 bridge 子进程 |
|
||||
| `packages/server/src/controllers/hermes/sessions.ts` | 会话列表和详情读取,包含 `source` 信息 |
|
||||
| `packages/server/src/controllers/hermes/profiles.ts` | profile 管理接口;按 URL/body 中的 profile 做权限校验 |
|
||||
|
||||
### 已移除的旧文件
|
||||
|
||||
| 文件 | 状态 |
|
||||
|------|------|
|
||||
| `packages/client/src/api/hermes/cli-chat.ts` | 已删除 |
|
||||
| `packages/client/src/components/hermes/chat/CliChatPanel.vue` | 已删除 |
|
||||
| `packages/server/src/services/hermes/cli-chat-run-socket.ts` | 已删除 |
|
||||
|
||||
---
|
||||
|
||||
## 前端流程
|
||||
|
||||
### 新建会话
|
||||
|
||||
`ChatPanel.vue` 中的新建按钮使用下拉菜单:
|
||||
|
||||
- `API`:调用 `chatStore.newChat()`,创建默认 `api_server` 会话。
|
||||
- `Bridge (beta)`:调用 `chatStore.newCliSession()`,创建 `source: 'cli'` 会话。
|
||||
|
||||
Bridge 会话 ID 使用类似 `YYYYMMDD_HHMMSS_xxxxxx` 的格式,便于与 Hermes CLI 风格的 session ID 对齐。
|
||||
|
||||
### 发送消息
|
||||
|
||||
1. `ChatInput.vue` 触发 store 的发送逻辑。
|
||||
2. `chat.ts` 根据 active session 组装输入内容,附件会被转为 `ContentBlock[]`。
|
||||
3. 调用 `startRunViaSocket()`。
|
||||
4. 前端向 `/chat-run` emit:
|
||||
|
||||
```ts
|
||||
socket.emit('run', {
|
||||
session_id,
|
||||
input,
|
||||
instructions,
|
||||
model,
|
||||
queue_id,
|
||||
source, // api_server 或 cli
|
||||
})
|
||||
```
|
||||
|
||||
5. 前端注册本 session 的事件 handler,通过 `session_id` 隔离多会话并发事件。
|
||||
|
||||
### Resume
|
||||
|
||||
切换会话、页面恢复可见、或刷新后,前端通过:
|
||||
|
||||
```ts
|
||||
socket.emit('resume', { session_id })
|
||||
```
|
||||
|
||||
服务端返回:
|
||||
|
||||
```ts
|
||||
{
|
||||
session_id,
|
||||
messages,
|
||||
isWorking,
|
||||
isAborting,
|
||||
events,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
queueLength,
|
||||
}
|
||||
```
|
||||
|
||||
如果服务端发现该 session 仍在运行,前端会重新注册 handler,并允许继续 abort。
|
||||
|
||||
### 审批
|
||||
|
||||
Bridge 工具需要人工确认时,服务端会发 `approval.requested`,前端 store 记录为 `activePendingApproval`,`ChatPanel.vue` 在输入框上方显示审批条。
|
||||
|
||||
前端响应审批:
|
||||
|
||||
```ts
|
||||
socket.emit('approval.respond', {
|
||||
session_id,
|
||||
approval_id,
|
||||
choice, // once | session | always | deny
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `/chat-run` Socket.IO 协议
|
||||
|
||||
### 客户端 → 服务端
|
||||
|
||||
| 事件 | 数据 | 说明 |
|
||||
|------|------|------|
|
||||
| `run` | `{ session_id, input, model?, instructions?, queue_id?, source? }` | 启动一轮运行;`source` 决定 API Server 或 Bridge |
|
||||
| `resume` | `{ session_id }` | 加入 session room 并恢复状态 |
|
||||
| `abort` | `{ session_id }` | 中断当前运行 |
|
||||
| `cancel_queued_run` | `{ session_id, queue_id }` | 取消等待队列中的一条 run |
|
||||
| `approval.respond` | `{ session_id, approval_id, choice }` | 响应 Bridge 工具审批 |
|
||||
|
||||
客户端不再发送独立 `command` 或 `steer` Socket.IO 事件;slash command 作为普通 `run.input` 进入 `/chat-run`,由服务端在 `source=cli` 时解析。
|
||||
|
||||
### 服务端 → 客户端
|
||||
|
||||
| 事件 | 说明 |
|
||||
|------|------|
|
||||
| `resumed` | 返回 DB 消息、运行状态、队列长度和最近事件 |
|
||||
| `run.started` | 运行开始 |
|
||||
| `run.queued` | 当前 session 已有运行,新请求进入队列 |
|
||||
| `message.delta` | 文本增量 |
|
||||
| `reasoning.delta` | reasoning 增量 |
|
||||
| `thinking.delta` | thinking 增量 |
|
||||
| `reasoning.available` | reasoning 内容可用 |
|
||||
| `tool.started` | 工具调用开始 |
|
||||
| `tool.completed` | 工具调用结束 |
|
||||
| `approval.requested` | Bridge 工具请求人工审批 |
|
||||
| `approval.resolved` | 审批完成或超时 |
|
||||
| `compression.started` | 上下文压缩开始 |
|
||||
| `compression.completed` | 上下文压缩结束 |
|
||||
| `usage.updated` | token 用量更新 |
|
||||
| `abort.started` | 中断开始 |
|
||||
| `abort.completed` | 中断结束 |
|
||||
| `session.command` | slash command 的执行结果或错误反馈 |
|
||||
| `run.completed` | 运行完成 |
|
||||
| `run.failed` | 运行失败 |
|
||||
|
||||
### 认证
|
||||
|
||||
`/chat-run` 使用 Socket.IO auth token:
|
||||
|
||||
```ts
|
||||
io(`${baseUrl}/chat-run`, {
|
||||
auth: { token },
|
||||
query: { profile },
|
||||
})
|
||||
```
|
||||
|
||||
服务端会与 Web UI token 比对。
|
||||
|
||||
---
|
||||
|
||||
## ChatRunSocket 后端行为
|
||||
|
||||
### API Server 路径
|
||||
|
||||
`source=api_server` 时:
|
||||
|
||||
1. 写入用户消息到 Web UI 本地 session DB。
|
||||
2. 通过 `buildCompressedHistory()` 构建上下文。
|
||||
3. 请求当前 profile 的 Hermes Gateway:
|
||||
|
||||
```text
|
||||
POST <upstream>/v1/responses
|
||||
```
|
||||
|
||||
4. 读取 SSE frame,映射为统一的 `/chat-run` 事件。
|
||||
5. 完成后写入 assistant/tool 消息,更新 usage。
|
||||
|
||||
### Bridge 路径
|
||||
|
||||
`source=cli` 时:
|
||||
|
||||
1. 写入用户消息到 Web UI 本地 session DB。
|
||||
2. 复用同一套 `buildCompressedHistory()` 构建压缩上下文。
|
||||
3. 调用:
|
||||
|
||||
```ts
|
||||
this.bridge.chat(session_id, input, history, instructions, profile)
|
||||
```
|
||||
|
||||
4. 轮询 `AgentBridgeClient.streamOutput(run_id)`。
|
||||
5. 将 Python bridge 的 delta 和 events 映射成统一事件。
|
||||
6. 将 assistant 文本、reasoning、tool 调用结果 flush 回 DB。
|
||||
|
||||
### 队列
|
||||
|
||||
同一个 `session_id` 同时只能有一个 active run。新的 `run` 到达时:
|
||||
|
||||
- 如果当前 session 正在运行,则放入 `state.queue`。
|
||||
- 发送 `run.queued` 更新队列长度。
|
||||
- 当前 run 结束或 abort 完成后,自动执行下一条 queued run。
|
||||
|
||||
---
|
||||
|
||||
## Python Agent Bridge
|
||||
|
||||
### 通信协议
|
||||
|
||||
Node 和 Python bridge 之间使用本地 socket 的单行 JSON 协议:
|
||||
|
||||
```json
|
||||
{ "action": "chat", "session_id": "xxx", "message": "hello" }
|
||||
```
|
||||
|
||||
响应也是单行 JSON:
|
||||
|
||||
```json
|
||||
{ "ok": true, "run_id": "xxx", "session_id": "xxx", "status": "running" }
|
||||
```
|
||||
|
||||
### Endpoint
|
||||
|
||||
默认 endpoint 按平台选择:
|
||||
|
||||
| 平台 | 默认 endpoint |
|
||||
|------|---------------|
|
||||
| Windows | `tcp://127.0.0.1:18765` |
|
||||
| macOS/Linux | `ipc:///tmp/hermes-agent-bridge.sock` |
|
||||
|
||||
Windows 使用 TCP 是因为部分 Python/Windows 环境没有 Unix domain socket 支持。
|
||||
|
||||
### 当前实际使用的 action
|
||||
|
||||
| Action | 说明 |
|
||||
|--------|------|
|
||||
| `chat` | 启动一轮 `AIAgent.run_conversation()` |
|
||||
| `get_output` | 通过 `cursor` 和 `event_cursor` 获取增量文本与事件 |
|
||||
| `interrupt` | 调用 agent 中断当前运行 |
|
||||
| `approval_respond` | 响应工具审批 |
|
||||
| `destroy_all` | 维护动作;仅用于明确的全量清理/进程关闭场景,普通 profile 切换不会调用 |
|
||||
|
||||
bridge 代码里还保留了一些调试/维护 action,例如 `ping`、`get_result`、`get_history`、`destroy`、`list`、`shutdown`、`steer`。当前 `/chat-run` 前端路径不会直接暴露这些 action;需要的能力由 Node `/chat-run` 层封装,例如 `/steer` slash command 会调用 `steer` action。
|
||||
|
||||
旧的 `command` action 已移除,Python bridge 不再直接解析 `/new`、`/undo`、`/retry`、`/branch` 等旧斜杠命令;当前 CLI/Bridge slash command 支持范围以 Node `/chat-run` 的 `session-command.ts` 为准。
|
||||
|
||||
### 会话和 profile
|
||||
|
||||
`AgentPool` 维护 `session_id -> AgentSession`:
|
||||
|
||||
- 每个 session 持有独立 `AIAgent` 实例。
|
||||
- session 按请求中的 profile 创建和复用;前端切换 Hermes Profile 只改变后续请求使用的 profile,不会影响其他 bridge 内存 session。
|
||||
- `HERMES_HOME` 会在创建 agent 时临时切到 profile home。
|
||||
- `SessionDB` 按 profile 的 `state.db` 路径缓存。
|
||||
- 空闲 session 会被 bridge GC,默认 30 分钟无运行后销毁内存态。
|
||||
|
||||
### 工具和审批事件
|
||||
|
||||
bridge 从 `AIAgent` 回调中收集事件:
|
||||
|
||||
- `stream.delta`
|
||||
- `reasoning.delta`
|
||||
- `thinking.delta`
|
||||
- `tool.started`
|
||||
- `tool.completed`
|
||||
- `tool.progress`
|
||||
- `approval.requested`
|
||||
- `approval.resolved`
|
||||
- `turn.boundary`
|
||||
- `status`
|
||||
|
||||
`ChatRunSocket` 会把这些事件转换为前端统一事件,并负责 DB 落盘。
|
||||
|
||||
审批默认等待 60 秒,超时自动 `deny`。
|
||||
|
||||
---
|
||||
|
||||
## AgentBridgeClient
|
||||
|
||||
`AgentBridgeClient` 是 Node 端本地 socket 客户端。
|
||||
|
||||
行为:
|
||||
|
||||
- 支持 `ipc://` 和 `tcp://` endpoint。
|
||||
- 每次请求新建 socket,发送一行 JSON,读取一行 JSON。
|
||||
- 请求通过内部 lock 串行化。
|
||||
- 默认请求响应超时为 `120000ms`。
|
||||
- `streamOutput()` 每 100ms 轮询一次 `get_output`。
|
||||
|
||||
示例:
|
||||
|
||||
```ts
|
||||
const started = await bridge.chat(sessionId, input, history, instructions, profile)
|
||||
|
||||
for await (const chunk of bridge.streamOutput(started.run_id)) {
|
||||
// chunk.delta
|
||||
// chunk.events
|
||||
// chunk.done
|
||||
}
|
||||
```
|
||||
|
||||
注意:目前 socket connect 阶段没有独立 connect timeout,主要依赖系统连接错误和请求响应 timeout。
|
||||
|
||||
---
|
||||
|
||||
## AgentBridgeManager
|
||||
|
||||
`AgentBridgeManager` 负责启动和停止 Python bridge。
|
||||
|
||||
启动流程:
|
||||
|
||||
1. 定位 `hermes_bridge.py`。
|
||||
2. 发现 `hermes-agent` 根目录。
|
||||
3. 选择 Python 解释器。
|
||||
4. 以子进程启动:
|
||||
|
||||
```text
|
||||
python hermes_bridge.py --endpoint <endpoint> --agent-root <root> --hermes-home <home>
|
||||
```
|
||||
|
||||
5. 监听 stdout,等待:
|
||||
|
||||
```json
|
||||
{ "event": "ready", "endpoint": "..." }
|
||||
```
|
||||
|
||||
6. 默认 ready 超时为 `120000ms`。
|
||||
|
||||
Python 选择优先级:
|
||||
|
||||
1. `HERMES_AGENT_BRIDGE_PYTHON`
|
||||
2. `agentRoot/venv` 或 `agentRoot/.venv`
|
||||
3. installed `hermes` 命令 shebang
|
||||
4. `uv run --project <agentRoot> python`
|
||||
5. 系统 `python3` / `python`
|
||||
|
||||
关闭时先发 `SIGTERM`,1.5 秒后仍未退出则 `SIGKILL`。
|
||||
|
||||
---
|
||||
|
||||
## 启动与关闭
|
||||
|
||||
### 启动
|
||||
|
||||
`bootstrap()` 中会先尝试启动 bridge:
|
||||
|
||||
```ts
|
||||
agentBridgeManager = await startAgentBridgeManager()
|
||||
```
|
||||
|
||||
bridge 启动失败不会阻止 Web UI 启动,但 Bridge(beta) 会话后续运行会失败。
|
||||
|
||||
随后创建统一的 chat socket:
|
||||
|
||||
```ts
|
||||
chatRunServer = new ChatRunSocket(groupChatServer.getIO())
|
||||
chatRunServer.init()
|
||||
```
|
||||
|
||||
### 关闭
|
||||
|
||||
服务关闭时会清理:
|
||||
|
||||
- `/chat-run` Socket.IO 状态
|
||||
- Python agent bridge 子进程
|
||||
- 其他 WebSocket/Socket.IO 服务
|
||||
|
||||
---
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `HERMES_AGENT_BRIDGE_ENDPOINT` | Bridge endpoint;Windows 默认 `tcp://127.0.0.1:18765`,macOS/Linux 默认 `ipc:///tmp/hermes-agent-bridge.sock` |
|
||||
| `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`;仅在 worker transport 为 TCP 时生效 |
|
||||
| `HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_TRANSPORT` | Version Preview 的 bridge broker endpoint transport;设为 `tcp` 可让预览环境在 macOS/Linux 上也使用 loopback TCP;未设置时会跟随 `HERMES_AGENT_BRIDGE_WORKER_TRANSPORT=tcp` |
|
||||
| `HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_ENDPOINT` | 直接覆盖 Version Preview 的 bridge broker endpoint;用于需要完全自定义预览 bridge 地址的部署 |
|
||||
| `HERMES_AGENT_BRIDGE_TIMEOUT_MS` | Node 等待 bridge 请求响应的超时,默认 `120000` ms |
|
||||
| `HERMES_AGENT_BRIDGE_CONNECT_RETRY_MS` | Node 连接 bridge socket 失败时的短重试窗口,默认 `5000` ms |
|
||||
| `HERMES_AGENT_BRIDGE_STARTUP_TIMEOUT_MS` | Node 等待 Python bridge ready 的超时,默认 `120000` ms |
|
||||
| `HERMES_AGENT_BRIDGE_AUTO_RESTART` | bridge broker 意外退出后是否自动重启;设为 `0`/`false`/`no`/`off` 可关闭,默认开启 |
|
||||
| `HERMES_AGENT_BRIDGE_RESTART_DELAY_MS` | bridge broker 自动重启基础延迟,默认 `1000` ms,连续失败时最多退避到 `30000` ms |
|
||||
| `HERMES_AGENT_BRIDGE_PYTHON` | 指定 Python 解释器路径 |
|
||||
| `HERMES_AGENT_ROOT` | hermes-agent 安装目录 |
|
||||
| `HERMES_AGENT_BRIDGE_UV` | 指定 uv 可执行文件路径 |
|
||||
| `HERMES_AGENT_BRIDGE_PLATFORM` | bridge 传给 Hermes Agent 的平台标识,默认 `cli` |
|
||||
| `HERMES_BRIDGE_PROVIDER` | 覆盖 bridge 使用的 provider |
|
||||
| `HERMES_BRIDGE_MAX_TURNS` | 覆盖 bridge 最大轮数 |
|
||||
| `UV` | uv 可执行文件路径 fallback |
|
||||
|
||||
正常使用不需要配置这些变量。Bridge 支持多个用户/多个 profile 的运行并存;Web UI 的 Hermes Profile 切换不会重启 bridge 或销毁其他正在运行的任务。`HERMES_AGENT_BRIDGE_ENDPOINT` 控制 Node 与 Python bridge broker 的连接地址;`HERMES_AGENT_BRIDGE_WORKER_TRANSPORT` 控制 broker 与每个 profile worker 的连接方式。macOS/Linux 默认仍使用 IPC;如果 Electron、沙盒或安全软件环境下 IPC 不稳定,可以设置 `HERMES_AGENT_BRIDGE_WORKER_TRANSPORT=tcp` 切到 loopback TCP。Version Preview 默认继续使用独立的 broker endpoint,也会为 TCP worker 使用独立端口段,避免和正式实例共享端口池;如需让 Preview 的 broker 在 macOS/Linux 上也走 TCP,可设置 `HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_TRANSPORT=tcp`,未设置时会跟随 `HERMES_AGENT_BRIDGE_WORKER_TRANSPORT=tcp`。Windows 下如果默认 TCP 端口被旧 bridge/broker/worker 占用,新 bridge 会先按端口杀掉旧进程树,再用同一个 endpoint 重建。
|
||||
|
||||
Windows 首次启动慢时可以临时放大:
|
||||
|
||||
```powershell
|
||||
$env:HERMES_AGENT_BRIDGE_STARTUP_TIMEOUT_MS = "300000"
|
||||
$env:HERMES_AGENT_BRIDGE_TIMEOUT_MS = "300000"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 当前限制
|
||||
|
||||
- Bridge(beta) 仍依赖 Python bridge 成功启动;启动失败时 Web UI 可用,但 bridge 会话不可用。
|
||||
- bridge socket connect 阶段还没有单独 connect timeout。
|
||||
- 旧 CLI 独立面板和独立 `/cli-chat-run` namespace 已移除。
|
||||
- 旧 bridge `command/steer` socket 控制层已移除;CLI/Bridge slash command 现在通过统一 `/chat-run` 的 `run.input` 解析并以 `session.command` 反馈。
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# Docker Compose Guide
|
||||
|
||||
This repository ships an environment-variable driven Docker Compose setup.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Pull pre-built image (Recommended)
|
||||
|
||||
```bash
|
||||
WEBUI_IMAGE=ekkoye8888/hermes-web-ui docker compose up -d
|
||||
docker compose logs -f hermes-webui
|
||||
```
|
||||
|
||||
Open: `http://localhost:6060`
|
||||
|
||||
### Build from source
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
docker compose logs -f hermes-webui
|
||||
```
|
||||
|
||||
## Services
|
||||
|
||||
This compose file runs a single service:
|
||||
|
||||
- `hermes-webui` — Web UI dashboard with integrated Hermes Agent runtime (pre-built image or built from source)
|
||||
|
||||
The Web UI container is built on the `nousresearch/hermes-agent` base image and uses the Hermes CLI / agent bridge runtime for chat execution. It does not start or manage a separate Hermes gateway process.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
All key runtime settings are configured from compose variables.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `PORT` | `6060` | Web UI listen port |
|
||||
| `BIND_HOST` | `0.0.0.0` | Optional Web UI bind host. Defaults to IPv4 for stable WSL/Windows access. Set `::` explicitly if you want IPv6 listening. |
|
||||
| `HERMES_BIN` | `/opt/hermes/.venv/bin/hermes` | Path to Hermes CLI binary |
|
||||
| `HERMES_AGENT_IMAGE` | `nousresearch/hermes-agent:latest` | Hermes Agent base image (used only during build) |
|
||||
| `WEBUI_IMAGE` | `hermes-web-ui-local:latest` | Web UI image (set to `ekkoye8888/hermes-web-ui` to use pre-built) |
|
||||
| `HERMES_DATA_DIR` | `./hermes_data` | Hermes runtime data directory |
|
||||
|
||||
Override variables directly from shell:
|
||||
|
||||
```bash
|
||||
PORT=16060 docker compose up -d
|
||||
```
|
||||
|
||||
Or create a `.env` file in the project root:
|
||||
|
||||
```
|
||||
WEBUI_IMAGE=ekkoye8888/hermes-web-ui
|
||||
PORT=6060
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
| Path | Description |
|
||||
|---|---|
|
||||
| `${HERMES_DATA_DIR}` (`./hermes_data`) | Hermes runtime data (sessions, config, profiles) |
|
||||
| `${HERMES_DATA_DIR}/hermes-web-ui` | Web UI data (auth token, etc.) |
|
||||
|
||||
- Hermes data persists in `./hermes_data`, mapped to `/home/agent/.hermes` in the container.
|
||||
- Web UI data persists in `./hermes_data/hermes-web-ui/`, mapped to `/home/agent/.hermes-web-ui` in the container.
|
||||
- The auth token is auto-generated on first run and printed to container logs.
|
||||
- Deleting the token file and restarting will generate a new one.
|
||||
|
||||
## Port Mapping
|
||||
|
||||
| Port | Description |
|
||||
|---|---|
|
||||
| `${PORT}` (6060) | Web UI dashboard |
|
||||
|
||||
No Hermes gateway ports are exposed by this compose setup.
|
||||
|
||||
## Code Runtime Behavior
|
||||
|
||||
- Hermes CLI binary comes from `HERMES_BIN` env (`packages/server/src/services/hermes-cli.ts`).
|
||||
- If `HERMES_BIN` is not provided, code falls back to `hermes` in `PATH`.
|
||||
- Profile-specific chat runs are handled through the Hermes agent bridge. The selected/requested profile is authorized per account and passed with runtime requests; switching the frontend Hermes Profile does not restart the bridge or clear other running tasks.
|
||||
- Docker is a managed gateway runtime: Web UI checks profile gateways on startup, but it does not run a periodic gateway recovery loop.
|
||||
|
||||
## Common Operations
|
||||
|
||||
Recreate:
|
||||
|
||||
```bash
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
View auth token:
|
||||
|
||||
```bash
|
||||
docker compose logs hermes-webui | grep token
|
||||
# or
|
||||
cat ./hermes_data/hermes-web-ui/.token
|
||||
```
|
||||
|
||||
Stop:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
# Harness Overview
|
||||
|
||||
This harness turns recurring project knowledge into files and checks that an
|
||||
agent can discover without chat history.
|
||||
|
||||
## Goals
|
||||
|
||||
- Make repository context legible through short maps and deeper docs.
|
||||
- Keep architecture constraints close to the code they protect.
|
||||
- Give agents a deterministic validation path before opening or updating a PR.
|
||||
- Prefer mechanical checks over reminder text when a rule can be verified.
|
||||
|
||||
## Entry Points
|
||||
|
||||
- `AGENTS.md` is the root map for coding agents.
|
||||
- `ARCHITECTURE.md` documents package boundaries and state ownership.
|
||||
- `DEVELOPMENT.md` remains the contributor rules and command reference.
|
||||
- `docs/harness/validation.md` maps change types to checks.
|
||||
- `docs/harness/worktree-runbook.md` explains isolated worktree development.
|
||||
- `docs/harness/pr-review.md` provides a PR self-review checklist.
|
||||
- `scripts/harness-check.mjs` enforces baseline repository invariants.
|
||||
|
||||
## Operating Model
|
||||
|
||||
1. Read the root map and the specific doc for the task.
|
||||
2. Make the smallest scoped change.
|
||||
3. Add or update focused tests when behavior changes.
|
||||
4. Run `npm run harness:check` and the relevant validation commands.
|
||||
5. If a failure pattern repeats, improve this harness with docs, tests, scripts,
|
||||
or CI instead of relying on a longer prompt.
|
||||
|
||||
## What Belongs In The Harness
|
||||
|
||||
- Facts that future agents must know to work safely.
|
||||
- Checklists that prevent repeated PR review comments.
|
||||
- Scripts that fail fast on repository-wide invariants.
|
||||
- Runbooks for local, CI, release, and desktop packaging flows.
|
||||
|
||||
Do not put long implementation notes in `AGENTS.md`. Add them under `docs/` and
|
||||
link to them from the map.
|
||||
@@ -0,0 +1,41 @@
|
||||
# PR Self-Review
|
||||
|
||||
Use this checklist before pushing or updating a pull request.
|
||||
|
||||
## Scope
|
||||
|
||||
- The PR title states the behavior being changed.
|
||||
- The diff is limited to the requested task and required harness updates.
|
||||
- Unrelated formatting or refactors are not bundled into the change.
|
||||
- User-facing text has locale coverage.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Client code uses shared API helpers and existing UI patterns.
|
||||
- Server routes stay thin and delegate reusable behavior to controllers/services.
|
||||
- Web UI state uses `config.appHome` or documented helpers.
|
||||
- Hermes Agent state and Web UI state remain separate.
|
||||
- Subprocess calls use argument arrays instead of shell string construction.
|
||||
|
||||
## Tests And Validation
|
||||
|
||||
- A focused test was added or updated for behavior changes.
|
||||
- Browser-visible flows have e2e coverage when the risk justifies it.
|
||||
- `npm run harness:check` passes.
|
||||
- The PR body lists validation commands that actually ran.
|
||||
- Known limitations or follow-ups are called out.
|
||||
|
||||
## Release And CI
|
||||
|
||||
- Workflow changes were checked with `npm run harness:check`.
|
||||
- Desktop release artifacts remain platform-specific.
|
||||
- `fail_on_unmatched_files: true` is preserved when each matrix target has its
|
||||
own expected artifact list.
|
||||
- Package manifest changes have matching lockfile changes when dependencies
|
||||
change.
|
||||
|
||||
## Before Merge
|
||||
|
||||
- CI is green or failures are explained as unrelated.
|
||||
- The branch is mergeable.
|
||||
- The PR does not depend on hidden local state, credentials, or uncommitted files.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Validation Guide
|
||||
|
||||
Run the smallest relevant checks while iterating. Escalate to the broad checks
|
||||
when touching shared behavior, release automation, auth, persistence, or chat.
|
||||
|
||||
## Always Run For PRs
|
||||
|
||||
```bash
|
||||
npm run harness:check
|
||||
```
|
||||
|
||||
For broad or shared changes, also run:
|
||||
|
||||
```bash
|
||||
npm run test:coverage
|
||||
npm run test:e2e
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Change-Type Matrix
|
||||
|
||||
| Change | Minimum local validation |
|
||||
| --- | --- |
|
||||
| Docs only | `npm run harness:check` |
|
||||
| Client component/store/API | focused `npm run test -- <pattern>`, then `npm run build` |
|
||||
| User-visible browser flow | focused Vitest plus `npm run test:e2e` |
|
||||
| Server controller/service/db | focused `npm run test -- tests/server/<file>` |
|
||||
| Auth, profile, or credential behavior | focused server tests plus relevant e2e auth tests |
|
||||
| Chat, Socket.IO, group chat | focused server tests plus relevant e2e chat tests |
|
||||
| Desktop packaging | `npm run harness:check`, `npm run build`, and a platform-specific desktop build when practical |
|
||||
| GitHub workflow | `npm run harness:check` and `actionlint` when available |
|
||||
| Package manifests | `npm ci --ignore-scripts` and lockfile workflow expectations |
|
||||
|
||||
## CI Mapping
|
||||
|
||||
- Build workflow: installs dependencies, runs coverage, and builds production
|
||||
assets on pushes and pull requests.
|
||||
- Playwright workflow: runs browser e2e tests.
|
||||
- NPM lockfile workflow: verifies `package-lock.json` is synchronized.
|
||||
- Desktop release and manual desktop build workflows build and upload
|
||||
platform-specific desktop artifacts.
|
||||
- Docker workflow: builds and publishes release images.
|
||||
|
||||
## Release Workflow Guardrail
|
||||
|
||||
Desktop release jobs must upload only the artifacts that their matrix target can
|
||||
produce. Keep artifact globs in matrix data and keep `fail_on_unmatched_files:
|
||||
true` so missing expected files still fail.
|
||||
|
||||
Expected desktop release outputs:
|
||||
|
||||
| Target | Required release globs |
|
||||
| --- | --- |
|
||||
| macOS | `*.dmg`, `*.dmg.blockmap`, `*.zip`, `*.zip.blockmap`, `latest*.yml` |
|
||||
| Windows | `*.exe`, `*.exe.blockmap`, `latest*.yml` |
|
||||
| Linux x64 | `*.AppImage`, `*.deb`, `latest*.yml` |
|
||||
| Linux arm64 | `*.AppImage`, `latest*.yml` |
|
||||
|
||||
## Failure Handling
|
||||
|
||||
When a command fails:
|
||||
|
||||
1. Read the first actionable error, not just the final stack trace.
|
||||
2. Check whether the failure indicates missing context, missing test coverage,
|
||||
or a missing mechanical rule.
|
||||
3. Fix the product bug when there is one.
|
||||
4. Update docs or `scripts/harness-check.mjs` when the same class of mistake
|
||||
should be prevented next time.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Worktree Runbook
|
||||
|
||||
Use a separate git worktree for agent changes so local user work remains
|
||||
untouched.
|
||||
|
||||
## Create A Worktree
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/<short-topic> ../worktrees/hermes-web-ui-<short-topic> origin/main
|
||||
cd ../worktrees/hermes-web-ui-<short-topic>
|
||||
```
|
||||
|
||||
If the repository uses a fork remote, push to the remote requested by the task.
|
||||
Do not rewrite or reset unrelated branches.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm ci --ignore-scripts
|
||||
npm rebuild node-pty
|
||||
```
|
||||
|
||||
Desktop package dependencies are separate:
|
||||
|
||||
```bash
|
||||
npm ci --prefix packages/desktop --no-audit --no-fund
|
||||
```
|
||||
|
||||
## Isolated Runtime
|
||||
|
||||
Use per-worktree state and ports to avoid colliding with a running local app:
|
||||
|
||||
```bash
|
||||
export PORT=18648
|
||||
export HERMES_WEB_UI_HOME="$PWD/.tmp/hermes-web-ui"
|
||||
export HERMES_WEBUI_STATE_DIR="$HERMES_WEB_UI_HOME"
|
||||
export UPLOAD_DIR="$PWD/.tmp/uploads"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Do not point `HERMES_WEB_UI_HOME` at a user's real `~/.hermes-web-ui` when a task
|
||||
only needs local verification.
|
||||
|
||||
## Browser Checks
|
||||
|
||||
For browser-visible changes:
|
||||
|
||||
```bash
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
Prefer existing Playwright fixtures and mocked backend services. Add real-service
|
||||
requirements only when the behavior cannot be represented with mocks.
|
||||
|
||||
## Cleanup
|
||||
|
||||
After a PR is pushed and no more local work is needed:
|
||||
|
||||
```bash
|
||||
git worktree remove ../worktrees/hermes-web-ui-<short-topic>
|
||||
```
|
||||
|
||||
Only remove the worktree you created.
|
||||
+3449
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user