Hermes WebUI URL 查询预填机制解析:`?q=` 参数引导、焦点恢复与 5884 回归治理
Hermes WebUI URL 查询预填机制解析?q参数引导、焦点恢复与 #5884 回归治理【免费下载链接】hermes-webuiHermes WebUI: The best way to use Hermes Agent from the web or from your phone!项目地址: https://gitcode.com/GitHub_Trending/he/hermes-webui导读本指南以 Hermes WebUI 仓库中的回归复现记录 tests/fixtures/webui-PR-TARGET-5884-REPRO.md 为核心深入剖析 WebUI 的“URL 查询参数预填composer prefill”机制当用户通过https://your-hermes-host/?qhelloworld这类带查询参数的链接打开页面时前端如何在启动完成后把参数文本填入输入框#msg、正确恢复焦点并最终由回归测试锁定这一行为。读完本文你将掌握 Hermes WebUI 预填参数协议q/prompt/send/session/profile等、启动时预填的完整执行链路、URL 参数消费与清理规则以及仓库如何用 Node 驱动的回归测试守护该功能。1. 问题起点issue #5884 复现记录仓库中的 tests/fixtures/webui-PR-TARGET-5884-REPRO.md 是一份极简的“问题复现reproduction”素材记录了从外部 issue 归档而来的一段用户可感知缺陷1. Open https://your-hermes-host/?qhelloworld 2. Wait for the page to finish booting 3. Press Enter Observed on current origin/master d7c3c2b7: the composer is prefilled, but the prompt is not submitted because #msg never took focus.它描述的现象非常具体通过?qhelloworld打开页面后输入框内容确实被预填了但焦点focus没有落在#msg输入框上导致用户直接按回车时提示词不会提交。换句话说预填只完成了“填值”没有完成“聚焦”而聚焦恰恰是键盘提交的前提。这份 fixture 的价值在于它是后续回归测试的“锚点”。在 tests/test_4961_url_query_prefill.py 中测试代码会直接读取该 fixture并断言其中确实存在?qhelloworld查询串确保复现素材与测试目标一一对应def _recorded_repro_query() - str: repro REPRO_PATH.read_text(encodingutf-8) match re.search(r\?qhello\world, repro) assert match, expected recorded query in repo fixture return match.group(0)由此“URL 查询参数预填 启动聚焦”成为被显式测试契约锁定的前端行为而非一次性的手工修补。2. URL 预填参数协议前端如何理解查询串要理解 #5884先要弄清 WebUI 从 URL 查询参数中能读取哪些“意图”。相关解析逻辑集中在 static/sessions.js 的一组工具函数中它们全部以_前缀命名、由boot.js在启动时按需调用。2.1 会话定位_sessionIdFromLocation()该函数负责从 URL 中提取目标会话 ID来源有两个优先级从高到低路径标记路径中出现/session/sid时取标记后的第一个路径段并做decodeURIComponent解码查询参数依次读取session、session_id两个查询参数取第一个非空值。function _sessionIdFromLocation(){ if(typeof windowundefined||!window.location) return null; const marker/session/; const pathwindow.location.pathname||; const idxpath.indexOf(marker); if(idx0){ const rawpath.slice(idxmarker.length).split(/)[0]; if(raw){try{return decodeURIComponent(raw);}catch(_e){return raw;}} } try{ const qsnew URLSearchParams(window.location.search||); return qs.get(session)||qs.get(session_id)||null; }catch(_e){return null;} }因此https://host/?session_idabc123qhello与https://host/session/abc123?qhello都可以让页面在启动时定位到目标会话。2.2 预填意图_composerPrefillIntentFromLocation()这是 #5884 的核心解析函数它把查询参数翻译成一个结构化的“预填意图”对象function _composerPrefillIntentFromLocation(){ const empty{hasParams:false,hasText:false,text:,autoSend:false}; if(typeof windowundefined||!window.location) return empty; try{ const qsnew URLSearchParams(window.location.search||); const hasQqs.has(q); const hasPromptqs.has(prompt); const hasSendqs.has(send); if(!hasQ!hasPrompt!hasSend) return empty; const texthasQ?(qs.get(q)||):(hasPrompt?(qs.get(prompt)||):); return { hasParams:true, hasText:!!String(text).trim(), text, autoSend:false }; }catch(_e){return empty;} }关键语义总结如下查询参数作用取值规则q预填文本主来源与prompt同时存在时q优先prompt预填文本备选来源仅当q不存在时生效send预填意图的“存在标记”仅用于判定hasParams不触发自动发送无任何参数返回empty空意图hasParams:false, hasText:false特别注意两点实现细节hasText用String(text).trim()判定只有非空白文本才算“有内容”。这解释了 tests/test_4961_url_query_prefill.py 中?q%20%20send1两个空格被判定为hasText:false的行为。autoSend恒为false从源码结构看当前解析器从未通过查询参数开启自动发送预填的默认契约是“只填不送”#5884 修复所期望的正是“填值 聚焦”而不是“填值 自动提交”。2.3 关联参数profile与action除预填文本外查询串还承担另外两个引导职责profile配置文件切换由_profileQueryIntentFromLocation()解析要求名称匹配^[a-z0-9][a-z0-9_-]{0,63}$并通过_consumeProfileQueryParamFromLocation()在使用后从 URL 中删除。actionnew-chatPWA 新会话启动由_shouldStartFreshPwaChat(action, urlSession)判定用于 PWA 安装后的“新聊天”启动路径static/boot.js。3. 启动期执行链路从解析到聚焦预填意图解析出来后真正“落地”到输入框发生在 static/boot.js 的启动流程中。3.1 预填写入与聚焦_applyComposerPrefillOnBoot()async function _applyComposerPrefillOnBoot(prefillIntent){ if(!prefillIntent||!prefillIntent.hasText) return; const msg(typeof $function)?$(msg):document.getElementById(msg); if(!msg) return; const textString(prefillIntent.text||); msg.valuetext; if(typeof autoResizefunction) autoResize(); else if(typeof updateSendBtnfunction) updateSendBtn(); if(typeof msg.focusfunction) msg.focus(); }该函数的三步动作与 #5884 直接对应写值msg.value text把 URL 参数文本填入输入框布局同步优先调用autoResize()让输入框按内容高度自适应否则回退到updateSendBtn()同步发送按钮状态聚焦调用msg.focus()让键盘事件如回车提交直接命中输入框。在 tests/test_4961_url_query_prefill.py 中针对 fixture 记录的?qhelloworld查询测试断言聚焦与提交计数严格为{autoResize:1, updateSendBtn:0, focus:1, send:0}即“聚焦一次、绝不自动发送”——这正是对 #5884 现象的回归契约预填必须伴随焦点恢复但不得自动提交。同时测试也锁定了边界行为当预填意图为空或文本为空白时不得调用focus()也不得改写输入框现有值见 tests/test_4961_url_query_prefill.py而当输入框缺少focus方法如非标准宿主环境时仍需完成写值与autoResize做到“能聚焦就聚焦不能聚焦也要保证预填生效”tests/test_4961_url_query_prefill.py。3.2 收尾阶段_finalizeComposerPrefillOnBoot()启动流程并非只调用一次预填函数而是在多个分支路径上都以“收尾”形式调用它async function _finalizeComposerPrefillOnBoot(prefillIntent){ if(prefillIntentprefillIntent.hasParamstypeof _consumeComposerPrefillParamsFromLocationfunction){ _consumeComposerPrefillParamsFromLocation(); } await _applyComposerPrefillOnBoot(prefillIntent); }它先执行参数消费URL 清理再执行预填写入。在 tests/test_4961_url_query_prefill.py 中测试验证了两点只有当hasParams为真时才会触发一次_consumeComposerPrefillParamsFromLocation()且事件顺序为[consume, focus, focus]——参数先被清掉随后每次预填调用都会重新聚焦即便第二次调用时hasParams为假只要hasText为真文本仍会更新为“second pass”聚焦也照常发生。3.3 根页面预填与已保存会话的决策在根路径/打开时页面可能同时存在“URL 中的会话/文本”与“localStorage 中保存的上次会话”。boot 启动 IIFEstatic/boot.js通过一组守卫函数做决策function _prefillHasDraftText(prefillIntent){ return !!(prefillIntentprefillIntent.hasText); } function _rootPrefillNeedsFreshComposer(urlSession, savedLocal, prefillIntent){ return !urlSession!!savedLocal_prefillHasDraftText(prefillIntent); }_rootPrefillNeedsFreshComposer当“无 URL 会话 有本地保存会话 预填含文本”时返回true此时页面放弃恢复已保存会话改为展示全新空状态S.sessionnull; S.messages[]再执行预填。这正是为了让?qhelloworld的文本落在一个干净的新会话输入框里。测试覆盖了三种组合tests/test_4961_url_query_prefill.py本地保存会话胜出savedLocalWins:true、显式 URL 会话覆盖本地explicitSessionWins:false、空白预填被忽略blankPrefillIgnored:false。若页面配置了默认工作区且处于browse模式_maybeBindFreshDefaultWorkspaceSession()会在预填前尝试newSession(false, {awaitWorkspaceLoad: true, worktree: false})绑定一个全新的默认工作区会话一旦检测到预填含草稿文本则跳过该绑定见 static/boot.js 与对应测试 tests/test_4961_url_query_prefill.py。4. 参数消费与 URL 清理不留“脏参数”预填参数是一次性引导语义使用后应当从地址栏移除避免刷新后重复触发。这一职责由_consumeComposerPrefillParamsFromLocation()承担function _consumeComposerPrefillParamsFromLocation(){ if(typeof windowundefined||!window.location||!window.history||typeof window.history.replaceState!function) return; try{ const currentnew URL(window.location.href); const beforecurrent.searchParams.toString(); current.searchParams.delete(q); current.searchParams.delete(prompt); current.searchParams.delete(send); const aftercurrent.searchParams.toString(); if(afterbefore) return; const nextcurrent.pathname(after??${after}:)(current.hash||); window.history.replaceState(window.history.state||null,,next); }catch(_e){} }实现要点只删除q、prompt、send三个被消费的参数保留session_id、keep等其它查询参数避免破坏并行引导语义用history.replaceState原位替换地址不产生新的历史记录条目浏览器后退行为不受影响若没有参数发生变化则直接返回避免无意义的地址重写。测试用例 tests/test_4961_url_query_prefill.py 验证了完整场景对/app/?qhellopromptbackupsend1session_idtargetkeep1#frag消费后URL 应变为/app/?session_idtargetkeep1#frag且history.replaceState携带的 state 对象原样保留。类似地会话 URL 的构建由_sessionUrlForSid()完成它把会话 ID 编码进/session/sid路径同时剔除session/session_id/q/prompt/send参数并保留其余参数与 hashstatic/sessions.js最终由_setActiveSessionUrl()以pushState/replaceState写入地址栏。5. 回归测试的工程组织为何能持续守护 #5884该功能的回归测试具有鲜明的工程特点值得借鉴1. 用 Node 直接执行浏览器源码函数。由于sessions.js与boot.js中的核心函数以普通函数而非模块导出形式定义测试文件通过正则提取函数体、eval构造可调用函数再注入模拟的window/document/history全局对象运行tests/test_4961_url_query_prefill.py。这使纯前端逻辑可以在不启动浏览器的情况下获得确定性验证。2. 以 fixture 作为复现锚点。_recorded_repro_query()直接读取 tests/fixtures/webui-PR-TARGET-5884-REPRO.md把 issue 中的原始查询串注入测试实现“问题描述 → 测试输入”的强绑定。3. 环境缺失时自动跳过。测试模块声明pytestmark pytest.mark.skipif(NODE is None, ...)当运行环境没有 Node 时自动跳过避免 CI 误报tests/test_4961_url_query_prefill.py。4. 启动顺序的源码级断言。测试还直接对boot.js源码做字符串定位验证关键调用的先后顺序——例如_finalizeComposerPrefillOnBoot(prefillIntent)必须出现在checkInflightOnBoot(saved)之后、renderSessionList()之后且参数消费不能在设置加载前过早执行tests/test_4961_url_query_prefill.py。这种“源码顺序契约”能防止未来重构打乱预填时序。6. 实践总结预填行为一览结合上述源码与测试可以将 Hermes WebUI 的 URL 预填行为归纳为一张可操作清单场景URL 示例期望行为新页面预填并聚焦/?qhelloworld输入框写入hello world、自适应高度、聚焦不自动发送无内容参数/?q%20%20send1不写值、不聚焦hasText为假恢复指定会话并预填/session/abc?qhi或/?session_idabcqhi加载该会话后完成预填与聚焦带保存会话的根页面预填/?qhilocalStorage 有会话走“全新空状态 预填”路径本地会话不覆盖预填意图预填后清理地址栏任意带q的 URL使用后删除q/prompt/send保留其它参数对于 #5884 这类“填了值却无法回车提交”的问题根因与解法都清晰了预填必须同时包含写值、布局同步与焦点恢复三个动作且不得引入自动发送语义。该契约现已固化在 tests/test_4961_url_query_prefill.py 的回归测试与 static/boot.js、static/sessions.js 的实现中任何后续改动若破坏“聚焦但不自动发送”的行为都会立即被测试捕获。【免费下载链接】hermes-webuiHermes WebUI: The best way to use Hermes Agent from the web or from your phone!项目地址: https://gitcode.com/GitHub_Trending/he/hermes-webui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考