dsh-browser-view 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,6 +61,8 @@ dsh plugin --profile web add dsh-browser-view
61
61
 
62
62
  **工具集裁剪(toolset)**:默认 `all` 注册全部工具。日常浏览/操作/人机协作为主的会话可设 `toolset: core`——只注册 25 个高频核心工具(打开/快照/查找/点击/输入/等待/断言/网络/标签/下载上传/登录等待/人工接管/审批/点选教学/诊断),`browser_record / replay / report / extract / form / profile / context / cookies` 等低频工具不注册、不占 systemPrompt 常驻 token(低频工具的录制管理界面不受影响,已有录制仍可回放,只是 agent 会话内无这些工具)。需要做回归测试或数据抓取时把配置改回 `all` 并重启 DSH 即可。
63
63
 
64
+ **GUI 配置(免改 YAML)**:DSH 侧栏「插件」→ 打开 `dsh-browser-view` 组合包详情页,描述下方即配置面板(通道 / 无头 / userDataDir / 视口 / 超时 / toolset / 审批模式 / 截图保留)。保存经平台 configEditor 写回当前 profile 的 `cordis.patch.yml` 并由 Loader 重组——启用 HMR 的 profile 立即重载生效(运行中的浏览器会话随插件重启),未启用 HMR 时重启生效;留空/选「默认」即删除该键、回落组合包默认值。环境无 configEditor 服务(如独立 MCP 模式)时面板自动降级只读。
65
+
64
66
  ## 浏览器工具
65
67
 
66
68
  | 工具 | 说明 |
