状态:本文档按当前
main重新整理普通 Chat 会话的完整实现链路。旧的独立 CLI Chat 面板、/cli-chat-runnamespace、cli-chat.ts客户端和 Python bridge 直连命令层已经不再是产品入口。最后重建时间:2026-06-03。
维护要求:后续 PR 如果修改本文列出的普通 Chat 链路核心文件,需要新增
docs/chat-chain-changes/下的独立变更片段。每个 PR 一个变更片段,写清楚 修改时间、PR/commit、动到的功能和行为影响,避免多个 PR 同时改本文产生冲突。packages/server/src/services/hermes/agent-bridge/是普通 Chat 的核心链路, 该目录下任何改动都算 Chat 链路改动;即使只是启动、环境变量、日志或品牌 attribution,也要记录影响范围,必要时明确“运行行为无变化”。packages/server/src/services/hermes/group-chat/、/group-chatSocket.IO、 group-chat 前端 store/API/component、共享压缩器和 context-engine 也属于核心 聊天链路,改动时同样需要记录。
当前普通 Chat 的主链路是:
ChatPanel / ChatInput
-> Pinia chat store
-> packages/client/src/api/hermes/chat.ts
-> Socket.IO namespace /chat-run
-> ChatRunSocket
-> handleBridgeRun()
-> AgentBridgeClient
-> hermes_bridge.py broker
-> profile worker
-> AIAgent / Hermes Agent tools
也就是说,当前 Web UI 普通聊天默认不是走 Hermes Gateway /v1/responses,
而是走 source=cli 的 Agent Bridge 路径。代码里仍保留 api_server
类型和 handle-api-run.ts,但 resolveRunSource() 当前固定返回 cli。
核心原则:
/chat-run。sessions/messages 表。resume 重新加入同一个 session:{id} room。/chat-run 事件通道。变更记录按 PR 拆分保存在 docs/chat-chain-changes/。新增 Chat session chain、
Agent Bridge、compression 或 Group Chat 核心链路改动时,新增一个 fragment 文件,
不要继续修改本文维护集中表格。
每个 fragment 至少包含:
---
date: YYYY-MM-DD
pr: 1234
feature: 改动功能
impact: 行为影响
---
补充说明。
开 PR 前还没有编号时可以先写 pr: pending,PR 创建后再把该 fragment 改成实际
PR 号。
| 文件 | 职责 |
|---|---|
packages/client/src/components/hermes/chat/ChatPanel.vue |
普通 Chat 页面容器,组合消息列表、输入框、审批条、澄清条、抽屉面板。 |
packages/client/src/components/hermes/chat/ChatInput.vue |
输入框、发送、附件、停止按钮入口。 |
packages/client/src/components/hermes/chat/MessageList.vue / VirtualMessageList.vue |
消息列表渲染和虚拟滚动。 |
packages/client/src/components/hermes/chat/MessageItem.vue |
单条消息渲染,包含 assistant、tool、reasoning、附件等 UI。 |
packages/client/src/stores/hermes/chat.ts |
Chat 核心状态机:session 列表、发送、resume、队列、流式事件、审批、澄清、abort、压缩状态。 |
packages/client/src/api/hermes/chat.ts |
/chat-run Socket.IO 客户端,负责连接、全局事件分发、run/resume/abort/approval/clarify 协议。 |
packages/client/src/api/hermes/sessions.ts |
HTTP session API:列表、详情分页、删除、重命名、模型更新。 |
packages/client/src/api/hermes/group-chat.ts |
/group-chat Socket.IO 客户端和 group-chat HTTP room/agent/config API。 |
packages/client/src/stores/hermes/group-chat.ts |
Group Chat 状态机:rooms、members、messages、agents、streaming、context/compression 状态。 |
packages/client/src/components/hermes/group-chat/* |
Group Chat 页面、输入框、消息列表、成员/agent 展示和房间创建配置。 |
| 文件 | 职责 |
|---|---|
packages/server/src/services/hermes/run-chat/index.ts |
ChatRunSocket,/chat-run namespace 入口、认证、profile 校验、run/resume/abort/approval/clarify/queue 分发。 |
packages/server/src/services/hermes/run-chat/handle-bridge-run.ts |
当前主运行路径:创建/更新本地 session,构建上下文,调用 Agent Bridge,消费 bridge 事件,落库。 |
packages/server/src/services/hermes/run-chat/handle-api-run.ts |
保留的 API Server 路径实现;当前 resolveRunSource() 固定返回 cli,正常不会走到这里。 |
packages/server/src/services/hermes/run-chat/session-command.ts |
slash command 解析和执行。 |
packages/server/src/services/hermes/run-chat/abort.ts |
active run 中断、状态落盘、队列衔接。 |
packages/server/src/services/hermes/run-chat/compression.ts |
DB history 构建、snapshot-aware history、上下文压缩。 |
packages/server/src/services/hermes/run-chat/bridge-message.ts |
Bridge assistant/tool 消息的内存态与 DB flush。 |
packages/server/src/services/hermes/run-chat/bridge-delta.ts |
过滤 bridge 输出中的工具调用标记,避免 UI 文本重复或丢字符。 |
packages/server/src/services/hermes/agent-bridge/client.ts |
Node 到 Python bridge 的本地 socket 客户端。 |
packages/server/src/services/hermes/agent-bridge/manager.ts |
Python bridge broker 子进程生命周期管理。 |
packages/server/src/services/hermes/agent-bridge/python/hermes_bridge.py |
Python broker/worker entrypoint;实现拆分在同目录的 bridge_*.py 模块中,覆盖 AIAgent 会话池、工具审批、澄清、压缩协作、goal/plan 命令等。 |
packages/server/src/services/hermes/group-chat/index.ts |
/group-chat Socket.IO server、room/member/message 存储、agent 恢复、mention 分发、approval/interrupt 入口。 |
packages/server/src/services/hermes/group-chat/agent-clients.ts |
Group Chat agent socket client,调用 Agent Bridge 执行被 mention 的 agent,并同步 tool/reasoning/context 状态。 |
packages/server/src/services/hermes/context-engine/* |
Group Chat 上下文压缩和 summary cache。 |
packages/server/src/lib/context-compressor/* |
普通 Chat 和 Group Chat 共用的 token 估算、摘要压缩和 context message 处理。 |
packages/server/src/routes/hermes/group-chat.ts |
Group Chat HTTP room/agent/config/compress/clear-context API。 |
packages/server/src/db/hermes/session-store.ts |
Web UI 本地 session/message SQLite 存储。 |
packages/server/src/controllers/hermes/sessions.ts |
HTTP session 列表、详情、分页、删除、导入/导出等控制器。 |
普通 Chat 使用 Web UI 本地 SQLite,而不是直接把 Hermes CLI 历史当作唯一状态。
核心表由 packages/server/src/db/hermes/schemas.ts 初始化。
session-store.ts 暴露的主要字段:
| 字段 | 说明 |
|---|---|
id |
Web UI session id。前端新建时生成,发送 run 时传入。 |
profile |
Hermes profile。新 session 使用当前 active profile,已存在 session 优先使用 DB 中的 profile。 |
source |
当前会话来源。当前普通 Chat 实际写入 cli。历史数据可能存在 api_server。 |
model / provider |
session 绑定模型。首轮发送会写入选中的模型/供应商,后续可更新。 |
title / preview |
会话标题和预览。标题可以由 /title 改,也可由首条用户输入生成。 |
workspace |
当前工作目录上下文,会被注入 run instructions。 |
message_count / tool_call_count |
由 updateSessionStats() 从 messages 表统计。 |
input_tokens / output_tokens |
usage 统计结果。 |
last_active / started_at / ended_at |
会话时间元数据。 |
主要 role:
| role | 说明 |
|---|---|
user |
普通用户输入。 |
command |
用户输入的 slash command 展示消息。 |
assistant |
Agent 输出文本,可带 reasoning / reasoning_content。 |
tool |
工具执行结果。 |
工具调用相关字段:
tool_call_idtool_callstool_namefinish_reasonreasoningreasoning_detailsreasoning_content前端读取历史时通过 mapHermesMessages() 把 DB 行映射成 UI Message,包括:
useChatStore() 是普通 Chat 的中心状态。
常见状态:
| 状态 | 说明 |
|---|---|
sessions |
当前加载的 session 列表和每个 session 的消息数组。 |
activeSessionId / activeSession |
当前页面正在显示的 session。 |
serverWorking |
前端认为服务端仍有 active run 的 session id 集合。 |
streamStates |
当前前端已注册流式 handler 的 session。 |
queueLengths / queued user messages |
每个 session 的服务端队列长度和 UI 可见队列消息。 |
pendingApprovals |
工具审批请求,按 sessionId + approvalId 存。 |
pendingClarifies |
用户澄清请求,按 sessionId + clarifyId 存。 |
compressionStates |
上下文压缩中的临时 UI 状态。 |
abortState |
当前 active run 的中断状态。 |
普通新建入口最终调用:
newChat(options)
它会:
历史上的 newCliSession() 仍存在,但普通 Chat 当前也是 source=cli,不再需要单独 UI 面板区分。
切换 session 时,store 会优先通过 Socket.IO resume:
resumeSession(sessionId, onResumed, profile)
服务端会:
socket.join("session:<id>")sessionMap 有状态,直接返回。前端收到 resumed 后会:
isWorking、isAborting、queue、usage。入口是 chat.ts store 的:
sendMessage(content, attachments?)
主要步骤:
sid,后续所有回调都用这个 sid,避免用户切换 session 后事件写错地方。content.trim().startsWith("/")。/upload;ContentBlock[];type/name/path/media_type;type/name/path/media_type?。当前 payload 形态:
{
input,
session_id: sid,
profile,
model,
provider,
model_groups,
queue_id: userMsg.id,
source: "cli",
}
首轮发送会带 model/provider,已存在 session 后通常依赖 DB 中的 session 模型。
startRunViaSocket() 负责:
connectChatRun(profile) 建立或复用 /chat-run socket。session_id 的事件 handlers 到 sessionEventHandlers。发送:
socket.emit("run", body)
如果同一个 session 已经有 handler,新的 run 只 emit,不重复注册,避免多 tab/多次发送造成事件重复处理。
/chat-run 连接和认证前端连接:
io(`${baseUrl}/chat-run`, {
auth: { token },
query: { profile },
transports: ["websocket", "polling"],
reconnection: true,
})
后端 ChatRunSocket.authMiddleware():
authenticateUserToken() 验证 token。socket.data.user。每次 run 时还会调用 resolveRunProfile():
profile 优先。userCanAccessProfile()。ChatRunSocket 监听:
| 客户端事件 | 服务端行为 |
|---|---|
run |
解析 profile、source、slash command、queue,然后进入 run handler。 |
resume |
加入 session room,返回 DB/内存状态。 |
abort |
调用 handleAbort()。 |
cancel_queued_run |
从 session queue 删除指定 queue item。 |
approval.respond |
转发到 bridge approval_respond。 |
clarify.respond |
转发到 bridge clarify_respond。 |
当前代码:
export function resolveRunSource(_source?: string, _sessionId?: string): ChatRunSource {
return "cli"
}
因此:
source: "api_server",当前仍会被解析为 cli。handleApiRun() 是保留实现,不是当前普通 Chat 的实际路径。source 写为 cli。后端内存态是:
Map<string, SessionState>
每个 SessionState 包含:
messagesisWorkingisAbortingeventsqueuerunIdactiveRunMarkerprofilesourceDB 是持久层,sessionMap 是运行时和 resume 用的 transient 层。
实际执行在:
handleBridgeRun(...)
服务端会:
runMarker = cli_run_<...>,用于把同一轮用户、assistant、tool 消息串起来。SessionState:
isWorking = trueisAborting = falsesource = "cli"state.messages.push(...)createSession({ source: "cli" })addMessage() 写入 DBsocket.join("session:<id>")run.peer_user_message。最终 instructions 包含:
getSystemPrompt()instructionsX-Hermes-Profilerun 前调用:
buildCompressedHistory(...)
它会基于 DB history 和 compression snapshot 构建适合本次 run 的历史:
context_estimate 估算固定上下文开销:
服务端发起:
bridge.chat(sessionId, bridgeInput, bridgeHistory, fullInstructions, profile, {
storage_message,
model,
provider,
})
返回:
{
ok: true,
run_id,
session_id,
status
}
随后 Node 轮询:
for await (const chunk of bridge.streamOutput(run_id)) {
applyBridgeChunkAsync(...)
}
streamOutput() 实际通过 get_output action,携带 cursor 和 event_cursor
增量读取文本和事件。
AgentBridgeManager 启动 hermes_bridge.py broker 子进程:
python hermes_bridge.py --endpoint <endpoint> --agent-root <root> --hermes-home <home>
默认 endpoint:
| 平台 | 默认 |
|---|---|
| Windows | tcp://127.0.0.1:18765 |
| macOS/Linux | ipc:///tmp/hermes-agent-bridge.sock |
broker 会再按 profile 路由到 worker。worker 里维护实际 AIAgent 会话池。
Node 和 Python 使用本地 socket 的单行 JSON 协议。
示例请求:
{"action":"chat","session_id":"abc","message":"hello","profile":"default"}
示例响应:
{"ok":true,"run_id":"...","session_id":"abc","status":"running"}
AgentBridgeClient 每次请求新建 socket,发送一行 JSON,读取一行 JSON。
请求默认串行化,默认 timeout 是 120 秒,connect 失败有短重试窗口。
| action | 说明 |
|---|---|
chat |
启动一轮 agent conversation。 |
get_output |
根据 cursor/event_cursor 拉取增量输出。 |
context_estimate |
估算 system prompt/tool 固定上下文 token。 |
interrupt |
中断 session 当前 run。 |
steer |
给正在运行的 session 追加 steering instruction。 |
command |
执行 plan/goal/subgoal 等 bridge command。 |
approval_respond |
响应工具审批。 |
clarify_respond |
响应澄清请求。 |
compression_respond |
Node 完成本地压缩后把压缩结果交还给 bridge。 |
goal_evaluate / goal_pause |
goal continuation 的状态评估和暂停。 |
status |
查询 bridge session 状态。 |
destroy |
销毁指定 bridge session。 |
destroy_all |
销毁全部 bridge session,主要用于进程关闭或维护。 |
mcp_* |
MCP 相关维护动作,例如 reload。 |
Bridge 输出 chunk 里有两类数据:
delta:聚合文本增量。events:有序事件列表。如果 events 中出现 stream.delta,Node 以事件顺序处理文本,避免再处理
聚合 chunk.delta,否则会重复输出。
| Bridge event | /chat-run event |
UI 结果 |
|---|---|---|
stream.delta / chunk delta |
message.delta |
assistant 文本增量。 |
reasoning.delta |
reasoning.delta |
reasoning 增量。 |
thinking.delta |
thinking.delta |
thinking/reasoning 增量。 |
reasoning.available |
reasoning.available |
标记 reasoning 可用。 |
tool.started |
tool.started |
显示工具开始行,先 flush pending assistant 文本。 |
tool.completed |
tool.completed |
显示工具结果和耗时/error。 |
subagent.* |
subagent.* |
显示子代理状态、工具、进度和总结。 |
status |
agent.event |
显示 agent 状态事件。 |
approval.requested |
approval.requested |
显示审批条。 |
approval.resolved |
approval.resolved |
清理审批条。 |
clarify.requested |
clarify.requested |
显示澄清输入。 |
clarify.resolved |
clarify.resolved |
清理澄清输入。 |
bridge.compression.requested |
compression.started |
UI 显示压缩中,Node 执行本地压缩。 |
bridge.compression.completed |
compression.completed |
UI 显示压缩结果,更新 context tokens。 |
| terminal chunk done | run.completed / run.failed |
结束 run,更新 usage,衔接队列。 |
Bridge run 中不会每个字符都立即落库。Node 会维护 pending assistant/tool 状态:
bridgePendingAssistantContent。bridgePendingReasoningContent。flushBridgePendingToDb()。recordBridgeToolStarted() 和
recordBridgeToolCompleted() 形成 DB 可恢复的 assistant/tool 结构。这样可以保证:
当 bridge chunk done=true:
updateSessionStats(sessionId)。calcAndUpdateUsage() 计算 input/output tokens。refreshFinalContextUsage() 计算 snapshot-aware context tokens。updateUsage() 写 usage store。bridgeTerminalError():
errorrun.completed 或 run.failed。前端收到 terminal event 后:
serverWorking。queue_remaining > 0,保持 handler,不提前清理。同一个 session 同时只允许一个 active run。
队列进入点:
run,发现 state.isWorking=true。/queue <message> command。/plan 或 /goal 生成 kickoff prompt,但当前 session 正在运行。队列项类型:
interface QueuedRun {
queue_id: string
input: string | ContentBlock[]
displayInput?: string | ContentBlock[] | null
displayRole?: "user" | "command"
storageMessage?: string
model?: string
provider?: string
model_groups?: Array<{ provider: string; models: string[] }>
instructions?: string
profile: string
source?: "cli" | "api_server"
originSocketId?: string
goalContinuation?: boolean
}
队列事件:
run.queued:队列长度变化。queued_messages:UI 可见队列消息。dequeued_queue_id:某条队列开始执行,前端移除 queued 展示。取消队列:
socket.emit("cancel_queued_run", { session_id, queue_id })
前端 stop 按钮最终调用:
socket.emit("abort", { session_id })
服务端 handleAbort():
abort.completed。state.isAborting=true。abort.started。bridge.interrupt(sessionId, "Aborted by user", profile)bridge.goalPause(..., "user-interrupted")前端收到:
abort.started:显示中断中。abort.completed:清理中断状态;如果有队列继续保持 streaming handler。Bridge 里有两类审批来源:
tools.approval 按 session key 路由。Python 侧生成:
{
"event": "approval.requested",
"approval_id": "...",
"command": "...",
"description": "...",
"choices": ["once", "session", "always", "deny"],
"allow_permanent": true,
"timeout_ms": 60000
}
Node 映射到 /chat-run:
approval.requested
前端:
chat.ts store 写入 pendingApprovals。ChatPanel.vue 显示审批 UI。用户选择后调用:
respondToolApproval(sessionId, approvalId, choice)
再发送:
socket.emit("approval.respond", {
session_id,
approval_id,
choice, // once | session | always | deny
})
Python bridge 会:
approval.resolved。command_allowlist 的进程内缓存,确保“始终允许”的持久配置能被后续工具判断读到。澄清请求和审批类似,但用于 agent 主动问用户问题。
Bridge event:
{
"event": "clarify.requested",
"clarify_id": "...",
"question": "...",
"choices": ["..."],
"timeout_ms": 60000
}
前端显示澄清 UI,并发送:
socket.emit("clarify.respond", {
session_id,
clarify_id,
response
})
服务端转发到:
bridge.clarifyRespond(clarifyId, response)
然后通过 clarify.resolved 清理 UI 状态。
slash command 不是独立 socket 事件。它们作为普通 run.input 发给 /chat-run,
后端在 source=cli 时由 session-command.ts 解析。
当前支持:
| 命令 | 说明 |
|---|---|
/usage |
计算并显示当前 session usage。 |
/status |
显示 session/bridge/profile/model/queue/run 状态。 |
/abort |
请求中断当前 run。 |
/queue <message> |
当前 run 活跃时追加一条队列消息。 |
/plan ... |
调用 bridge plan command,可能生成 kickoff prompt 并立即/排队运行。 |
/goal ... |
设置、查询、暂停、恢复、清理 goal。 |
/subgoal ... |
子目标命令。 |
/clear |
清理当前显示状态,不删 DB 历史。 |
/clear --history |
删除当前 session DB messages,要求 session idle。 |
/title <title> |
重命名 session。 |
/compress |
session idle 时手动触发上下文压缩。 |
/steer <instruction> |
对正在运行的 bridge run 发送 steering instruction。 |
/destroy |
销毁 bridge agent,清空运行态和队列。 |
/reload-mcp [server] |
session idle 时重载 MCP。 |
未知 command 会返回 session.command error,不进入 agent run。
压缩有三种触发方式:
buildCompressedHistory() 根据上下文窗口自动判断。bridge.compression.requested。/compress。压缩结果会写 compression snapshot,并在后续 context 构建时使用 snapshot-aware history,避免重复发送完整长历史。
事件:
compression.startedcompression.completed前端会显示临时压缩状态,并更新 session contextTokens。
多 tab 使用同一个 session:{id} room。
关键机制:
run.peer_user_message。session_id。session_id 分发到对应 handler。resume。resume 返回:
isWorkingisAbortingevents因此页面刷新、切换 session、断线重连后可以恢复:
Socket.IO /chat-run 负责 active run。HTTP API 负责静态数据读写:
| 能力 | 路径/模块 |
|---|---|
| session list | controllers/hermes/sessions.ts + session-store.ts |
| session detail/page | fetchSessionMessagesPage() 对应后端分页 detail |
| delete session | 删除 Web UI DB session/messages,同时尝试删除对应 Hermes profile 历史(如果存在) |
| rename session | renameSession() |
| set session model | setSessionModel() |
| conversation monitor | 从本地 session/conversation summary 读,不驱动 active run |
Chat 运行过程中落库由 Socket.IO run handler 做;页面历史和列表刷新使用 HTTP API。
Group Chat 是聊天核心链路的一部分,使用独立 namespace /group-chat,但 agent
执行、context token、压缩、tool/reasoning 展示仍会复用 Agent Bridge 和共享
context-compressor。
主链路:
GroupChatPanel / GroupChatInput
-> group-chat Pinia store
-> packages/client/src/api/hermes/group-chat.ts
-> Socket.IO namespace /group-chat
-> GroupChatServer
-> AgentClients
-> AgentBridgeClient
-> hermes_bridge.py broker
-> profile worker
-> AIAgent / Hermes Agent tools
核心行为:
GroupChatServer 管理 room、member、typing、message、agent runtime 和 room
runtime state。/group-chat socket 加入 room;agent 也作为 socket client 加入
同一个 namespace,但使用 source=agent 和 GROUP_CHAT_AGENT_SOCKET_SECRET。AgentClients 根据 mention routing 选择目标 agent,构造 group history 和
instructions,然后通过 AgentBridgeClient 调用对应 profile 的 bridge session。triggerTokens、maxHistoryTokens、tailMessageCount。ContextEngine 和共享 context-compressor 实现;压缩进度会
以 room/agent 维度同步到前端。排查 group-chat 时优先看:
/group-chat socket handshake auth、authUserId、source=agent。GroupChatServer.onConnection()、room join/message/approval/interrupt handlers。AgentClients 的 mention routing、bridge context cache、tool/reasoning 映射。ContextEngine 和 room compression 配置。gc_* DB 表以及 group-chat 相关 server/client tests。启动时:
ChatRunSocket(groupChatServer.getIO()) 并 init()。Bridge 启动失败不会阻止 Web UI server 启动,但普通 Chat run 会在调用 bridge 时失败。
关闭时:
ChatRunSocket.close() abort active response stream/清理内存态。AgentBridgeManager.stop() 默认关闭 Python broker,包括 restart 使用的
SIGUSR2。如果需要保留 broker 和正在运行的 bridge session,可显式设置
HERMES_AGENT_BRIDGE_STOP_ON_SHUTDOWN=0。| 变量 | 说明 |
|---|---|
HERMES_AGENT_BRIDGE_ENDPOINT |
Node 到 Python bridge broker 的 endpoint。Windows 默认 TCP,macOS/Linux 默认 IPC。 |
HERMES_AGENT_BRIDGE_WORKER_TRANSPORT |
broker 到 profile worker 的 transport,支持 tcp/ipc。 |
HERMES_AGENT_BRIDGE_WORKER_PORT_BASE |
TCP worker 起始端口。 |
HERMES_AGENT_BRIDGE_TIMEOUT_MS |
Node 等待 bridge 请求响应的超时,默认 120000ms。 |
HERMES_AGENT_BRIDGE_CONNECT_RETRY_MS |
Node connect bridge 的短重试窗口,默认 5000ms。 |
HERMES_AGENT_BRIDGE_STARTUP_TIMEOUT_MS |
bridge ready 超时,默认 120000ms。 |
HERMES_AGENT_BRIDGE_STOP_ON_SHUTDOWN |
Web UI shutdown/restart 是否关闭 bridge broker,默认开启。设为 0/false/no/off 才会保留 broker。 |
HERMES_AGENT_BRIDGE_AUTO_RESTART |
broker 意外退出是否自动重启,默认开启。 |
HERMES_AGENT_BRIDGE_RESTART_DELAY_MS |
自动重启基础延迟。 |
HERMES_AGENT_BRIDGE_PYTHON |
指定 Python 解释器。 |
HERMES_AGENT_ROOT |
指定 hermes-agent 根目录。 |
HERMES_AGENT_BRIDGE_UV / UV |
指定 uv。 |
HERMES_AGENT_BRIDGE_PLATFORM |
bridge 传给 Hermes Agent 的 platform,默认 cli。 |
HERMES_BRIDGE_PROVIDER |
覆盖 bridge provider。 |
HERMES_BRIDGE_MAX_TURNS |
覆盖 bridge 最大轮数。 |
HERMES_WEB_UI_DISABLE_GATEWAY_AUTOSTART |
跳过启动时的 gateway 检查/自动启动;dashboard-only 部署可用。 |
HERMES_WEB_UI_DISABLE_SKILL_INJECTION |
跳过启动时的内置 skill 注入;外部管理 skills 时可用。默认注入只更新 Web UI 管理或完全相同的内置副本,用户修改的同名 skills 会跳过。 |
HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_TRANSPORT |
Version Preview bridge transport。 |
HERMES_WEB_UI_PREVIEW_AGENT_BRIDGE_ENDPOINT |
Version Preview bridge endpoint。 |
cli bridge;api_server handler 是保留代码,不是主链路。sessionMap 是进程内 transient 状态,server 重启后只能从 DB 恢复已落库内容,不能恢复已丢失的 Python 内存 run。workspace、profile、model/provider 都会影响 run 上下文,排查问题时不要只看 session id。/group-chat 和独立 store/service,但属于聊天核心链路,相关改动也要更新本文变更记录。常见问题和优先检查点:
| 问题 | 检查 |
|---|---|
| 发送后没有输出 | 看 server log 中 [chat-run-socket] starting CLI bridge run 和 bridge 是否 ready。 |
| Socket 认证失败 | 检查 localStorage token、/chat-run auth middleware、用户 profile 权限。 |
| profile 不对 | 看 socket query profile、payload profile、DB session.profile。已存在 session 优先使用 DB profile。 |
| 输出重复 | 看 stream.delta 和 chunk.delta 是否被重复处理,重点查 bridge-delta.ts / applyBridgeChunkAsync()。 |
| 工具消息丢失 | 查 flushBridgePendingToDb()、recordBridgeToolStarted()、recordBridgeToolCompleted()。 |
| 刷新后状态不对 | 查 resume payload、state.events、前端 resumeServerWorkingRun()。 |
| 队列不同步 | 查 run.queued、queued_messages、dequeued_queue_id。 |
| 审批一直弹 | 查 bridge approval callback、approval.respond、工具 allowlist 是否持久化且 cache 已刷新。 |
| abort 后仍运行 | 查 bridge.interrupt()、goalPause()、abort.completed 和 Python worker 状态。 |
| token/context 数不对 | 查 context_estimate、compression snapshot、refreshFinalContextUsage()。 |