最后更新:2026-08-06 22:30 项目目录:
/opt/Project/reasonix-x交接状态:批量重复 bug 已修复(方案 A)+ 工具调用过程无显示 bug 已修复(tool_dispatch/tool_result,21:08 已生效)+ 对话不显示思维链 bug 已修复(reasoning 分支发 response.reasoning.delta,21:22 已生效)+ 编程工具页 scoped 启动 500 已修复(providerName 块外引用,22:27 已生效,CLI 实测跑通)+ reasonix 启动新增--permission-mode bypassPermissions(20:58 已生效),前三项待群聊实测确认 + 群聊头像问题仍待解决
功能接入已基本完成并验证:
- ✅ 编程工具页:Reasonix 卡片出现(老板确认)
- ✅ 编程工具页:内置终端启动正常——「全局默认设置」与「使用提供商和模型」均可启动(scoped 500 已修复 22:27,见下)
- ✅ 群聊房间:可添加 Reasonix agent,不再报
Invalid agent(老板确认) - ✅ 工作流:可添加 Reasonix 节点并运行(老板确认)
- ✅ 群聊里 Reasonix 头像显示正常(根因=浏览器缓存旧版前端,修复=入口加
?v=2cache busting,见下文「已修复」) - ✅ reasonix 升级到 v1.21.0(stream-json 新增
stream_attempt事件,已显式忽略,见下文「升级记录」) - ✅ 已解决:Reasonix-x 群聊房间进不去/浏览器卡死(根因=177k tokens 历史消息 + 群聊列表
virtualized:!1一次性渲染全部。修复=前端补丁开虚拟滚动virtualized:!0,见下文「群聊卡死排查」)
| 阶段 | 状态 | 说明 |
|---|---|---|
| 1. 可行性验证 | ✅ 完成 | reasonix 能跑任务/流式输出/多轮续聊(--continue) |
| 2. 方案设计 | ✅ 完成 | OpenAI 兼容 API,与 codex 一致(老板拍板) |
| 3. 补丁实现 | ✅ 完成 | 后端 43 处 reasonix、前端 3 文件(保持原名) |
| 4. 端到端验证 | ✅ 完成 | 面板/群聊/工作流/头像均通(头像=cache busting 修复) |
| 5. 补丁机制收尾 | ✅ 完成 | apply-patch.sh + patches/(rules.json + apply_patch.py)已实现并验证 |
- 白名单
a_I():加||I==="reasonix" - 注册数组
ErI:加{id:"reasonix",name:"Reasonix",provider:"DeepSeek",command:"reasonix",packageName:"reasonix"} - launch 配置
EH():reasonix 分支——写项目级reasonix.toml([[providers]]段:base_url/model/api_key_env=HERMES_REASONIX_KEY)+ apiKey 写~/.reasonix/.env - 启动分发
send():加agentId==="reasonix"→startReasonixExecTurn - 运行方法:
startReasonixExecTurn/handleReasonixExecLine/appendReasonixText/completeReasonixExecTurn(spawnreasonix run --dir <ws> --model <provider> --output-format stream-json --permission-mode bypassPermissions <task>,续聊--continue。⚠️ 20:58 新增固定追加--permission-mode bypassPermissions,免确认运行) - 群聊白名单
A3:加"reasonix"(否则报 400 Invalid agent) - 群聊映射
this.agent==="reasonix"?"reasonix":从兜底 codex 拎出 - 工作流:
j9():加 reasonix 分支(agent:"reasonix",codingAgentId:"reasonix")- workflow 校验:
G!=="reasonix"放行 iRI能力校验:reasonix 只要求 apiMode 在pie白名单(W=a==="reasonix"?pie.has(b):...),不要求 provider 在 profile groups(关键修复,否则 409 "target capability is unavailable")Poe()映射加 reasonix
- 其他硬编码(reasonix 掉到 claude/codex 分支的):
startCodingAgentMemoryExport、Dhe()、KQI、emitTerminalStatus显示名、die()、pQ()、workflow agent 会话判断、事件上报 agent 短名(3 处三元链)
| 文件 | 改动 |
|---|---|
CodingAgentsView-CjdONoI4.js |
Ke 卡片 + B 配置段 + _ 状态 + I/w/F/O 状态对象 + 图标映射 |
GroupChatView-BoO-c_wr.js |
下拉加 reasonix + 映射 + ba 头像映射加 reasonix:"/coding-agents/reasonix.png" |
WorkflowView-BRPAvC7B.js |
下拉加 reasonix + gi() 映射加 reasonix |
index-BhdrPxdZ.js |
保持原名(不再改名绕缓存) |
index.html |
引用指向 index-BhdrPxdZ.js |
- 头像:
coding-agents/reasonix.png(老板提供的 77x79 PNG,4062 字节) - systemd unit:加
Environment=PATH=/root/.nvm/versions/node/v23.11.1/bin:... /root/.reasonix/.env:含DEEPSEEK_API_KEY+HERMES_REASONIX_KEY(真实 key)
问题:群聊 @reasonix 回复显示不流畅,不像流式渲染;@codex 相对流畅。
已逐环查明的完整链路(代码层全部支持流式):
- reasonix 上游 (
esengine/DeepSeek-Reasonix.git, main-v2):agent.go逐 chunk emitevent.Text(每 chunk 一个),实测stream-json每 40ms 一个 delta、逐行带换行输出 → 上游原生 token 级流式。⚠️ 修正(2026-08-06 方案 A 修复后实测):本行"token 级流式"为早期查证,与下文 156 行矛盾。方案 A 定位确认 reasonix stream-json 对同一内容发text/message/result3 种事件、text事件给整段文本(非逐 token),故 156 行「整段 delta 已是可给的最细粒度」才是最终定论。本行保留作历史脉络。 - Hermes 单聊:
startReasonixExecTurn→handleReasonixExecLine逐行解析 →appendReasonixText逐 delta 发output_text.delta。 - Hermes 群聊:
replyToMentionWithChatRun→chatRunService.runAndWait→OQI(普通agent) /KQI(coding agent 专用) →JH→ye.start→startReasonixExecTurn(class I @6410311)。reasonix 输出逐行流式转发。 - 前端:
GroupChatView监听message_stream_delta,content+=delta实时渲染(依赖message_stream_start置 isStreaming=true)。
关键结论:
- codex 与 reasonix 走完全相同的 class I(
startCodexExecTurn/startReasonixExecTurn),逐行处理,代码层无流式差异。 - 群聊
coding_agent_id:reasonix走KQI→JH→ye.start专用路径,reasonix 是主执行者,理论上流式。 - 实测局限:通过 agent-bridge IPC 发的普通 chat(让 Hermes agent 把 reasonix 当工具调)得到聚合返回(284 delta 同刻),但该实测路径 ≠ 群聊专用路径。codex 同测试也聚合(分4批),说明"工具调用路径聚合"是 Hermes 通用行为,非 reasonix 特有。
老板确认具体表现:逐字出现但经常停顿(不是整段一次性跳出)。
结论(已定位,2026-08-06):流式链路是通的(文字确实逐 token 出),停顿点是 reasonix 工具调用边界。reasonix 的 text 事件逐 chunk 输出,但进入工具调用(ToolDispatch)时暂停输出 text,直到工具执行完才继续。任务里 reasonix 频繁调工具(写代码/跑命令),所以呈现"逐字→停顿(工具执行)→逐字→停顿"的节奏。codex 工具调用节奏不同(更少/更快),视觉上相对流畅。
这是 reasonix 上游的设计行为(工具调用期间本就不该输出 text),不是 Hermes 的 bug。若需改善视觉,可在 Hermes 侧给工具调用停顿加"加载中"提示(待老板拍板是否做)。
验证方法(未跑,待用):用 reasonix 跑含工具调用的任务,实时抓 text/tool 事件时间戳,确认 text 在 tool 事件期间暂停。
查证代码位置速查:
- reasonix 上游:
internal/agent/agent.go(ChunkText→event.Text)、internal/cli/run_output.go(stream-json 逐事件) - Hermes 群聊编码引擎:
class I@dist/server/index.js 6410311(含 startReasonixExecTurn/startCodexExecTurn) - 群聊分发:
KQI@8415033(coding_agent 专用)、JH@7670265(引擎启动) - bridge 聚合:
agent-bridge/python/bridge_pool.py_run_chatstream_callback(普通 chat 路径)
现象:群聊里 Reasonix 头像显示旧图,不是提供的 reasonix.png。
服务端/前端逻辑 100% 正确(已逐环验证):
- 后端
bB类this.agent=e.agent||"hermes"→ 存 "reasonix" - 前端
ba映射含reasonix:"/coding-agents/reasonix.png"✅ - 前端
Ia(f)=ea(f?.avatar)||Si(f?.agent),Si查 ba → reasonix.png ✅ - 服务端 GroupChatView 含 reasonix.png ✅
- index.html → index-BhdrPxdZ.js → GroupChatView ✅
- reasonix.png HTTP 200 ✅
- 排除 Service Worker(notification-sw.js 不缓存资源)
根因(确定):浏览器缓存了旧版未改名的前端文件。前端资产用一年 immutable 缓存,改名文件走新名,但入口 index-BhdrPxdZ.js 未改名 → 浏览器命中旧缓存,加载旧版 GroupChatView(无 reasonix 头像映射)。
修复(cache busting):给入口 JS 引用加版本查询串,强制浏览器加载全部新资产:
<script ... src="/assets/js/index-BhdrPxdZ.js?v=2" ...>对 dist/client/index.html 追加 ?v=2,强制所有 index-BhdrPxdZ.js 依赖的 chunk 重新拉取。已确认 index.html 当前为 ?v=2,老板确认头像显示正常。
踩坑教训:新代理接入后头像不更新,先验证全链路逻辑正确,再怀疑/处理缓存(memory 明确记录此反模式)。改名文件要同步改入口引用,否则浏览器缓存旧版。
群聊里 reasonix 回复时,同一段内容会重复出现 3 遍,且思考链路没有逐字流式渲染(一块一块蹦出来)。
reasonix 的 --output-format stream-json 对同一内容会发 3 种事件(实测逐行 NDJSON):
{"kind":"text","text":"Hi!"} // 第1遍:增量文本(最接近流式)
{"kind":"message","text":"Hi!"} // 第2遍:完整消息(与 text 同内容)
{"type":"result","result":"Hi!"} // 第3遍:最终结果(与上同内容)原 handleReasonixExecLine 把 text / message / result 三种事件都当作独立 delta 追加(appendReasonixText -> 发 response.output_text.delta),前端按 delta 累加 → 同一文本被发 3 次 → 群聊重复 3 遍。
对照 codex 为何没问题:codex 走 item.started / item.completed / response_item 结构化事件 + appendCodexFinalText(只发一次最终文本),有状态机去重;reasonix 抄了简化版,没区分"增量"和"终态"。
修改文件:/root/.nvm/versions/node/v23.11.1/lib/node_modules/hermes-web-ui/dist/server/index.js
(已备份为 index.js.reasonix-rep.bak,node --check 语法验证通过,进程已重启生效)
handleReasonixExecLine 改动逻辑:
| 事件 | 改前 | 改后 |
|---|---|---|
text |
appendReasonixText(第1次) |
✅ 保留:appendReasonixText(增量) |
message |
appendReasonixText(第2次,重复) |
⛔ 改为 return,忽略(内容已被 text 覆盖) |
result |
appendReasonixText(第3次,重复)+ 完成 |
⛔ 改为:仅当 !e.printTextStarted(text 从未来过)时兜底补发 n.result,再完成 turn;若 text 已发过则不再补文本 |
关键状态:appendReasonixText 第一次调用会把 e.printTextStarted 置 true。所以:
- 正常情况:
text来过 →printTextStarted=true→result跳过补文本,只完成回合 → 不重复 - 兜底情况:
text从没来 →printTextStarted=false→result补发一次完整文本 → 不丢内容
补丁后 handleReasonixExecLine 代码体(实测):
handleReasonixExecLine(e,l){let t=l.trim();if(!t)return;let n;try{n=JSON.parse(t)}catch{Y.debug({...},"[coding-agent-run] ignored non-json Reasonix line");return}
let a=String(n.kind||n.type||"").trim();
if(a==="text"){let c=String(n.text||"");if(c)this.appendReasonixText(e,c);return}
if(a==="message")return; // ← 去重:忽略 message
if(a==="usage"){e.codexPendingUsage=n.usage;return}
if(a==="result"){
if(e.printCompleted)return;
let c=String(n.result||"");
if(c&&!e.printTextStarted)this.appendReasonixText(e,c); // ← 仅 text 从未发过时兜底
this.completeReasonixExecTurn(e,e.codexPendingUsage)
}
if(a==="turn.failed"||a==="error")this.failCodexExecTurn(e,...)}本次修复消除了重复,但未解决"逐字流式"。原因:reasonix 的 stream-json 协议本身只给 message 级整段文本({"kind":"text","text":"整段"}),没有 token 级增量事件。要做到真正逐字流式,需 reasonix 上游支持 token 级 delta(如 text_delta)或后端改走 SSE/原始 token 流。在当前协议下,整段 delta 已是它能给的最细粒度。
- 补丁写入:19:21:52
- 服务进程重启:19:23:23(重启后已加载补丁代码)
- 老板确认重启成功 ✅,待群里实测验证重复是否消除
群聊 @reasonix 期间,reasonix 调用工具(写代码/跑命令)时,前端看不到工具执行过程(没有工具调用卡片/状态),只有最终大段文字跳出。对照 codex 能显示工具调用卡片。
reasonix 的 --output-format stream-json 在工具调用时会发出独立的 tool_dispatch(开始)和 tool_result(结束)事件:
{"kind":"tool_dispatch","tool":{"id":"...","call_id":"...","name":"bash","args":"...","partial":false}}
{"kind":"tool_result","tool":{"id":"...","call_id":"...","name":"bash","output":"..."}}(已从 reasonix 上游二进制确认:case 'tool_dispatch': if(e.tool)renderToolDispatch(e.tool)、case 'tool_result': ...,tool 对象含 id/call_id/name/args/output/partial 字段)
原 handleReasonixExecLine 只处理 text/message/usage/result/turn.failed,遇到 tool_dispatch/tool_result 直接忽略(fall through 到结尾 return),导致工具调用过程在前端完全不显示。
修改文件:/root/.nvm/versions/node/v23.11.1/lib/node_modules/hermes-web-ui/dist/server/index.js(21:08 修改,8521943 字节,语法 node --check 通过,服务 21:08:26 重启已加载)
handleReasonixExecLine 新增两个分支(复用 Hermes 已有 codex 工具渲染机制 e.codexToolBlocks Map + handleClaudePrintResponseEvent 发标准 output_item 事件):
| 事件 | 处理 |
|---|---|
tool_dispatch |
跳过 partial(增量预览帧);解析 id/call_id/name/args,存入 codexToolBlocks,发 response.output_item.added(item.type:"function_call")→ 前端显示工具调用卡片 |
tool_result |
解析 id/call_id/output,发 response.output_item.done(item.type:"function_call_output")→ 前端回填工具执行结果 |
同时把原 text/message 合并分支拆开:text 只取 n.text,message 单独 return(保持方案 A 去重逻辑,忽略 message)。
- 文件修改:21:08:03
- 服务进程重启:21:08:26(已加载)
- 前端 GroupChatView 本就能渲染
tool_calls(toolStatus:"running"等,Hermes 通用机制),reasonix 复用同一通道,无需改前端 ✅
群聊/对话里 @reasonix 回复时,reasonix 的思维链(thinking/reasoning)过程不显示,只看到最终答案。对照 codex 能展开显示思维链折叠块。
reasonix 的 --output-format stream-json 在模型输出思维链时会发独立的 reasoning 事件(已从上游二进制确认:case 'reasoning': appendReasoning(e.reasoning||e.text||''),reasoning 事件含 e.reasoning/e.text 字段)。原 handleReasonixExecLine 只处理 text/message/usage/result/tool_dispatch/tool_result/turn.failed,遇到 reasoning 事件直接忽略,导致思维链在前端完全不显示。
修改文件:/root/.nvm/versions/node/v23.11.1/lib/node_modules/hermes-web-ui/dist/server/index.js(21:22 修改,8522034 字节,语法 node --check 通过,服务 21:22:39 重启已加载)
handleReasonixExecLine 新增一个分支(复用 Hermes 已有 codex 思维链机制):
| 事件 | 处理 |
|---|---|
reasoning |
取 n.text,调 this.appendCodexReasoning(e,rc) → 发 response.reasoning.delta 流式事件 → 前端思维链折叠块逐字显示 |
appendCodexReasoning 定义(codex 侧原有):
appendCodexReasoning(e,l){l&&this.handleClaudePrintResponseEvent(e,{type:"response.reasoning.delta",data:{type:"response.reasoning.delta",item_id:e.printMessageId,output_index:0,delta:l}})}reasonix 复用同一方法,与 codex 思维链行为完全一致。
- 文件修改:21:22:22
- 服务进程重启:21:22:39(已加载)
- 前端 GroupChatView 本就有思维链渲染(
reasoning/reasoning_content字段 +reasoning_delta流式事件,Hermes 通用机制),reasonix 复用同一通道,无需改前端 ✅
web 控制台编程工具页启动 reasonix 内置终端:
- 选「使用提供商和模型」→ 提示
API Error 500: Error: providerName is not defined - 选「全局默认设置」→ 成功进入 reasonix CLI 界面
EH()(launch 配置函数)reasonix 分支块内定义 let providerName="hermes-"+n 并构造 h=["--model",providerName,"--output-format","stream-json",...];但 22:05 某次改动把函数尾部 QH 调用从 args:h 改成了块外三元表达式:
args:l.id==="reasonix"?["--model",providerName,...]:h在 reasonix 分支块外引用块内 let 变量 → ReferenceError: providerName is not defined → HTTP 500。
触发条件:
- scoped(使用提供商和模型):走到
if(l.id==="reasonix")分支求值三元 → 抛错 - global(全局默认设置):
if(t==="global")提前 return,不经过 QH 三元 → 正常
该手误自接入 reasonix 时已存在(19:21 备份中 providerName 即未定义),之前群聊均走 global 路径未暴露;22:05 改动首次把它引入必经路径。
QH 调用改回 args:h(块内 h 已含完整 --model 参数):
let N=QH({workspaceDir:Z,env:u,command:l.command,args:h}),V=await Sle({rootDir:m,workspaceDir:Z,env:u,command:l.command,args:h})- 文件修改:22:26:39(8522184 字节,
node --check通过) - 服务重启:22:27:07(已加载修复代码)
providerName出现 5→4 处(块外引用归零)
.env实为 174 字节普通文件,含DEEPSEEK_API_KEY/FEISHU_BOT_APP_SECRET/HERMES_REASONIX_KEY三键——印证核对记录二的「沙箱外可读真实 .env」结论- 实测 reasonix CLI 接受 scoped 路径的
--model 'hermes-custom:火山'(含冒号中文 provider 名):- 在 reasonix-x workspace(有
reasonix.toml定义该 provider)完整跑通:turn_started → text → message → usage → result success,模型ark-code-latest,正常回复 ✅ - 在无
reasonix.toml的目录报unknown model "hermes-custom:火山" (configured: deepseek)——属预期(workspace 级 provider 需 workspace 配置,非 bug)
- 在 reasonix-x workspace(有
- 结论:scoped 路径 500 已消除,内置终端「使用提供商和模型」应可正常启动
核对方式:读取 docs/DEVELOPMENT.md 与 docs/PROGRESS.md,对照生产安装的 dist/server/index.js、前端 3 文件、coding-agents/reasonix.png、reasonix.toml、systemd unit。
核对结论:文档与代码基本一致,发现 1 处未记录的新改动 + 1 处环境认知澄清:
| 项目 | 核对结果 |
|---|---|
| 后端 reasonix 出现处 | 生产 index.js 共 43 处 reasonix,与文档一致 ✅ |
handleReasonixExecLine 方案 A 去重 |
与文档描述的补丁代码体完全一致(text 保留 / message 忽略 / result 兜底补发 + completeReasonixExecTurn)✅ |
appendReasonixText / completeReasonixExecTurn |
与文档一致(complete 复用 completeClaudePrintTurn)✅ |
| 前端 GroupChatView | 含 reasonix:"/coding-agents/reasonix.png" 头像映射 + reasonix 选项,共 2 处 ✅ |
| 前端 CodingAgentsView | 含 reasonix 卡片($e 图标映射 / Ke 数组 / I/w/F/O 状态 / 配置段)✅ |
| 前端 WorkflowView | 含 reasonix 选项 ✅ |
coding-agents/reasonix.png |
存在,4062 字节(与文档 77x79 PNG 一致)✅ |
index.html |
引用 index-BhdrPxdZ.js(保持原名)✅ |
| systemd unit | Environment=PATH 含 nvm bin,与文档一致 ✅ |
workspace reasonix.toml |
default_model="hermes-custom:火山" + [[providers]](base_url ark / model / api_key_env=HERMES_REASONIX_KEY),与文档一致 ✅ |
startReasonixExecTurn 固定追加 --permission-mode bypassPermissions
- 生产
index.js(20:58 修改,服务 20:59 重启已加载)reasonix 启动命令为:reasonix run [--dir <ws>] ... --output-format stream-json [--effort r] --permission-mode bypassPermissions <task> - 缘由:reasonix
--permission-mode默认 ask,群聊/工作流场景会卡权限确认;bypassPermissions免确认运行(reasonix 合法取值之一,reasonix --help确认)。 - 已同步更新 DEVELOPMENT.md 5.5 与本文档第 5 点。
核对方式:对照 19:21 备份 index.js.reasonix-rep.bak 与生产 index.js(21:08),定位 handleReasonixExecLine 方法体差异(旧 678 字节 → 新 1611 字节,+933)。
核对结论:新增 tool_dispatch / tool_result 两个事件处理分支,修复 reasonix 工具调用过程无显示:
| 项目 | 核对结果 |
|---|---|
新增 tool_dispatch |
跳过 partial,解析 id/call_id/name/args → codexToolBlocks + 发 response.output_item.added(function_call) ✅ |
新增 tool_result |
解析 id/call_id/output → 发 response.output_item.done(function_call_output) ✅ |
| reasonix 上游事件真实性 | 二进制确认有 tool_dispatch/tool_result(case 'tool_dispatch': renderToolDispatch),tool 对象含 id/call_id/name/args/output/partial,与后端解析字段匹配 ✅ |
| 前端渲染 | GroupChatView 内置 tool_calls 渲染(toolStatus:"running" 等),reasonix 复用 output_item 通道,无需改前端 ✅ |
| 语法与生效 | node --check 通过;index.js 21:08:03 修改、服务 21:08:26 重启已加载 ✅ |
tool_progress 事件(工具执行中进度),后端 handleReasonixExecLine 未处理该事件(落入 return 忽略)。因 tool_dispatch 已带 partial 帧逻辑,tool_progress 是否为必要功能待实测确认(非阻塞)。
环境认知澄清(非问题):/root/.reasonix/.env 显示为字符设备(1:3=/dev/null)
- 原因:Reasonix 的 bwrap 沙箱把
/dev/null只读绑定到/root/.reasonix/.env(--ro-bind /dev/null /root/.reasonix/.env),防止沙箱内子进程读取含密钥的.env。这是正常安全隔离,非环境损坏。 - 沙箱外(reasonix 主进程)能访问真实
.env;从工具 shell 内看到的.env是/dev/null。不要把它当 bug 去删/改。
核对方式:对比 19:21 备份 index.js.reasonix-rep.bak 与生产 index.js(21:22),定位 handleReasonixExecLine 新增的 reasoning 分支。
核对结论:新增 reasoning 事件处理分支,修复 reasonix 对话不显示思维链:
| 项目 | 核对结果 |
|---|---|
新增 reasoning 分支 |
`if(a==="reasoning"){let rc=String(n.text |
appendCodexReasoning |
复用 codex 思维链通道,发 response.reasoning.delta 流式事件(item_id:e.printMessageId),与 codex 完全一致 ✅ |
| reasonix 上游事件真实性 | 二进制确认 stream-json 产出 `case 'reasoning': appendReasoning(e.reasoning |
| 前端渲染 | GroupChatView 处理 reasoning/reasoning_content 字段 + reasoning_delta 流式事件(chat-F3DnDu6u.js 含 reasoning.delta),reasonix 复用同一通道,无需改前端 ✅ |
| 语法与生效 | node --check 通过;index.js 21:22:22 修改、服务 21:22:39 重启已加载 ✅ |
| 既有改动未回退 | 生产仍 43 处 reasonix;tool_dispatch/tool_result/bypassPermissions 均仍在 ✅ |
.env 绑定限制(主进程有 key,shell 内读不到),无法在本 shell 实跑抓取 reasoning 事件;但上游二进制 case 'reasoning' 分支 + 当前会话本身即 reasonix 运行产物,足以佐证链路。
- 解决 Reasonix-x 群聊房间进不去/浏览器卡死(根因=177k tokens + 群聊
virtualized:!1一次性渲染全部。修复=前端补丁patches/apply_frontend_patch.py开虚拟滚动virtualized:!0,2026-08-07 已应用并验证)
- 群聊"批量重复"已消除(方案A已打+重启)
- 群聊"工具调用过程"已显示(tool_dispatch/tool_result 已打+重启 21:08)
- 群聊/对话"思维链"已显示(reasoning 分支已打+重启 21:22)
- 编程工具页「使用提供商和模型」启动 reasonix 不再 500(已修复 22:27)
- 群聊头像已修复(cache busting ?v=2)
-
apply-patch.sh补丁脚本已实现并验证(21 条规则+2插入,verify 全 ✅) - reasonix 升级到 v1.21.0 + stream_attempt 显式忽略
- 确认 reasonix 上游
tool_progress事件是否需后端处理(当前未处理,非阻塞) - 更新
/opt/workspaces/local-docs维护文档 - 升级保护:npm 升级会覆盖 dist 所有前端改动,补丁脚本必须覆盖(当前 apply-patch.sh 只覆盖后端 index.js,前端改动需另行处理)
- 接入方式:方案A(真实 Reasonix 卡片 + 群聊 + 工作流)
- API 方式(老板拍板):像 codex 一样面板选 provider/model/apiKey,走 OpenAI 兼容 API
- 补丁维护:脚本化补丁(apply-patch.sh 一键重打),非源码 fork
- 续聊:
--continue
- minified 单行文件 + 正则转义:
b.split(/\r?\n/)在 Python 字符串转义错写成/\n?\n/→Invalid regular expression。改完立即node --check。 - 重复插入:同一方法插两次 →
Unexpected identifier。插入前先确认存在性。 - 括号闭合:方法后多加
}闭合类 →Unexpected token 'async'。 - 缓存机制(重要):
assets/js/*.js是max-age=31536000, immutable(一年缓存,内容 hash 文件名)。直接改 dist 内容不改文件名 = 浏览器永远用旧版。⚠️ 曾尝试改名-rx后缀绕缓存,酿成白屏,最终方案:保持文件名不变(见踩坑 10)。 - HOME 差异(重要):Web UI 进程 HOME=/root,reasonix 读
/root/.reasonix/.env;Hermes CLI 会话 HOME=/root/.hermes/profiles/kaifazhe/home。测试 reasonix 必须 HOME=/root,否则报missing env误判。 - 群聊 400 Invalid agent:
A3白名单漏了 reasonix。 - 工作流 409:
iRI能力校验要求 provider 在 profile groups,reasonix 需宽放(只查 apiMode)。 - 改动编译产物文件名 → 全站白屏(高危,本次已踩):Vite 模块引用是双向的——主入口
__vite__mapDeps指向依赖,依赖 chunk 也用import("./index-BhdrPxdZ.js")相对路径指回主入口。只改主入口文件名 + index.html 引用、不同步改其他 249 个 chunk 里的相对 import,浏览器请求旧文件名 → 服务端 SPA-fallback 返回 200+text/html→ strict MIME 拒绝 →Failed to load module script: MIME text/html→ 白屏。教训:不要改编译产物文件名。验证白屏根因用无头 Chrome(--headless=new --enable-logging=stderr --dump-dom)抓 console 的Failed to load module与 Network 请求序列(INIT=script能看出谁发起的旧引用)。
- 升级即失效:npm 升级覆盖 dist → 所有改动全消失。必须先跑补丁脚本再重启。
- 费用:reasonix 走 DeepSeek(同 codex 账号),模型档位注意(默认 low)
- 数据未持久化:群聊 agent 在内存(gc_room_agents 表 0 行),重启后可能丢失,需确认业务是否接受
未接入 git 前,回滚只能靠手工 .bak 备份。本轮建立 git 仓库并整理回滚基线。
| 备份 | 时间 | reasonix 数 | providerName | JrI reasonix 段 | 内容 |
|---|---|---|---|---|---|
| index.js.bak | 16:25 | 0 | 0 | 无 | 原始未接入 |
| index.js.bak2 | 20:58 | 43 | 4 | 无 | 主接入 |
| index.js.bak3 | 21:42 | 43 | 4 | 无 | 主接入+去重+工具+思维链 |
| index.js.bak4 | 22:01 | 47 | 4 | 有 | = 当前完整生产 |
| index.js.fixed | 同步 | 47 | 4 | 有 | 生产修复版(同步入库) |
bak3 与 bak4 的唯一差异 = 只有 JrI 里的 reasonix 配置文件段(150 字节),经 diff 逐字节确认(仅 1507c1507 一处)。
- scoped 500 修复(providerName 块外引用)在 bak3 里就已存在(4 处),从未丢失。
- 早期"回滚到 bak3 会丢 scoped 500 修复"的判断有误——那是用
tr ';' '\n'分行 diff 被整大块误导。实际 bak3/bak4 只差配置段。
- 建 git 仓库 + 首次提交(docs/配置/4个bak基线)
- 按"方案A"回滚到 bak3(cp index.js.bak3 → 生产)
- 发现点配置文件报 500:根因 = 回滚丢 JrI 里 reasonix 配置段 →
JrI["reasonix"]undefined → HW/d3 的.find()TypeError - 只补 JrI reasonix 配置段(保留回滚态,不复活其他),生产 md5 恢复 b1a5dbcb = bak4 = 完整修复态
- 提交 git checkpoint(bak5/bak6)+ 同步 index.js.fixed 入库
- 生产 index.js = f506dc2a(含 reasonix 全部接入 + 配置段 + scoped 终端启动修复),服务 active 23:29:03 重启生效
- 点配置文件 500 已修复
- scoped 内置终端启动 reasonix CLI 已修复(见下节)
- git 历史:
8d9f15c(初始化) →bf9742b(回滚) →68bd69a(docs+fixed)
编程工具页选「使用提供商和模型」(scoped)启动 reasonix 内置终端,只能进入 shell,无法启动 reasonix CLI:
cd '.../workspace/default/custom_火山' && '.../reasonix/launch.sh'
root@debian:~/.hermes-web-ui/.../custom_火山#
(直接退回 shell 提示符,reasonix 未启动)
EH() 里 reasonix 分支生成的 h=["--model",providerName,"--output-format","stream-json",...],被用于 launch.sh:
exec reasonix --model 'hermes-custom:火山' --output-format stream-json而 reasonix help 明确 --output-format 只在 run/-p 子命令下有效,裸交互模式(reasonix [--model])不接受该 flag → 启动即退出(退出码 2)→ 退回 shell。
全局默认配置能启动:全局分支 args:y=[](无 flag),launch.sh = reasonix(裸交互),所以正常。
EH()reasonix 分支:h构造去掉--output-format stream-json→h=["--model",providerName,...r?["--effort",r]:[]]- launch.sh 变为
exec reasonix --model X(干净,能进交互 CLI)
- launch.sh 变为
startReasonixExecTurn的run命令拼接补回--output-format stream-json:["run",...cmd,...args,"--permission-mode","bypassPermissions","--output-format","stream-json",l]
- 群聊/对话
reasonix run不受影响(flag 在 run 子命令下合法)
- 群聊/对话
- node-pty(内置终端环境)跑修复后命令
reasonix --model 'hermes-custom:火山' --permission-mode bypassPermissions→ 成功进入 reasonix CLI(◆ reasonix · ark-code-latest ... ❯提示符) node --check通过;生产 md5 b1a5dbcb → f506dc2a- 服务重启 23:29:03 生效,老板 web 控制台确认修复成功
- 内置终端(scoped 启动 reasonix CLI):✅ 修复
- 群聊/对话(
reasonix run):✅ 不受影响(补回 flag) - 全局默认配置:✅ 不受影响
- 业务表面现象("只能进 shell")≠ 根因在终端/pty。真正根因在 launch.sh 的命令参数——
--output-format stream-json对 reasonix 交互 CLI 是无效 flag。 - scoped 和 global 的启动命令差异是排查关键:全局
args=[]能启动,scoped 带 flag 不能 → 差异点即根因。 - reasonix 交互 CLI 需要真实 TTY(bubbletea TUI),但 Hermes 用 node-pty 提供,能支持;首次会弹 telemetry 确认(
允许发送匿名统计 [Y/n]),全局已确认过则正常。
- 版本:
reasonix@1.19.1 → 1.21.0(npm 全局,npm install -g reasonix@1.21.0) - 走正式发布渠道,未用自更新
新版 1.21.0 的 --output-format stream-json 新增 stream_attempt 事件:
旧版: turn_started → text → message → usage → result
新版: turn_started → stream_attempt(begin) → text → message → stream_attempt(commit) → usage → result
Hermes handleReasonixExecLine 按 kind 分发,原对未知 kind(含 stream_attempt)静默忽略。为"防患未然",显式声明忽略 stream_attempt:
if(a==="stream_attempt"){Y.debug({runId:e.id,event:"reasonix-stream-attempt",attempt:n.streamAttempt},"[coding-agent-run] ignoring reasonix stream_attempt");return}- 由"被动跳过"改为"主动声明忽略",留调试日志
reasonix-stream-attempt - 不影响 text/message/result 正常处理(它们仍在处理分支)
- 生产 md5 f506dc2a → 22a54955,重启 10:37:28 生效
- 备份:应用前
index.js.bak8(=旧生产 f506dc2a),快照index.js.test2.js(=当前生产)
reasonix run "只回复OK" --output-format stream-json跑通,result success- Hermes 补丁 verify 14 项全 ✅
- 服务 active,无需重启(reasonix 是外部二进制,Hermes 每次 spawn 调用新版本)
- reasonix 升级会改变 stream-json 输出格式(新增事件)。升级后必须重新验证 stream-json 输出,确认 Hermes 的
handleReasonixExecLine能兼容新事件。 - 未知 kind 事件被静默忽略不会报错,但可能掩盖未来协议变化。关键事件若被忽略会导致功能异常而不自知 → 建议显式声明忽略并留日志。
老板进「Reasonix-x」群聊房间(该房间用 reasonix 审核项目代码,发了很多对话+工具消息,统计 token 约 177k),一进就浏览器卡死/页面无响应。
- 不是工具事件风暴:服务端日志显示 reasonix 群聊 run 正常启动/退出(
reasonix exec exited code:1),无 delta 洪峰。reasonixtool_dispatch有if(m.partial)return保护,不会每个 delta 发 tool.started(与 codex 不同)。 - 不是 reasonix 升级导致:stream_attempt 已在后端显式忽略,不影响前端。
- 不是历史消息落盘:
gc_messages/gc_rooms数据库全空(0 条)——群聊数据在 Web UI 进程内存里,未持久化。 - 根因:前端一次性渲染全部历史消息,无虚拟滚动:
S(Ed, { ..., messages:i.value, virtualized:!1, "estimated-item-height":170, ...})virtualized:!1 = 不虚拟滚动,一次性渲染所有消息 DOM。177k tokens 的审核对话+工具卡片全部渲染 → 主线程阻塞 → 浏览器无响应。
5. 每次消息变化全量重算:Be(()=>i.value.map(...[G.id,G.content?.length,G.reasoning?.length,G.toolStatus...])) 每次都对全部消息重新计算长度,消息量大时同样卡死。
表 C:前端补丁开虚拟滚动(实际采用)。
实施:升级 hermes-web-ui 0.6.37→0.6.39(重打 reasonix 补丁,8 个锚点因 minified 重构改名全部适配)+ 新增前端补丁 patches/apply_frontend_patch.py,把群聊列表 virtualized:!1 → virtualized:!0:
// GroupChatView-C-9d0P7n.js
M(Ud, { class:"group-message-list", messages:s.value, virtualized:!0, ... })为什么有效(代码证据):VirtualMessageList 组件渲染是三元分支:
virtualized:true→ 走DynamicScroller(vue-virtual-scroller),只渲染视口内+overscan 的 item,用 spacer 撑高,DOM 数量恒定virtualized:false→mn(e.messages)遍历渲染全部消息 DOM → 177k 消息全量渲染 → 主线程阻塞
验证:前端补丁 verify ✅(virtualized:!0 生效),服务端补丁 verify ✅(14 项全绿),服务 active,reasonix v1.21.0 installed:true。
注意:
- 单聊 MessageList 仍
virtualized:!1(上游 0.6.39 全局行为,未擅改) - 前端补丁在 npm 升级后会因文件 hash 变化失效,需重新
apply_frontend_patch.py apply - 浏览器需强刷(原 GroupChatView 被缓存)
注意:升级 0.6.39 后所有 reasonix 补丁丢失,需 apply-patch.sh 重打,且新版本工具事件逻辑大改,补丁可能需更新。
- 仓库:ddonggit/reasonix-x(public,MIT)
- 描述/主题已配置;README + LICENSE 已写入
- 凭证:git credential store(
~/.git-credentials,权限600),token 已存本机,后续 push 无需再提供 - remote:
https://github.com/ddonggit/reasonix-x.git(干净 URL,无内嵌 token) - git 身份:
咚咚 <ddonggit@ddonggit.noreply.github.com> - 全部提交已推送,
git push即可同步
老板在 Reasonix-x 群聊房间跑 reasonix 任务,工具调用卡死后无法 @人、无法发言,刷新页面报 502 Bad Gateway。系统自动重启后短暂恢复,但反复崩溃(当日 16:53/16:56/16:57/16:59/17:02/17:24 共 6 次)。
hermes-web-ui v0.6.39 服务端群聊消息保存路径存在单次调用内事务嵌套缺陷:
saveMessageAndRefreshRoom → 外层 BEGIN IMMEDIATE(未提交)
└─ pruneMessages(roomId) → withImmediateTransaction → 内层 BEGIN IMMEDIATE
└─ SQLite 抛 "cannot start a transaction within a transaction"
└─ 未捕获异常 → process.exit(1) → nginx upstream 8648 拒连 → 502
pruneMessages(消息超 500 条时触发清理)内部调用withImmediateTransaction开新事务,而外层saveMessageAndRefreshRoom已在BEGIN IMMEDIATE事务中且未提交 → 嵌套 BEGIN。- 这是单次调用内的嵌套(非多连接并发),所以稳定可复现,不是偶发环境问题。
- 崩溃栈(6 次完全一致):
Error: cannot start a transaction within a transaction
at PB.withImmediateTransaction
at PB.pruneMessages
at PB.saveMessageAndRefreshRoom
at Sy.handleMessage ← socket.io 群聊消息处理
将 withImmediateTransaction 改用 SAVEPOINT:进入时检测当前连接是否已在事务中,若是则发 SAVEPOINT sp_n(SQLite 原生支持在 BEGIN IMMEDIATE 事务内嵌套 SAVEPOINT),不再发 BEGIN IMMEDIATE,消除嵌套 BEGIN。
patches/rules.json:新增 SAVEPOINT 事务修复replace规则(幂等)patches/apply_patch.py:verify增加 SAVEPOINT 检查项;apply改为逐规则幂等应用
- 数据库副本压测:精确模拟
saveMessageAndRefreshRoom+pruneMessages完整事务链,含 prune 触发(>500 条)+ 异常恢复,全部通过,0 崩溃 - 服务 17:36:46 重启加载修复版后稳定运行,0 崩溃
apply_patch.py verify全 ✅(含新增 SAVEPOINT 检查项)
- commit
348b9fa(rules.json + apply_patch.py) - 补丁规则已写入
rules.json,npm 升级后apply-patch.sh会自动重打,不会再丢
- 此前的修复只改了运行文件,没存进补丁体系(定时炸弹:升级即丢)。本次已补齐 rules.json 规则 + verify 检查 + git 提交,三重保障。
- SQLite 单连接同步执行下,崩溃未必来自并发,单次调用内的事务嵌套同样致命且更隐蔽。
群聊房间 reasonix(开发者)显示的不是定制头像,而是 hermes 默认头像。
hermes-web-ui 0.6.39 升级覆盖了前端 GroupChatView bundle,导致两个问题:
- 前端头像映射表
Aa只含 hermes/ekko/codex/claude 四个条目,缺少 reasonix → reasonix 回落显示hermes.png reasonix.png图片文件未复制到dist/client/coding-agents/目录 → 网页读取不到
此 bug 在 0.6.36 时代(8月6日)修过一次(当时仅 cache busting,见 DEVELOPMENT.md 5.7),但 0.6.39 升级把修复覆盖了,且当时只改运行文件没进补丁体系,所以升级即丢。
patches/reasonix.png:reasonix 群聊头像图片资源(随补丁入库,4062 字节)patches/apply_frontend_patch.py:新增「reasonix 群聊头像映射」补丁规则,将reasonix:"/coding-agents/reasonix.png"注入Aa映射表;apply时自动复制reasonix.png到dist/client/coding-agents/;verify/check覆盖头像映射 + 图片存在性检查- 运行时:已应用补丁 + cache-bust(
?v=avatar)绕过 immutable 缓存
reasonix.pngHTTP 200,4062 字节(真实图片,非 SPA fallback)- 前端
Aa映射表已含 reasonix 条目 index.html为no-cache,主入口 + GroupChatView 引用加?v=avatar,确保浏览器重新下载- 老板清除浏览器数据 + 刷新后确认头像恢复正常
- commit
ba2a0ae(apply_frontend_patch.py + reasonix.png) - 补丁规则已写入前端补丁脚本,npm 升级后
apply_frontend_patch.py apply会自动重打
- 0.6.36 时代的头像修复只改运行文件没进补丁体系,0.6.39 升级即丢。本次已补齐前端补丁规则 + 图片入库 + git 提交。
- 前端 immutable 缓存(
max-age=31536000)下,改内容不改文件名 = 浏览器永远用旧版。cache-bust 查询串是必要手段;用户侧需清除浏览器数据或强刷。
群聊里每个 agent(含审核家)回复时受 runtime.maxSteps 限制,单轮工具调用+思考步数到顶即被强制截断回复。此前值为 90,导致多步骤任务(如读文档结构+写文档)一轮做不完就被迫先开口,表现为"做一半被中断"。
- 配置文件:
/root/.hermes-web-ui/.ekko/config/config.json的runtime.maxSteps由 90 调为 150 - 原文件已备份为
config.json.bak-maxSteps,可随时回滚 - 该配置在 agent 启动时读取,需重启 hermes-web-ui 服务才对新会话生效;当前正在跑的对话沿用旧值直到重启
- 19:05:17 由助理执行
systemctl restart hermes-web-ui重启,进程 PID 41747 拉起 - 重启后验证:服务 active,
maxSteps=150已生效;新对话/任务自动用新配置
maxModelRetries= 3(模型出错重试)maxConsecutiveToolFailures= 6(连续工具失败上限)tools.codeExec.maxToolCalls= 50(代码执行工具次数)delegation.subtaskMaxSteps= 30(子任务步数)model.requestTimeoutMs= 300000(单次请求超时 5 分钟)
- 调大 maxSteps 让单轮能做更多事、减少中断,但每轮更久、更费 token
- 如后续觉得太慢/太费,可调回 90 或折中值
- 此配置不在 reasonix-x 补丁体系内(属 hermes-web-ui 运行时配置,非 dist 补丁),npm 升级不会覆盖该文件,无需写进 apply-patch
- 若 hermes-web-ui 重装/换机,需手动重新设置该值
- 升级 hermes-web-ui 后,web 控制台「编程工具」页只剩 Claude Code、Codex 两张卡片,Reasonix 卡片消失
- 编程工具页无法看到/操作 reasonix
- 编程工具页是独立的客户端 JS 文件
CodingAgentsView-*.js,与群聊页(GroupChatView)分开 - 该文件里 agent 列表
Ke是写死的,只有 claude-code 和 codex,没有 reasonix - 0.6.39 升级覆盖了该文件,之前(如有)的 reasonix 条目丢失
- 结构性漏补:
apply_frontend_patch.py此前只覆盖 GroupChatView(群聊虚拟滚动 + 头像映射),从未覆盖 CodingAgentsView;apply-patch.sh一键脚本也只调用后端apply_patch.py,不调用前端补丁脚本 -> 升级后前端补丁根本没被打上
- 在
CodingAgentsView-*.js补 8 处 reasonix 配置:$e图标映射加Reasonix:"/coding-agents/reasonix.png"Keagent 列表加{id:"reasonix",tool:"Reasonix",provider:"DeepSeek"}B配置对象加 reasonix 条目- 安装状态
I、删除状态O、错误状态w、加载状态F等均加 reasonix 条目
- 缓存绕过(cache-bust):CodingAgentsView 被浏览器标记
immutable(缓存一年),改文件不重新下载。把 index.html 主入口版本从?v=avatar升到?v=coding,主入口里 CodingAgentsView 的 import 加?v=coding(2 处)-> 浏览器重新下载新版主入口 + 新版 CodingAgentsView - 持久化到补丁体系:
apply_frontend_patch.py新增 8 条 CodingAgentsView 规则 + cache-bust 应用逻辑 + verify 检查apply-patch.sh一键脚本改为同时调用后端 + 前端补丁(修复结构性漏补)
- verify 自检 27 项全部通过
- 运行文件 8 处替换全部成功,
node --check语法通过 - HTTP 拉取的 CodingAgentsView 内容已包含 reasonix(Ke 数组、$e 图标、B 配置等多处)
- index.html
?v=coding+Cache-Control: no-cache生效 - 纯前端改动,无需重启服务;用户需强制刷新浏览器(Ctrl/Cmd+Shift+R)
- 一键脚本
apply-patch.sh之前只打后端补丁、不打前端补丁,这是导致本次漏补的结构性原因。升级后即便跑了一键脚本,前端补丁也没生效。现已修复:一键脚本改为后端 + 前端一起打 - 前端补丁脚本
apply_frontend_patch.py的覆盖范围必须与后端对齐:后端apply_patch.py的 rules.json 有大量 reasonix 规则全打在 server/index.js,但前端有多个独立文件(GroupChatView、CodingAgentsView 等),每个文件都要单独维护补丁规则,否则升级后只有部分页面有 reasonix
升级后:
- 新建群聊房间「agent 类型」下拉只有 Hermes / Claude Code / Codex / Ekko Agent,无 Reasonix
- 工作流新建 agent 节点「agent」下拉只有 Hermes / Claude Code / Codex,无 Reasonix
- 编程工具页(已修)与后端 reasonix 逻辑均正常
- 与 5.15 编程工具页同根因:前端多个独立 chunk 文件,agent 列表写死,升级后被覆盖,且 apply_frontend_patch.py 从未覆盖这两个文件
- 群聊下拉在
GroupChatView-*.js的Ne数组;工作流下拉在WorkflowView-*.js的_t数组,都无 reasonix
GroupChatView-*.js补 2 处:Ne下拉加{label:"Reasonix",value:"reasonix"};Veprovider 过滤映射加me.value==="reasonix"?"reasonix"分支WorkflowView-*.js补 2 处:_t下拉加{label:"Reasonix",value:"reasonix"};gi()skill key 映射加t==="reasonix"?"reasonix"分支- cache-bust:CACHE_VERSION
coding->gcwf;apply_cachebust()扩展循环处理 CodingAgentsView / GroupChatView / WorkflowView 三 chunk(WorkflowView 此前无版本参数) - 持久化:
apply_frontend_patch.py新增 4 条规则 + cache-bust 覆盖三 chunk + verify 同步检查
- 4 条锚点在真实 dist 文件唯一匹配(dry-run 通过);
check正确识别为「未补丁」;语法通过 - ✅ 已应用到运行文件:通过
nsenter -t <hermes-web-ui PID> -m进入服务进程的可写 mount namespace(当前 shell 根/只读,但服务进程 namespace 是 rw),执行python3 patches/apply_frontend_patch.py apply成功落地 4 条规则 + cache-bust。verify 全部通过 - 🔧 修复 apply_cachebust 脚本 bug:循环内写盘后未更新
js内存副本,导致后处理的 chunk(WorkflowView)覆盖先处理的(CodingAgentsView/GroupChatView)的 cache-bust。已补js = new_js(commit e68d84d) - 浏览器需强制刷新(Ctrl+Shift+R / Cmd+Shift+R)加载
?v=gcwf新资源
- 前端每个写死 agent 列表的独立 chunk(GroupChatView、CodingAgentsView、WorkflowView…)都必须单独维护补丁规则
- 升级后应跑
bash apply-patch.sh check逐一核对所有前端文件的 reasonix 条目,而不只是后端