package/lib/client.js CHANGED
@@ -1,2 +1,2 @@
1
- window.__ModuleLoader__.load({id:`dsh-browser-view`,factory:e=>{var t={exports:{}},n=t.exports;Object.defineProperty(n,Symbol.toStringTag,{value:`Module`});let r=e("react"),i=null;async function a(){if(i!==null)return i;try{let e=await fetch(`/__dsh-browser-view__/config`,{cache:`no-store`});if(e.ok){let t=await e.json();if(typeof t?.basePath==`string`&&t.basePath)return i=t.basePath,t.basePath}}catch{}return i=`/browser`,i}async function o(e){try{let t=await fetch(`${e}/status`,{cache:`no-store`});return t.ok?await t.json():null}catch{return null}}async function s(e){try{let t=await fetch(`${e}/history`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.history)?n.history:[]}catch{return[]}}async function c(e){try{let t=await fetch(`${e}/log`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.calls)?n.calls:[]}catch{return[]}}async function l(e){try{let t=await fetch(`${e}/runs/latest`,{cache:`no-store`});if(!t.ok)return{run:null,busy:!1};let n=await t.json();return{run:n?.run??null,busy:n?.busy===!0}}catch{return{run:null,busy:!1}}}async function u(e){try{let t=await fetch(`${e}/confirm`,{cache:`no-store`});if(!t.ok)return null;let n=await t.json();return n?.confirm??n?.pending??null}catch{return null}}async function d(e,t,n){try{return(await fetch(`${e}/confirm`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,decision:n})})).ok}catch{return!1}}async function f(e){try{let t=await fetch(`${e}/pick`,{cache:`no-store`});return t.ok?(await t.json())?.pick??null:null}catch{return null}}async function p(e){try{let t=await fetch(`${e}/login`,{cache:`no-store`});return t.ok?(await t.json())?.loginWait??null:null}catch{return null}}async function m(e,t){try{return(await fetch(`${e}/login`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t})})).ok}catch{return!1}}async function h(e,t,n,r,i,a){try{return(await fetch(`${e}/pick`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,x:n,y:r,imgW:i,imgH:a})})).ok}catch{return!1}}async function g(e,t,n){try{let r=await fetch(`${e}/runs/rerun`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:t,...n?{retryOf:n}:{}})});if(!r.ok){let e=await r.text().catch(()=>``);return{ok:!1,status:r.status,error:e}}return{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function _(e){try{let t=await fetch(`${e}/recordings`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.recordings)?n.recordings:[]}catch{return[]}}async function v(e,t){try{let n=await fetch(`${e}/recordings/detail?name=${encodeURIComponent(t)}`,{cache:`no-store`});return n.ok?await n.json():null}catch{return null}}async function y(e,t){try{return(await fetch(`${e}/recordings`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({name:t})})).ok}catch{return!1}}function b(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)([]),[s,u]=(0,r.useState)(Date.now()),[d,f]=(0,r.useState)(null),[p,m]=(0,r.useState)(!1),[h,_]=(0,r.useState)(``);return(0,r.useEffect)(()=>{let t=!0,r=async()=>{let[r,i,s]=await Promise.all([o(e),c(e),l(e)]);t&&(n(r),r?.open&&u(Date.now()),a(i.slice(0,8)),f(s.run),m(s.busy))};r();let i=setInterval(r,3e3);return()=>{t=!1,clearInterval(i)}},[e]),{status:t,ops:i,shotVersion:s,lastRun:d,agentBusy:p,rerunMsg:h,rerunLast:async()=>{let t=d;if(!t||p)return;let n=t.source.replace(/\(已从磁盘重载\)$/,``);if(!n||[`steps`,`recording`,`recent-log`].includes(n)){_(`该来源不是已保存录制,无法一键重跑`);return}_(`重跑中…`),m(!0);let r=await g(e,n,t.runId);r.ok?_(`重跑完成,报告稍后刷新`):(m(!1),_(r.error??`重跑失败`))}}}function x(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await u(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingConfirm:t,resolveConfirm:async(t,r)=>{await d(e,t,r)&&n(null)}}}function S(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await p(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{loginWait:t,confirmLoginDone:async t=>{await m(e,t)&&n(null)}}}function C(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await f(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingPick:t}}let w={primary:`#1f2937`,secondary:`#4b5563`,tertiary:`#6b7280`,border:`#d8dde3`,hoverBg:`#f1f4f7`,danger:`#dc2626`},T={primary:`#f3f4f6`,secondary:`#cbd5e1`,tertiary:`#94a3b8`,border:`#3f4a58`,hoverBg:`#2a3240`,danger:`#f87171`};function E(){let[e,t]=(0,r.useState)(()=>typeof document<`u`&&document.body?.getAttribute(`data-ds-dark-theme`)!==null);return(0,r.useEffect)(()=>{if(typeof document>`u`)return;let e=new MutationObserver(()=>t(document.body?.getAttribute(`data-ds-dark-theme`)!==null));return e.observe(document.body,{attributes:!0,attributeFilter:[`data-ds-dark-theme`]}),()=>e.disconnect()},[]),e}function D(e){let t=new Date(e);return`${(`0`+t.getHours()).slice(-2)}:${(`0`+t.getMinutes()).slice(-2)}`}function O({confirm:e,dark:t,onDecision:n}){return(0,r.createElement)(`div`,{style:{padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #ef4444`,background:t?`rgba(239, 68, 68, 0.12)`:`rgba(239, 68, 68, 0.07)`,color:t?`#fca5a5`:`#b91c1c`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 请求执行敏感操作,请审批`},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`⚠`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`,fontWeight:600}},`敏感操作待审批`)),(0,r.createElement)(`div`,{style:{marginTop:`4px`,wordBreak:`break-all`}},e.reason,e.tool?` (${e.tool})`:``),(0,r.createElement)(`div`,{style:{display:`flex`,gap:`8px`,marginTop:`6px`}},(0,r.createElement)(`button`,{onClick:()=>n(`allowed`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#16a34a`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`允许`),(0,r.createElement)(`button`,{onClick:()=>n(`denied`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#dc2626`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`拒绝`)))}function k({streamSrc:e,shotSrc:t,alt:n,title:i,style:a,onClick:o}){let[s,c]=(0,r.useState)(!0),[l,u]=(0,r.useState)(0);(0,r.useEffect)(()=>{s||(u(e=>e+1),c(!0))},[t,s]);let d=s&&e!==void 0?l>0?`${e}${e.includes(`?`)?`&`:`?`}probe=${l}`:e:t;return(0,r.createElement)(`img`,{src:d,onError:()=>c(!1),alt:n,title:i,onClick:o,style:a})}function A({status:e,ops:t,lastRun:n,agentBusy:i,rerunMsg:a,pendingConfirm:o,pendingPick:s,shotSrc:c,streamSrc:l,dark:u,palette:d,onOpenModal:f,onOpenReport:p,onConfirmDecision:m,onRerunLast:h}){return(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`68px`,width:`320px`,maxHeight:`78vh`,overflowY:`auto`,overflowX:`hidden`,display:`flex`,flexDirection:`column`,gap:`8px`,border:`1px solid ${d.border}`,borderRadius:`12px`,background:u?`#181e29`:`#ffffff`,boxShadow:`0 8px 32px rgba(0, 0, 0, 0.3)`,padding:`12px`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,padding:`4px 0`}},(0,r.createElement)(`span`,{style:{width:`8px`,height:`8px`,borderRadius:`999px`,flex:`none`,background:e?.open?`#10b981`:`#ef4444`}}),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:d.primary,fontSize:`13px`,fontWeight:600,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},`浏览器实时画面`),(0,r.createElement)(`span`,{style:{color:d.secondary,fontSize:`12px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,maxWidth:`160px`}},e===null?`未启用`:e.open?`${e.tabs??1} 标签 · ${(e.url||``).slice(0,20)}`:`未打开`)),o?(0,r.createElement)(O,{confirm:o,dark:u,onDecision:m}):null,e?.pause?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 正在等待人工操作,请在浏览器窗口中完成`},(0,r.createElement)(`span`,{style:{flex:`none`}},`⏸`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待人工操作: ${e.pause.instruction}`)):null,s?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`,cursor:`pointer`},title:`点击放大实时画面,在画面上点选目标元素`,onClick:f},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${s.instruction}(点此放大画面)`)):null,e?.open?(0,r.createElement)(k,{streamSrc:l,shotSrc:c,alt:`实时画面`,title:`点击放大查看实时大屏与访问历史`,onClick:f,style:{width:`100%`,maxHeight:`160px`,objectFit:`cover`,borderRadius:`8px`,border:`1px solid ${d.border}`,cursor:`pointer`,background:u?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,lineHeight:`1.5`,padding:`8px`,background:u?`#121620`:`#f8fafc`,borderRadius:`6px`}},`浏览器尚未启动。在对话中让 Agent 调用 browser_open 即可呈现画面。`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`3px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`}},(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,fontWeight:500,marginBottom:`2px`}},`最近操作 (${t.length} 条)`),t.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:{display:`flex`,alignItems:`center`,gap:`6px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{flex:`none`,color:e.ok?`#10b981`:`#ef4444`,fontWeight:600}},e.ok?`✓`:`✗`),(0,r.createElement)(`span`,{style:{color:d.tertiary,fontSize:`11px`,flex:`none`}},D(e.ts)),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`},title:`${e.tool}${e.error?`: ${e.error}`:``}`},`${e.tool}${e.error?`: ${e.error}`:``}`)))),n!==null&&(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,cursor:`pointer`},title:`runId=${n.runId} · 点击查看报告`,onClick:p},(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},`最近回归`),(0,r.createElement)(`span`,{style:{flex:`none`,fontWeight:600,color:n.failed===0?`#10b981`:`#ef4444`}},n.failed===0?`✓`:`✗`),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},`${n.source}${n.row===void 0?``:` 行${n.row}`} ${n.passed}/${n.total}${n.healedCount>0?` · 自愈${n.healedCount}`:``}${n.retries!==void 0&&n.retries.recovered>0?` · 重试挽回${n.retries.recovered}`:``} · ${new Date(n.ts).toLocaleTimeString(`zh-CN`,{hour12:!1})}`),a!==``&&(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},a),(0,r.createElement)(`button`,{type:`button`,title:i?`agent 忙碌,稍后可重跑`:`重跑 ${n.source}(结果进运行历史,标 retryOf)`,disabled:i,onClick:e=>{e.stopPropagation(),h()},style:{flex:`none`,height:`20px`,padding:`0 8px`,border:`1px solid ${d.border}`,borderRadius:`6px`,background:`transparent`,color:i?d.tertiary:d.secondary,fontSize:`11px`,cursor:i?`not-allowed`:`pointer`,opacity:i?.5:1}},i?`重跑(忙碌)`:`↻ 重跑`)))}function j({status:e,history:t,shotSrc:n,streamSrc:i,dark:a,palette:o,onClose:s,pendingPick:c,onPick:l}){(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&s()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[s]);let u=e=>{if(c==null||l===void 0)return;let t=e.currentTarget,n=t.getBoundingClientRect(),r=t.naturalWidth||n.width,i=t.naturalHeight||n.height;if(r<=0||i<=0)return;let a=Math.min(n.width/r,n.height/i),o=(n.width-r*a)/2,s=(n.height-i*a)/2,u=Math.min(Math.max((e.clientX-n.left-o)/a,0),r),d=Math.min(Math.max((e.clientY-n.top-s)/a,0),i);l(Math.round(u),Math.round(d),Math.round(r),Math.round(i))},d=c!=null;return(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&s()},style:{position:`fixed`,inset:0,zIndex:2147483001,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1100px, 94vw)`,maxHeight:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${o.border}`,borderRadius:`12px`,background:a?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,color:o.primary,fontSize:`14px`,fontWeight:600}},e?.open?`${e.title||``} (${e.url||``})`:`浏览器实时大屏`),(0,r.createElement)(`button`,{type:`button`,onClick:s,style:{width:`28px`,height:`28px`,border:`1px solid ${o.border}`,borderRadius:`6px`,background:`transparent`,color:o.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),d?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 10px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:a?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:a?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${c.instruction}(在下方画面上点击目标元素)`)):null,e?.open?(0,r.createElement)(k,{streamSrc:i,shotSrc:n,alt:`浏览器大图`,...d?{onClick:u}:{},style:{width:`100%`,maxHeight:`55vh`,objectFit:`contain`,borderRadius:`8px`,border:d?`2px dashed #f59e0b`:`1px solid ${o.border}`,background:a?`#10141c`:`#f8fafc`,cursor:d?`crosshair`:`default`}}):(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`13px`,textAlign:`center`,padding:`32px 0`}},`浏览器尚未打开`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`4px`,maxHeight:`22vh`,overflowY:`auto`,borderTop:`1px solid ${o.border}`,paddingTop:`8px`}},(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`12px`,fontWeight:500}},`访问历史 (${t.length} 条,点击在新标签打开)`),t.map((e,t)=>(0,r.createElement)(`a`,{key:t,href:e.url,target:`_blank`,rel:`noopener noreferrer`,style:{display:`flex`,gap:`8px`,alignItems:`center`,color:o.secondary,fontSize:`12px`,textDecoration:`none`,borderRadius:`6px`,padding:`4px 6px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{color:o.tertiary,flex:`none`}},D(e.ts)),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},e.title||e.url))))))}function M({base:e,dark:t,palette:n,onClose:i}){return(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&i()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[i]),(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&i()},style:{position:`fixed`,inset:0,zIndex:2147483002,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1000px, 94vw)`,height:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${n.border}`,borderRadius:`12px`,background:t?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:n.primary,fontSize:`14px`,fontWeight:600}},`回归测试报告 (最新运行)`),(0,r.createElement)(`a`,{href:`${e}/runs/report.html`,target:`_blank`,rel:`noopener noreferrer`,style:{color:n.secondary,fontSize:`12px`,textDecoration:`none`,padding:`4px 8px`,border:`1px solid ${n.border}`,borderRadius:`6px`}},`新标签页打开`),(0,r.createElement)(`button`,{type:`button`,onClick:i,style:{width:`28px`,height:`28px`,border:`1px solid ${n.border}`,borderRadius:`6px`,background:`transparent`,color:n.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),(0,r.createElement)(`iframe`,{src:`${e}/runs/report.html`,title:`回归测试报告`,style:{flex:1,width:`100%`,border:`1px solid ${n.border}`,borderRadius:`8px`,background:`#ffffff`}})))}function N({loginWait:e,onDone:t}){return(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`68px`,width:`260px`,padding:`10px 12px`,borderRadius:`10px`,border:`1px solid #16a34a`,background:`rgba(22, 163, 74, 0.12)`,color:`#15803d`,fontSize:`12px`,lineHeight:`1.5`,boxShadow:`0 4px 16px rgba(0, 0, 0, 0.18)`,pointerEvents:`auto`},title:`Agent 正在等待你完成登录:请在浏览器窗口登录后点击下方按钮`},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`🔐`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,fontWeight:600,wordBreak:`break-all`}},`等待人工登录`)),(0,r.createElement)(`div`,{style:{marginTop:`4px`,wordBreak:`break-all`,opacity:.9}},e.instruction),(0,r.createElement)(`button`,{onClick:t,style:{width:`100%`,marginTop:`8px`,padding:`6px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#16a34a`,color:`#fff`,fontSize:`13px`,fontWeight:600}},`我已完成登录`))}function P(){let e=E(),t=e?T:w,[n,i]=(0,r.useState)(`/browser`),[o,c]=(0,r.useState)(!1),[l,u]=(0,r.useState)(!1),[d,f]=(0,r.useState)(!1),[p,m]=(0,r.useState)([]);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&i(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{status:g,ops:_,shotVersion:v,lastRun:y,agentBusy:D,rerunMsg:O,rerunLast:k}=b(n),{pendingConfirm:P,resolveConfirm:F}=x(n),{loginWait:I,confirmLoginDone:L}=S(n),{pendingPick:R}=C(n);(0,r.useEffect)(()=>{if(!l)return;let e=!0,t=async()=>{let t=await s(n);e&&m(t)};t();let r=setInterval(t,2e3);return()=>{e=!1,clearInterval(r)}},[l,n]);let z=`${n}/screenshot?v=${v}`,B=`${n}/stream`;return(0,r.createElement)(`div`,{id:`dsh-browser-client-overlay`,style:{position:`fixed`,inset:0,pointerEvents:`none`,zIndex:2147483e3}},I&&!I.done?(0,r.createElement)(N,{loginWait:I,onDone:()=>{L(I.id)}}):null,(0,r.createElement)(`button`,{type:`button`,title:`浏览器自动化与实时画面`,onClick:()=>c(e=>!e),style:{position:`fixed`,right:`16px`,bottom:`16px`,width:`42px`,height:`42px`,borderRadius:`999px`,border:`1px solid ${t.border}`,background:e?`#1e2430`:`#ffffff`,color:t.primary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`18px`,boxShadow:`0 4px 16px rgba(0, 0, 0, 0.2)`,transition:`transform 0.15s ease, background 0.15s ease`,pointerEvents:`auto`}},`🌐`),o&&(0,r.createElement)(A,{status:g,ops:_,lastRun:y,agentBusy:D,rerunMsg:O,pendingConfirm:P,pendingPick:R,shotSrc:z,streamSrc:B,dark:e,palette:t,onOpenModal:()=>u(!0),onOpenReport:()=>f(!0),onConfirmDecision:e=>{P&&F(P.id,e)},onRerunLast:k}),l&&(0,r.createElement)(j,{status:g,history:p,shotSrc:z,streamSrc:B,dark:e,palette:t,onClose:()=>u(!1),pendingPick:R,onPick:(e,t,r,i)=>{R&&h(n,R.id,e,t,r,i)}}),d&&(0,r.createElement)(M,{base:n,dark:e,palette:t,onClose:()=>f(!1)}))}function F(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)(``),[o,s]=(0,r.useState)({});return(0,r.useEffect)(()=>{let t=!0;return n(null),a(``),_(e).then(e=>{t&&n(e)}).catch(e=>{t&&a(e instanceof Error?e.message:String(e))}),()=>{t=!1}},[e]),{list:t,error:i,expanded:o,toggleDetail:t=>{s(n=>{let r=n[t];if(r!==void 0&&r!==`loading`){let e={...n};return delete e[t],e}return v(e,t).then(e=>{s(n=>({...n,[t]:e??`failed`}))}).catch(()=>s(e=>({...e,[t]:`failed`}))),{...n,[t]:`loading`}})},remove:async t=>{await y(e,t)&&(n(e=>e===null?e:e.filter(e=>e.name!==t)),s(e=>{let n={...e};return delete n[t],n}))}}}function I(e){return{row:{display:`flex`,alignItems:`center`,gap:`8px`,minWidth:0},label:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,fontSize:`13px`,color:e.primary,cursor:`pointer`},meta:{color:e.tertiary,fontSize:`12px`,whiteSpace:`nowrap`},btn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.secondary,font:`inherit`,fontSize:`12px`,cursor:`pointer`},delBtn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.danger,font:`inherit`,fontSize:`12px`,cursor:`pointer`},itemBox:{display:`flex`,flexDirection:`column`,gap:`6px`,border:`1px solid ${e.border}`,borderRadius:`8px`,padding:`8px 10px`},stepsBox:{display:`flex`,flexDirection:`column`,gap:`2px`,borderTop:`1px solid ${e.border}`,paddingTop:`6px`},stepLine:{display:`flex`,gap:`8px`,fontSize:`13px`,lineHeight:`1.7`,color:e.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`},toolName:{color:e.primary,flex:`none`,fontWeight:500},stepArgs:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,color:e.secondary},empty:{color:e.tertiary,fontSize:`12px`,textAlign:`center`,padding:`12px 0`},root:{display:`flex`,flexDirection:`column`,gap:`8px`,maxWidth:`720px`},heading:{color:e.tertiary,fontSize:`12px`,lineHeight:`1.6`}}}function L(e){let t=E(),n=t?T:w,i=I(n),[o,s]=(0,r.useState)(`/browser`);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&s(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{list:c,error:l,expanded:u,toggleDetail:d,remove:f}=F(o);return(0,r.createElement)(`div`,{style:i.root},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`12px`,paddingBottom:`12px`,marginBottom:`6px`,borderBottom:`1px solid ${n.border}`}},(0,r.createElement)(`div`,{style:{width:`36px`,height:`36px`,borderRadius:`8px`,background:t?`rgba(99, 102, 241, 0.15)`:`rgba(99, 102, 241, 0.1)`,border:`1px solid ${t?`rgba(99, 102, 241, 0.3)`:`rgba(99, 102, 241, 0.2)`}`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`20px`,flex:`none`}},`🌐`),(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`2px`}},(0,r.createElement)(`div`,{style:{fontSize:`15px`,fontWeight:600,color:n.primary}},`浏览器自动化与录制管理`),(0,r.createElement)(`div`,{style:{fontSize:`12px`,color:n.tertiary}},`管理已保存的浏览器回归脚本,查看执行序列与历史数据`))),(0,r.createElement)(`div`,{style:i.heading},`已保存的录制(由 browser_record save 生成,供 browser_replay 一键回放):`),l?(0,r.createElement)(`div`,{style:{...i.empty,color:n.danger}},`加载失败: ${l}`):c===null?(0,r.createElement)(`div`,{style:i.empty},`加载中…`):c.length===0?(0,r.createElement)(`div`,{style:i.empty},`暂无已保存录制。在与 Agent 对话中完成一轮浏览器操作后,输入「保存录制为 xxx」即可沉淀为回归用例。`):c.map(e=>{let t=u[e.name],n=t!==void 0&&t!==`loading`;return(0,r.createElement)(`div`,{key:e.name,style:i.itemBox},(0,r.createElement)(`div`,{style:i.row},(0,r.createElement)(`span`,{style:i.label,title:`点击展开 / 折叠步骤详情`,onClick:()=>d(e.name)},(n?`▾ `:`▸ `)+e.name),(0,r.createElement)(`span`,{style:i.meta},`${e.steps} 步 · ${new Date(e.savedAt).toLocaleDateString(`zh-CN`)}`),(0,r.createElement)(`button`,{type:`button`,style:i.btn,onClick:()=>d(e.name)},n?`收起`:`步骤`),(0,r.createElement)(`button`,{type:`button`,style:i.delBtn,onClick:()=>f(e.name)},`删除`)),n&&t!==`failed`&&(0,r.createElement)(`div`,{style:i.stepsBox},t.steps.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:i.stepLine},(0,r.createElement)(`span`,{style:i.meta},`#${t+1}`),(0,r.createElement)(`span`,{style:i.toolName},e.tool),(0,r.createElement)(`span`,{style:i.stepArgs},JSON.stringify(e.args))))))}))}let R=[`浏览器`,`🌐 浏览器`],z=`data-dsh-browser-nav-icon`,B=`data-dsh-browser-nav-hidden`;function V(){if(typeof document>`u`)return;let e=document.querySelectorAll(`button[class*="navCell"]`),t=e.length>0?Array.from(e):Array.from(document.querySelectorAll(`[class*="navList"] button`));for(let e of t){let t=(e.querySelector(`[class*="navLabel"]`)?.textContent??e.textContent??``).trim();if(!R.some(e=>t===e)||e.querySelector(`[${z}]`))continue;let n=e.querySelector(`svg`);if(!n||!n.parentNode)continue;n.style.display=`none`,n.setAttribute(B,`1`);let r=document.createElement(`span`);r.setAttribute(z,`1`),r.setAttribute(`aria-hidden`,`true`),r.style.display=`inline-flex`,r.style.flex=`none`,r.innerHTML=`<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect x="1.5" y="2.75" width="13" height="10.5" rx="1.75" stroke="currentColor" stroke-width="1.1"/><path d="M1.5 5.75h13" stroke="currentColor" stroke-width="1.1"/><circle cx="3.4" cy="4.25" r="0.65" fill="currentColor"/><circle cx="5.2" cy="4.25" r="0.65" fill="currentColor"/></svg>`,n.parentNode.insertBefore(r,n.nextSibling)}}function H(){try{V()}catch{}}function U(){if(!(typeof document>`u`)){for(let e of Array.from(document.querySelectorAll(`[${z}]`)))e.remove();for(let e of Array.from(document.querySelectorAll(`svg[${B}]`)))e.style.display=``,e.removeAttribute(B)}}function W(e){if(typeof document>`u`||typeof MutationObserver>`u`)return;H();let t=null;try{t=new MutationObserver(H),t.observe(document.body,{childList:!0,subtree:!0,characterData:!0})}catch{t=null}e.effect(()=>()=>{if(t){let e=t;t=null;try{e.disconnect()}catch{}}try{U()}catch{}})}let G=[`slots`];function K(e){e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`browser`,order:40,label:`浏览器`},L)),W(e),e.slots.inject(`shell.overlay`,()=>e.slots.register({name:`shell.overlay`,id:`dsh-browser-overlay`,order:100},P))}return n.apply=K,n.inject=G,t.exports}});
1
+ window.__ModuleLoader__.load({id:`dsh-browser-view`,factory:e=>{var t={exports:{}},n=t.exports;Object.defineProperty(n,Symbol.toStringTag,{value:`Module`});let r=e("react"),i=null;async function a(){if(i!==null)return i;try{let e=await fetch(`/__dsh-browser-view__/config`,{cache:`no-store`});if(e.ok){let t=await e.json();if(typeof t?.basePath==`string`&&t.basePath)return i=t.basePath,t.basePath}}catch{}return i=`/browser`,i}async function o(e){try{let t=await fetch(`${e}/status`,{cache:`no-store`});return t.ok?await t.json():null}catch{return null}}async function s(e){try{let t=await fetch(`${e}/history`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.history)?n.history:[]}catch{return[]}}async function c(e){try{let t=await fetch(`${e}/log`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.calls)?n.calls:[]}catch{return[]}}async function l(e){try{let t=await fetch(`${e}/runs/latest`,{cache:`no-store`});if(!t.ok)return{run:null,busy:!1};let n=await t.json();return{run:n?.run??null,busy:n?.busy===!0}}catch{return{run:null,busy:!1}}}async function u(e){try{let t=await fetch(`${e}/confirm`,{cache:`no-store`});if(!t.ok)return null;let n=await t.json();return n?.confirm??n?.pending??null}catch{return null}}async function d(e,t,n){try{return(await fetch(`${e}/confirm`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,decision:n})})).ok}catch{return!1}}async function f(e){try{let t=await fetch(`${e}/pick`,{cache:`no-store`});return t.ok?(await t.json())?.pick??null:null}catch{return null}}async function p(e){try{let t=await fetch(`${e}/login`,{cache:`no-store`});return t.ok?(await t.json())?.loginWait??null:null}catch{return null}}async function m(e,t){try{return(await fetch(`${e}/login`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t})})).ok}catch{return!1}}async function h(e,t,n,r,i,a){try{return(await fetch(`${e}/pick`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,x:n,y:r,imgW:i,imgH:a})})).ok}catch{return!1}}async function g(e,t,n){try{let r=await fetch(`${e}/runs/rerun`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:t,...n?{retryOf:n}:{}})});if(!r.ok){let e=await r.text().catch(()=>``);return{ok:!1,status:r.status,error:e}}return{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function _(e){try{let t=await fetch(`${e}/recordings`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.recordings)?n.recordings:[]}catch{return[]}}async function v(e,t){try{let n=await fetch(`${e}/recordings/detail?name=${encodeURIComponent(t)}`,{cache:`no-store`});return n.ok?await n.json():null}catch{return null}}async function y(e,t){try{return(await fetch(`${e}/recordings`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({name:t})})).ok}catch{return!1}}async function b(e){try{let t=await fetch(`${e}/config`,{cache:`no-store`});if(!t.ok)return null;let n=await t.json();return typeof n?.editable==`boolean`?n:null}catch{return null}}async function x(e,t){try{let n=await fetch(`${e}/config`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({changes:t})});if(!n.ok){let e=await n.json().catch(()=>void 0);return{ok:!1,...e?.error===void 0?{error:`HTTP ${n.status}`}:{error:e.error}}}return{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}function S(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)([]),[s,u]=(0,r.useState)(Date.now()),[d,f]=(0,r.useState)(null),[p,m]=(0,r.useState)(!1),[h,_]=(0,r.useState)(``);return(0,r.useEffect)(()=>{let t=!0,r=async()=>{let[r,i,s]=await Promise.all([o(e),c(e),l(e)]);t&&(n(r),r?.open&&u(Date.now()),a(i.slice(0,8)),f(s.run),m(s.busy))};r();let i=setInterval(r,3e3);return()=>{t=!1,clearInterval(i)}},[e]),{status:t,ops:i,shotVersion:s,lastRun:d,agentBusy:p,rerunMsg:h,rerunLast:async()=>{let t=d;if(!t||p)return;let n=t.source.replace(/\(已从磁盘重载\)$/,``);if(!n||[`steps`,`recording`,`recent-log`].includes(n)){_(`该来源不是已保存录制,无法一键重跑`);return}_(`重跑中…`),m(!0);let r=await g(e,n,t.runId);r.ok?_(`重跑完成,报告稍后刷新`):(m(!1),_(r.error??`重跑失败`))}}}function C(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await u(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingConfirm:t,resolveConfirm:async(t,r)=>{await d(e,t,r)&&n(null)}}}function w(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await p(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{loginWait:t,confirmLoginDone:async t=>{await m(e,t)&&n(null)}}}function T(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await f(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingPick:t}}let E={primary:`#1f2937`,secondary:`#4b5563`,tertiary:`#6b7280`,border:`#d8dde3`,hoverBg:`#f1f4f7`,danger:`#dc2626`},D={primary:`#f3f4f6`,secondary:`#cbd5e1`,tertiary:`#94a3b8`,border:`#3f4a58`,hoverBg:`#2a3240`,danger:`#f87171`};function O(){let[e,t]=(0,r.useState)(()=>typeof document<`u`&&document.body?.getAttribute(`data-ds-dark-theme`)!==null);return(0,r.useEffect)(()=>{if(typeof document>`u`)return;let e=new MutationObserver(()=>t(document.body?.getAttribute(`data-ds-dark-theme`)!==null));return e.observe(document.body,{attributes:!0,attributeFilter:[`data-ds-dark-theme`]}),()=>e.disconnect()},[]),e}function k(e){let t=new Date(e);return`${(`0`+t.getHours()).slice(-2)}:${(`0`+t.getMinutes()).slice(-2)}`}function A({confirm:e,dark:t,onDecision:n}){return(0,r.createElement)(`div`,{style:{padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #ef4444`,background:t?`rgba(239, 68, 68, 0.12)`:`rgba(239, 68, 68, 0.07)`,color:t?`#fca5a5`:`#b91c1c`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 请求执行敏感操作,请审批`},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`⚠`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`,fontWeight:600}},`敏感操作待审批`)),(0,r.createElement)(`div`,{style:{marginTop:`4px`,wordBreak:`break-all`}},e.reason,e.tool?` (${e.tool})`:``),(0,r.createElement)(`div`,{style:{display:`flex`,gap:`8px`,marginTop:`6px`}},(0,r.createElement)(`button`,{onClick:()=>n(`allowed`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#16a34a`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`允许`),(0,r.createElement)(`button`,{onClick:()=>n(`denied`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#dc2626`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`拒绝`)))}function j({streamSrc:e,shotSrc:t,alt:n,title:i,style:a,onClick:o}){let[s,c]=(0,r.useState)(!0),[l,u]=(0,r.useState)(0);(0,r.useEffect)(()=>{s||(u(e=>e+1),c(!0))},[t,s]);let d=s&&e!==void 0?l>0?`${e}${e.includes(`?`)?`&`:`?`}probe=${l}`:e:t;return(0,r.createElement)(`img`,{src:d,onError:()=>c(!1),alt:n,title:i,onClick:o,style:a})}function M({status:e,ops:t,lastRun:n,agentBusy:i,rerunMsg:a,pendingConfirm:o,pendingPick:s,shotSrc:c,streamSrc:l,dark:u,palette:d,onOpenModal:f,onOpenReport:p,onConfirmDecision:m,onRerunLast:h}){return(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`68px`,width:`320px`,maxHeight:`78vh`,overflowY:`auto`,overflowX:`hidden`,display:`flex`,flexDirection:`column`,gap:`8px`,border:`1px solid ${d.border}`,borderRadius:`12px`,background:u?`#181e29`:`#ffffff`,boxShadow:`0 8px 32px rgba(0, 0, 0, 0.3)`,padding:`12px`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,padding:`4px 0`}},(0,r.createElement)(`span`,{style:{width:`8px`,height:`8px`,borderRadius:`999px`,flex:`none`,background:e?.open?`#10b981`:`#ef4444`}}),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:d.primary,fontSize:`13px`,fontWeight:600,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},`浏览器实时画面`),(0,r.createElement)(`span`,{style:{color:d.secondary,fontSize:`12px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,maxWidth:`160px`}},e===null?`未启用`:e.open?`${e.tabs??1} 标签 · ${(e.url||``).slice(0,20)}`:`未打开`)),o?(0,r.createElement)(A,{confirm:o,dark:u,onDecision:m}):null,e?.pause?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 正在等待人工操作,请在浏览器窗口中完成`},(0,r.createElement)(`span`,{style:{flex:`none`}},`⏸`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待人工操作: ${e.pause.instruction}`)):null,s?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`,cursor:`pointer`},title:`点击放大实时画面,在画面上点选目标元素`,onClick:f},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${s.instruction}(点此放大画面)`)):null,e?.open?(0,r.createElement)(j,{streamSrc:l,shotSrc:c,alt:`实时画面`,title:`点击放大查看实时大屏与访问历史`,onClick:f,style:{width:`100%`,maxHeight:`160px`,objectFit:`cover`,borderRadius:`8px`,border:`1px solid ${d.border}`,cursor:`pointer`,background:u?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,lineHeight:`1.5`,padding:`8px`,background:u?`#121620`:`#f8fafc`,borderRadius:`6px`}},`浏览器尚未启动。在对话中让 Agent 调用 browser_open 即可呈现画面。`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`3px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`}},(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,fontWeight:500,marginBottom:`2px`}},`最近操作 (${t.length} 条)`),t.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:{display:`flex`,alignItems:`center`,gap:`6px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{flex:`none`,color:e.ok?`#10b981`:`#ef4444`,fontWeight:600}},e.ok?`✓`:`✗`),(0,r.createElement)(`span`,{style:{color:d.tertiary,fontSize:`11px`,flex:`none`}},k(e.ts)),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`},title:`${e.tool}${e.error?`: ${e.error}`:``}`},`${e.tool}${e.error?`: ${e.error}`:``}`)))),n!==null&&(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,cursor:`pointer`},title:`runId=${n.runId} · 点击查看报告`,onClick:p},(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},`最近回归`),(0,r.createElement)(`span`,{style:{flex:`none`,fontWeight:600,color:n.failed===0?`#10b981`:`#ef4444`}},n.failed===0?`✓`:`✗`),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},`${n.source}${n.row===void 0?``:` 行${n.row}`} ${n.passed}/${n.total}${n.healedCount>0?` · 自愈${n.healedCount}`:``}${n.retries!==void 0&&n.retries.recovered>0?` · 重试挽回${n.retries.recovered}`:``} · ${new Date(n.ts).toLocaleTimeString(`zh-CN`,{hour12:!1})}`),a!==``&&(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},a),(0,r.createElement)(`button`,{type:`button`,title:i?`agent 忙碌,稍后可重跑`:`重跑 ${n.source}(结果进运行历史,标 retryOf)`,disabled:i,onClick:e=>{e.stopPropagation(),h()},style:{flex:`none`,height:`20px`,padding:`0 8px`,border:`1px solid ${d.border}`,borderRadius:`6px`,background:`transparent`,color:i?d.tertiary:d.secondary,fontSize:`11px`,cursor:i?`not-allowed`:`pointer`,opacity:i?.5:1}},i?`重跑(忙碌)`:`↻ 重跑`)))}function N({status:e,history:t,shotSrc:n,streamSrc:i,dark:a,palette:o,onClose:s,pendingPick:c,onPick:l}){(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&s()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[s]);let u=e=>{if(c==null||l===void 0)return;let t=e.currentTarget,n=t.getBoundingClientRect(),r=t.naturalWidth||n.width,i=t.naturalHeight||n.height;if(r<=0||i<=0)return;let a=Math.min(n.width/r,n.height/i),o=(n.width-r*a)/2,s=(n.height-i*a)/2,u=Math.min(Math.max((e.clientX-n.left-o)/a,0),r),d=Math.min(Math.max((e.clientY-n.top-s)/a,0),i);l(Math.round(u),Math.round(d),Math.round(r),Math.round(i))},d=c!=null;return(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&s()},style:{position:`fixed`,inset:0,zIndex:2147483001,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1100px, 94vw)`,maxHeight:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${o.border}`,borderRadius:`12px`,background:a?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,color:o.primary,fontSize:`14px`,fontWeight:600}},e?.open?`${e.title||``} (${e.url||``})`:`浏览器实时大屏`),(0,r.createElement)(`button`,{type:`button`,onClick:s,style:{width:`28px`,height:`28px`,border:`1px solid ${o.border}`,borderRadius:`6px`,background:`transparent`,color:o.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),d?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 10px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:a?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:a?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${c.instruction}(在下方画面上点击目标元素)`)):null,e?.open?(0,r.createElement)(j,{streamSrc:i,shotSrc:n,alt:`浏览器大图`,...d?{onClick:u}:{},style:{width:`100%`,maxHeight:`55vh`,objectFit:`contain`,borderRadius:`8px`,border:d?`2px dashed #f59e0b`:`1px solid ${o.border}`,background:a?`#10141c`:`#f8fafc`,cursor:d?`crosshair`:`default`}}):(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`13px`,textAlign:`center`,padding:`32px 0`}},`浏览器尚未打开`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`4px`,maxHeight:`22vh`,overflowY:`auto`,borderTop:`1px solid ${o.border}`,paddingTop:`8px`}},(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`12px`,fontWeight:500}},`访问历史 (${t.length} 条,点击在新标签打开)`),t.map((e,t)=>(0,r.createElement)(`a`,{key:t,href:e.url,target:`_blank`,rel:`noopener noreferrer`,style:{display:`flex`,gap:`8px`,alignItems:`center`,color:o.secondary,fontSize:`12px`,textDecoration:`none`,borderRadius:`6px`,padding:`4px 6px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{color:o.tertiary,flex:`none`}},k(e.ts)),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},e.title||e.url))))))}function P({base:e,dark:t,palette:n,onClose:i}){return(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&i()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[i]),(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&i()},style:{position:`fixed`,inset:0,zIndex:2147483002,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1000px, 94vw)`,height:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${n.border}`,borderRadius:`12px`,background:t?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:n.primary,fontSize:`14px`,fontWeight:600}},`回归测试报告 (最新运行)`),(0,r.createElement)(`a`,{href:`${e}/runs/report.html`,target:`_blank`,rel:`noopener noreferrer`,style:{color:n.secondary,fontSize:`12px`,textDecoration:`none`,padding:`4px 8px`,border:`1px solid ${n.border}`,borderRadius:`6px`}},`新标签页打开`),(0,r.createElement)(`button`,{type:`button`,onClick:i,style:{width:`28px`,height:`28px`,border:`1px solid ${n.border}`,borderRadius:`6px`,background:`transparent`,color:n.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),(0,r.createElement)(`iframe`,{src:`${e}/runs/report.html`,title:`回归测试报告`,style:{flex:1,width:`100%`,border:`1px solid ${n.border}`,borderRadius:`8px`,background:`#ffffff`}})))}function F({loginWait:e,onDone:t}){return(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`68px`,width:`260px`,padding:`10px 12px`,borderRadius:`10px`,border:`1px solid #16a34a`,background:`rgba(22, 163, 74, 0.12)`,color:`#15803d`,fontSize:`12px`,lineHeight:`1.5`,boxShadow:`0 4px 16px rgba(0, 0, 0, 0.18)`,pointerEvents:`auto`},title:`Agent 正在等待你完成登录:请在浏览器窗口登录后点击下方按钮`},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`🔐`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,fontWeight:600,wordBreak:`break-all`}},`等待人工登录`)),(0,r.createElement)(`div`,{style:{marginTop:`4px`,wordBreak:`break-all`,opacity:.9}},e.instruction),(0,r.createElement)(`button`,{onClick:t,style:{width:`100%`,marginTop:`8px`,padding:`6px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#16a34a`,color:`#fff`,fontSize:`13px`,fontWeight:600}},`我已完成登录`))}function I(){let e=O(),t=e?D:E,[n,i]=(0,r.useState)(`/browser`),[o,c]=(0,r.useState)(!1),[l,u]=(0,r.useState)(!1),[d,f]=(0,r.useState)(!1),[p,m]=(0,r.useState)([]);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&i(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{status:g,ops:_,shotVersion:v,lastRun:y,agentBusy:b,rerunMsg:x,rerunLast:k}=S(n),{pendingConfirm:A,resolveConfirm:j}=C(n),{loginWait:I,confirmLoginDone:L}=w(n),{pendingPick:R}=T(n);(0,r.useEffect)(()=>{if(!l)return;let e=!0,t=async()=>{let t=await s(n);e&&m(t)};t();let r=setInterval(t,2e3);return()=>{e=!1,clearInterval(r)}},[l,n]);let z=`${n}/screenshot?v=${v}`,B=`${n}/stream`;return(0,r.createElement)(`div`,{id:`dsh-browser-client-overlay`,style:{position:`fixed`,inset:0,pointerEvents:`none`,zIndex:2147483e3}},I&&!I.done?(0,r.createElement)(F,{loginWait:I,onDone:()=>{L(I.id)}}):null,(0,r.createElement)(`button`,{type:`button`,title:`浏览器自动化与实时画面`,onClick:()=>c(e=>!e),style:{position:`fixed`,right:`16px`,bottom:`16px`,width:`42px`,height:`42px`,borderRadius:`999px`,border:`1px solid ${t.border}`,background:e?`#1e2430`:`#ffffff`,color:t.primary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`18px`,boxShadow:`0 4px 16px rgba(0, 0, 0, 0.2)`,transition:`transform 0.15s ease, background 0.15s ease`,pointerEvents:`auto`}},`🌐`),o&&(0,r.createElement)(M,{status:g,ops:_,lastRun:y,agentBusy:b,rerunMsg:x,pendingConfirm:A,pendingPick:R,shotSrc:z,streamSrc:B,dark:e,palette:t,onOpenModal:()=>u(!0),onOpenReport:()=>f(!0),onConfirmDecision:e=>{A&&j(A.id,e)},onRerunLast:k}),l&&(0,r.createElement)(N,{status:g,history:p,shotSrc:z,streamSrc:B,dark:e,palette:t,onClose:()=>u(!1),pendingPick:R,onPick:(e,t,r,i)=>{R&&h(n,R.id,e,t,r,i)}}),d&&(0,r.createElement)(P,{base:n,dark:e,palette:t,onClose:()=>f(!1)}))}function L(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)(``),[o,s]=(0,r.useState)({});return(0,r.useEffect)(()=>{let t=!0;return n(null),a(``),_(e).then(e=>{t&&n(e)}).catch(e=>{t&&a(e instanceof Error?e.message:String(e))}),()=>{t=!1}},[e]),{list:t,error:i,expanded:o,toggleDetail:t=>{s(n=>{let r=n[t];if(r!==void 0&&r!==`loading`){let e={...n};return delete e[t],e}return v(e,t).then(e=>{s(n=>({...n,[t]:e??`failed`}))}).catch(()=>s(e=>({...e,[t]:`failed`}))),{...n,[t]:`loading`}})},remove:async t=>{await y(e,t)&&(n(e=>e===null?e:e.filter(e=>e.name!==t)),s(e=>{let n={...e};return delete n[t],n}))}}}function R(e){return{row:{display:`flex`,alignItems:`center`,gap:`8px`,minWidth:0},label:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,fontSize:`13px`,color:e.primary,cursor:`pointer`},meta:{color:e.tertiary,fontSize:`12px`,whiteSpace:`nowrap`},btn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.secondary,font:`inherit`,fontSize:`12px`,cursor:`pointer`},delBtn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.danger,font:`inherit`,fontSize:`12px`,cursor:`pointer`},itemBox:{display:`flex`,flexDirection:`column`,gap:`6px`,border:`1px solid ${e.border}`,borderRadius:`8px`,padding:`8px 10px`},stepsBox:{display:`flex`,flexDirection:`column`,gap:`2px`,borderTop:`1px solid ${e.border}`,paddingTop:`6px`},stepLine:{display:`flex`,gap:`8px`,fontSize:`13px`,lineHeight:`1.7`,color:e.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`},toolName:{color:e.primary,flex:`none`,fontWeight:500},stepArgs:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,color:e.secondary},empty:{color:e.tertiary,fontSize:`12px`,textAlign:`center`,padding:`12px 0`},root:{display:`flex`,flexDirection:`column`,gap:`8px`,maxWidth:`720px`},heading:{color:e.tertiary,fontSize:`12px`,lineHeight:`1.6`}}}function z(e){let t=O(),n=t?D:E,i=R(n),[o,s]=(0,r.useState)(`/browser`);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&s(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{list:c,error:l,expanded:u,toggleDetail:d,remove:f}=L(o);return(0,r.createElement)(`div`,{style:i.root},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`12px`,paddingBottom:`12px`,marginBottom:`6px`,borderBottom:`1px solid ${n.border}`}},(0,r.createElement)(`div`,{style:{width:`36px`,height:`36px`,borderRadius:`8px`,background:t?`rgba(99, 102, 241, 0.15)`:`rgba(99, 102, 241, 0.1)`,border:`1px solid ${t?`rgba(99, 102, 241, 0.3)`:`rgba(99, 102, 241, 0.2)`}`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`20px`,flex:`none`}},`🌐`),(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`2px`}},(0,r.createElement)(`div`,{style:{fontSize:`15px`,fontWeight:600,color:n.primary}},`浏览器自动化与录制管理`),(0,r.createElement)(`div`,{style:{fontSize:`12px`,color:n.tertiary}},`管理已保存的浏览器回归脚本,查看执行序列与历史数据`))),(0,r.createElement)(`div`,{style:i.heading},`已保存的录制(由 browser_record save 生成,供 browser_replay 一键回放):`),l?(0,r.createElement)(`div`,{style:{...i.empty,color:n.danger}},`加载失败: ${l}`):c===null?(0,r.createElement)(`div`,{style:i.empty},`加载中…`):c.length===0?(0,r.createElement)(`div`,{style:i.empty},`暂无已保存录制。在与 Agent 对话中完成一轮浏览器操作后,输入「保存录制为 xxx」即可沉淀为回归用例。`):c.map(e=>{let t=u[e.name],n=t!==void 0&&t!==`loading`;return(0,r.createElement)(`div`,{key:e.name,style:i.itemBox},(0,r.createElement)(`div`,{style:i.row},(0,r.createElement)(`span`,{style:i.label,title:`点击展开 / 折叠步骤详情`,onClick:()=>d(e.name)},(n?`▾ `:`▸ `)+e.name),(0,r.createElement)(`span`,{style:i.meta},`${e.steps} 步 · ${new Date(e.savedAt).toLocaleDateString(`zh-CN`)}`),(0,r.createElement)(`button`,{type:`button`,style:i.btn,onClick:()=>d(e.name)},n?`收起`:`步骤`),(0,r.createElement)(`button`,{type:`button`,style:i.delBtn,onClick:()=>f(e.name)},`删除`)),n&&t!==`failed`&&(0,r.createElement)(`div`,{style:i.stepsBox},t.steps.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:i.stepLine},(0,r.createElement)(`span`,{style:i.meta},`#${t+1}`),(0,r.createElement)(`span`,{style:i.toolName},e.tool),(0,r.createElement)(`span`,{style:i.stepArgs},JSON.stringify(e.args))))))}))}let B={channel:``,headless:``,userDataDir:``,viewportMode:``,viewportWidth:``,viewportHeight:``,timeoutMs:``,toolset:``,confirmMode:``,screenshotMaxAgeDays:``,screenshotMaxCount:``};function V(e){let t={...e.inherited,...e.override},n=t.viewport,r=``,i=``,a=``;return n===`window`?r=`window`:typeof n==`object`&&n&&(r=`custom`,i=String(n.width??``),a=String(n.height??``)),{channel:typeof t.channel==`string`?t.channel===``?`bundled`:t.channel:``,headless:typeof t.headless==`boolean`?String(t.headless):``,userDataDir:typeof t.userDataDir==`string`?t.userDataDir:``,viewportMode:r,viewportWidth:i,viewportHeight:a,timeoutMs:typeof t.timeoutMs==`number`?String(t.timeoutMs):``,toolset:typeof t.toolset==`string`?t.toolset:``,confirmMode:typeof t.confirmMode==`string`?t.confirmMode:``,screenshotMaxAgeDays:typeof t.screenshotMaxAgeDays==`number`?String(t.screenshotMaxAgeDays):``,screenshotMaxCount:typeof t.screenshotMaxCount==`number`?String(t.screenshotMaxCount):``}}function H(e){let t=e=>e.trim()===``?null:Number(e),n={channel:e.channel===``?null:e.channel===`bundled`?``:e.channel,headless:e.headless===``?null:e.headless===`true`,userDataDir:e.userDataDir.trim()===``?null:e.userDataDir.trim(),timeoutMs:t(e.timeoutMs),toolset:e.toolset===``?null:e.toolset,confirmMode:e.confirmMode===``?null:e.confirmMode,screenshotMaxAgeDays:t(e.screenshotMaxAgeDays),screenshotMaxCount:t(e.screenshotMaxCount)};return n.viewport=e.viewportMode===``?null:e.viewportMode===`window`?`window`:{width:Number(e.viewportWidth),height:Number(e.viewportHeight)},n}function U(e){let t=O(),n=t?D:E,i=R(n),[o,s]=(0,r.useState)(`/browser`),[c,l]=(0,r.useState)(!0),[u,d]=(0,r.useState)(!1),[f,p]=(0,r.useState)(``),[m,h]=(0,r.useState)(B),[g,_]=(0,r.useState)(!1),[v,y]=(0,r.useState)(null);if((0,r.useEffect)(()=>{let e=!0;return(async()=>{let t=(await a()).replace(/\/+$/,``);if(!e)return;s(t);let n=await b(t);e&&(n===null?p(`无法读取插件配置(宿主 API 不可达)`):(d(n.editable),n.editable||p(n.reason??`当前环境不支持在线编辑`),h(V(n))),l(!1))})(),()=>{e=!1}},[]),e.view===`summary`)return null;let S={height:`28px`,padding:`0 8px`,border:`1px solid ${n.border}`,borderRadius:`6px`,background:`transparent`,color:n.primary,font:`inherit`,fontSize:`13px`,minWidth:0},C=(e,t,i)=>(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`4px`}},(0,r.createElement)(`span`,{style:{fontSize:`13px`,color:n.primary,fontWeight:500}},e),t,i===void 0?null:(0,r.createElement)(`span`,{style:{fontSize:`12px`,color:n.tertiary}},i)),w=e=>t=>{let n=t?.target?.value??``;h(t=>({...t,[e]:n})),y(null)},T=(e,t)=>(0,r.createElement)(`select`,{value:m[e],onChange:w(e),style:S,disabled:!u},...t.map(([e,t])=>(0,r.createElement)(`option`,{key:e,value:e},t))),k=(e,t)=>(0,r.createElement)(`input`,{value:m[e],onChange:w(e),placeholder:t,style:S,disabled:!u}),A=async()=>{_(!0),y(null);let e=await x(o,H(m));_(!1),e.ok?y({ok:!0,text:`已保存并经 Loader 应用;浏览器会话随插件重载重启,未生效时请重启 DSH`}):y({ok:!1,text:`保存失败: ${e.error??`未知错误`}`})};return(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`12px`,padding:`4px 0`}},c?(0,r.createElement)(`span`,{style:{fontSize:`13px`,color:n.tertiary}},`读取配置中…`):u?null:(0,r.createElement)(`span`,{style:{fontSize:`13px`,color:n.tertiary}},`只读: ${f}(可手改 profile 的 cordis.patch.yml)`),C(`浏览器通道`,T(`channel`,[[``,`默认(auto:依次尝试 chrome → msedge → chromium)`],[`auto`,`auto`],[`chrome`,`Chrome`],[`msedge`,`Edge`],[`chromium`,`Chromium`],[`bundled`,`Playwright 内置 Chromium`]])),C(`无头模式`,T(`headless`,[[``,`默认(有头窗口,本机调试/人工登录)`],[`false`,`有头`],[`true`,`无头(服务器场景)`]])),C(`登录态持久化目录 (userDataDir)`,k(`userDataDir`,`留空 = 不持久化`),`设置后 Cookie/localStorage 跨重启保留`),C(`页面视口`,T(`viewportMode`,[[``,`默认(1280×720 固定仿真,测试确定性最佳)`],[`window`,`跟随浏览器窗口大小`],[`custom`,`自定义固定尺寸`]])),m.viewportMode===`custom`?(0,r.createElement)(`div`,{style:{display:`flex`,gap:`8px`,alignItems:`center`}},(0,r.createElement)(`input`,{value:m.viewportWidth,onChange:w(`viewportWidth`),placeholder:`宽度,如 1920`,style:{...S,width:`120px`}}),(0,r.createElement)(`span`,{style:{color:n.tertiary}},`×`),(0,r.createElement)(`input`,{value:m.viewportHeight,onChange:w(`viewportHeight`),placeholder:`高度,如 1080`,style:{...S,width:`120px`}})):null,C(`操作超时 (timeoutMs,毫秒)`,k(`timeoutMs`,`默认 30000`)),C(`工具集 (toolset)`,T(`toolset`,[[``,`默认(all:全量工具)`],[`all`,`all(全量)`],[`core`,`core(高频核心集,降 systemPrompt 常驻 token)`]])),C(`敏感操作审批 (confirmMode)`,T(`confirmMode`,[[``,`默认(off)`],[`off`,`off(不主动拦截)`],[`manual`,`manual(提示 agent 先调用 browser_confirm)`],[`auto`,`auto(宿主层硬拦截提交/删除/支付类点击)`]])),C(`截图保留天数 (screenshotMaxAgeDays)`,k(`screenshotMaxAgeDays`,`默认 7;0 = 不按时间清理`)),C(`截图保留数量 (screenshotMaxCount)`,k(`screenshotMaxCount`,`默认 200;0 = 不按数量清理`)),(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`10px`}},(0,r.createElement)(`button`,{onClick:()=>void A(),disabled:!u||g,style:{...i.btn,height:`30px`,padding:`0 16px`,...u&&!g?{background:t?`rgba(99, 102, 241, 0.25)`:`rgba(99, 102, 241, 0.12)`,color:n.primary}:{opacity:.5,cursor:`not-allowed`}}},g?`保存中…`:`保存配置`),v===null?null:(0,r.createElement)(`span`,{style:{fontSize:`12px`,color:v.ok?n.secondary:n.danger}},v.text)))}let W=[`浏览器`,`🌐 浏览器`],G=`data-dsh-browser-nav-icon`,K=`data-dsh-browser-nav-hidden`;function q(){if(typeof document>`u`)return;let e=document.querySelectorAll(`button[class*="navCell"]`),t=e.length>0?Array.from(e):Array.from(document.querySelectorAll(`[class*="navList"] button`));for(let e of t){let t=(e.querySelector(`[class*="navLabel"]`)?.textContent??e.textContent??``).trim();if(!W.some(e=>t===e)||e.querySelector(`[${G}]`))continue;let n=e.querySelector(`svg`);if(!n||!n.parentNode)continue;n.style.display=`none`,n.setAttribute(K,`1`);let r=document.createElement(`span`);r.setAttribute(G,`1`),r.setAttribute(`aria-hidden`,`true`),r.style.display=`inline-flex`,r.style.flex=`none`,r.innerHTML=`<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect x="1.5" y="2.75" width="13" height="10.5" rx="1.75" stroke="currentColor" stroke-width="1.1"/><path d="M1.5 5.75h13" stroke="currentColor" stroke-width="1.1"/><circle cx="3.4" cy="4.25" r="0.65" fill="currentColor"/><circle cx="5.2" cy="4.25" r="0.65" fill="currentColor"/></svg>`,n.parentNode.insertBefore(r,n.nextSibling)}}function J(){try{q()}catch{}}function Y(){if(!(typeof document>`u`)){for(let e of Array.from(document.querySelectorAll(`[${G}]`)))e.remove();for(let e of Array.from(document.querySelectorAll(`svg[${K}]`)))e.style.display=``,e.removeAttribute(K)}}function X(e){if(typeof document>`u`||typeof MutationObserver>`u`)return;J();let t=null;try{t=new MutationObserver(J),t.observe(document.body,{childList:!0,subtree:!0,characterData:!0})}catch{t=null}e.effect(()=>()=>{if(t){let e=t;t=null;try{e.disconnect()}catch{}}try{Y()}catch{}})}let Z=[`slots`];function Q(e){e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`browser`,order:40,label:`浏览器`},z)),X(e),e.slots.inject(`shell.overlay`,()=>e.slots.register({name:`shell.overlay`,id:`dsh-browser-overlay`,order:100},I)),e.slots.inject(`plugins.bundle.config`,()=>e.slots.register({name:`plugins.bundle.config`,key:`dsh-browser-view`},U))}return n.apply=Q,n.inject=Z,t.exports}});
2
2
  //# sourceMappingURL=client.js.map
package/lib/index.js CHANGED
@@ -10443,6 +10443,95 @@ function registerTools(ctx, config) {
10443
10443
  for (const toolList of toolFactories) for (const def of toolList) register(def);
10444
10444
  }
10445
10445
  //#endregion
10446
+ //#region lib/types/browser/config-api.js
10447
+ function configEditorOf(ctx) {
10448
+ const editor = ctx.get("configEditor") ?? ctx.configEditor;
10449
+ if (editor !== void 0 && typeof editor.entries === "function" && typeof editor.configuration === "function" && typeof editor.edit === "function") return editor;
10450
+ }
10451
+ /** 定位本插件的 profile 条目(按包名;恰一个才可编辑,与平台「唯一定位」约束一致)。 */
10452
+ function findOwnEntry(editor) {
10453
+ let entries;
10454
+ try {
10455
+ entries = editor.entries();
10456
+ } catch {
10457
+ return {
10458
+ ok: false,
10459
+ reason: "读取 profile 条目失败"
10460
+ };
10461
+ }
10462
+ const own = entries.filter((e) => e?.options?.name === "dsh-browser-view");
10463
+ if (own.length === 0) return {
10464
+ ok: false,
10465
+ reason: "当前 profile 中未找到本插件条目"
10466
+ };
10467
+ if (own.length > 1) return {
10468
+ ok: false,
10469
+ reason: "profile 中存在多个 dsh-browser-view 条目,无法唯一定位,请手改 cordis.patch.yml"
10470
+ };
10471
+ return {
10472
+ ok: true,
10473
+ entry: own[0]
10474
+ };
10475
+ }
10476
+ function getPluginConfig(ctx, effective) {
10477
+ const editor = configEditorOf(ctx);
10478
+ if (editor === void 0) return {
10479
+ editable: false,
10480
+ reason: "当前环境无 configEditor 服务(非 DSH profile 宿主)",
10481
+ override: {},
10482
+ inherited: {},
10483
+ effective
10484
+ };
10485
+ const found = findOwnEntry(editor);
10486
+ if (!found.ok) return {
10487
+ editable: false,
10488
+ reason: found.reason,
10489
+ override: {},
10490
+ inherited: {},
10491
+ effective
10492
+ };
10493
+ let override = {};
10494
+ let inherited = {};
10495
+ try {
10496
+ const row = editor.configuration().find((r) => r.entry === found.entry);
10497
+ override = { ...row?.override ?? {} };
10498
+ inherited = { ...row?.inherited ?? {} };
10499
+ } catch {}
10500
+ return {
10501
+ editable: true,
10502
+ override,
10503
+ inherited,
10504
+ effective
10505
+ };
10506
+ }
10507
+ /**
10508
+ * 校验并持久化配置变更。抛错(中文消息)即拒绝,不写盘。
10509
+ * - 只接受 schema 声明过的键;
10510
+ * - 值按 BROWSER_CONFIG_SCHEMA 校验(viewport 额外走 viewportOptionOf 形状校验);
10511
+ * - 校验以「合并默认值后的完整候选」进行,与启动时 resolveConfig 口径一致。
10512
+ */
10513
+ async function savePluginConfig(ctx, changes) {
10514
+ const editor = configEditorOf(ctx);
10515
+ if (editor === void 0) throw new Error("当前环境无 configEditor 服务,无法保存配置");
10516
+ const found = findOwnEntry(editor);
10517
+ if (!found.ok) throw new Error(found.reason);
10518
+ for (const key of Object.keys(changes)) if (!(key in BROWSER_CONFIG_SCHEMA)) throw new Error(`未知配置键: ${key}`);
10519
+ await editor.edit(found.entry, (current) => {
10520
+ const next = { ...current ?? {} };
10521
+ for (const [key, value] of Object.entries(changes)) if (value === null) delete next[key];
10522
+ else next[key] = value;
10523
+ const candidate = {
10524
+ ...BROWSER_DEFAULT_CONFIG,
10525
+ ...next
10526
+ };
10527
+ const errors = validateConfig(BROWSER_CONFIG_SCHEMA, candidate);
10528
+ if (errors.length > 0) throw new Error(`配置无效 — ${errors.join("; ")}`);
10529
+ if (next.viewport !== void 0) viewportOptionOf(next);
10530
+ return next;
10531
+ });
10532
+ return { saved: true };
10533
+ }
10534
+ //#endregion
10446
10535
  //#region lib/types/browser/ui.js
10447
10536
  /** 展示层参数截断:单字段 JSON 超过 300 字符截断(仅用于面板轨迹显示,不进录制)。 */
10448
10537
  function truncateForDisplay(args) {
@@ -10714,6 +10803,23 @@ function registerBrowserApi(ctx, config, base) {
10714
10803
  }
10715
10804
  return;
10716
10805
  }
10806
+ if (path === `${base}/config`) {
10807
+ if ((req.method ?? "GET") === "POST") {
10808
+ const body = await readJsonBody(req).catch(() => void 0);
10809
+ if (typeof body?.changes !== "object" || body.changes === null || Array.isArray(body.changes)) {
10810
+ sendJson(res, 400, { error: "body.changes 必须是对象(key → 值;null = 删除该键回默认)" });
10811
+ return;
10812
+ }
10813
+ try {
10814
+ sendJson(res, 200, await savePluginConfig(ctx, body.changes));
10815
+ } catch (e) {
10816
+ sendJson(res, 400, { error: e instanceof Error ? e.message : String(e) });
10817
+ }
10818
+ return;
10819
+ }
10820
+ sendJson(res, 200, getPluginConfig(ctx, config));
10821
+ return;
10822
+ }
10717
10823
  sendText(res, 404, "not found");
10718
10824
  }
10719
10825
  }));
@@ -0,0 +1,25 @@
1
+ import type { ReefContext } from "../lib/types.js";
2
+ import type { BrowserConfig } from "./types.js";
3
+ export interface PluginConfigView {
4
+ editable: boolean;
5
+ reason?: string;
6
+ /** profile 层显式覆盖值(用户可编辑的部分)。 */
7
+ override: Record<string, unknown>;
8
+ /** 下层(组合包 patch)组合出的值。 */
9
+ inherited: Record<string, unknown>;
10
+ /** 运行中生效的完整配置(resolveConfig 之后)。 */
11
+ effective: BrowserConfig;
12
+ }
13
+ export declare function getPluginConfig(ctx: ReefContext, effective: BrowserConfig): PluginConfigView;
14
+ /** 配置变更集:key → 新值;值为 null 表示从条目配置中删除(回落到继承/默认)。 */
15
+ export type PluginConfigChanges = Record<string, unknown>;
16
+ /**
17
+ * 校验并持久化配置变更。抛错(中文消息)即拒绝,不写盘。
18
+ * - 只接受 schema 声明过的键;
19
+ * - 值按 BROWSER_CONFIG_SCHEMA 校验(viewport 额外走 viewportOptionOf 形状校验);
20
+ * - 校验以「合并默认值后的完整候选」进行,与启动时 resolveConfig 口径一致。
21
+ */
22
+ export declare function savePluginConfig(ctx: ReefContext, changes: PluginConfigChanges): Promise<{
23
+ saved: true;
24
+ }>;
25
+ //# sourceMappingURL=config-api.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { BrowserOp, BrowserStatus, LatestRun, NavHistoryItem, PendingConfirm, PendingLoginWait, PendingPick, RecordingDetail, RecordingRow } from "./types.js";
1
+ import type { BrowserOp, BrowserStatus, LatestRun, NavHistoryItem, PendingConfirm, PendingLoginWait, PendingPick, PluginConfigState, RecordingDetail, RecordingRow } from "./types.js";
2
2
  /** 获取插件后端 API 基路径 (默认为 /browser) */
3
3
  export declare function getBasePath(): Promise<string>;
4
4
  export declare function fetchStatus(base: string): Promise<BrowserStatus | null>;
@@ -24,4 +24,11 @@ export declare function triggerRerun(base: string, source: string, retryOf?: str
24
24
  export declare function fetchRecordings(base: string): Promise<RecordingRow[]>;
25
25
  export declare function fetchRecordingDetail(base: string, name: string): Promise<RecordingDetail | null>;
26
26
  export declare function deleteRecordingApi(base: string, name: string): Promise<boolean>;
27
+ /** 读取插件配置(插件页配置面板)。 */
28
+ export declare function fetchPluginConfig(base: string): Promise<PluginConfigState | null>;
29
+ /** 保存插件配置变更(value 为 null 表示删除该键回默认)。 */
30
+ export declare function savePluginConfigApi(base: string, changes: Record<string, unknown>): Promise<{
31
+ ok: boolean;
32
+ error?: string;
33
+ }>;
27
34
  //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,13 @@
1
+ /** plugins.bundle.config 的 owner props(契约见 dsh-client-ui-plugin-manager slot-contract)。 */
2
+ export interface PluginConfigViewProps {
3
+ view: "summary" | "page";
4
+ }
5
+ export declare function BrowserPluginConfigPage(props: PluginConfigViewProps): import("react").DetailedReactHTMLElement<{
6
+ style: {
7
+ display: "flex";
8
+ flexDirection: "column";
9
+ gap: string;
10
+ padding: string;
11
+ };
12
+ }, HTMLElement> | null;
13
+ //# sourceMappingURL=PluginConfigForm.d.ts.map
@@ -12,6 +12,14 @@ declare module "@deepseek-ai/dsh-client-ui-slots" {
12
12
  kind: "list";
13
13
  scope: "root";
14
14
  };
15
+ /** 侧栏「插件」页的组合包配置区(key = 包名;契约属 dsh-client-ui-plugin-manager)。 */
16
+ "plugins.bundle.config": {
17
+ kind: "keyed";
18
+ scope: "root";
19
+ owner: {
20
+ view: "summary" | "page";
21
+ };
22
+ };
15
23
  }
16
24
  }
17
25
  /** Required services (cordis fiber inject). */
@@ -78,4 +78,12 @@ export interface LatestRun {
78
78
  };
79
79
  retryOf?: string;
80
80
  }
81
+ /** 插件配置视图(GET {base}/config):override = profile 层显式值,effective = 生效值。 */
82
+ export interface PluginConfigState {
83
+ editable: boolean;
84
+ reason?: string;
85
+ override: Record<string, unknown>;
86
+ inherited: Record<string, unknown>;
87
+ effective: Record<string, unknown>;
88
+ }
81
89
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-browser-view",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "DSH 插件:共享 Playwright 浏览器自动化(38 个 browser_* 工具,含 what 意图语义定位(一次调用替代快照→查找→点击)、SoM 标徽看图点击与零命中截图兜底、SPA ref 软失效重锚定、open Shadow DOM 组合树穿透、跨 Frame/IFrame 定位、视口感知动态快照、批量表单智能填充、点击效果验证与定位失败遥测、Playwright 规范代码导出、批处理、视觉回归、选择器自愈、增量快照)+ 人机协作(面板点选教学 browser_pick_element、人工接管等待、敏感操作审批硬拦截、人机混合录制、面板一键重跑)+ CDP 流式推流、拟人化轨迹与高斯击键、全景时光机、企业富控件矩阵。另附独立 MCP server 入口(bin dsh-browser-mcp,stdio),任意 MCP 客户端可直接调用全部工具。运行时依赖 playwright-core 与 @modelcontextprotocol/sdk(仅 MCP 入口,不进 DSH 插件加载链)。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",