最近AI圈最热的关键词就一个字Code。Claude Code把终端Agent带火之后各家大模型产品都在往命令行里塞编程助手今天你出一个Code明天我出一个Skill谁不说自己能接管代码库谁就不好意思出门。但就在大家盯着终端卷得昏天黑地的时候Kimi掏出来的东西有点不一样——不是又一个Code而是一个浏览器插件核心卖点一句话概括网页操作一秒变Skill。这件事有意思的地方在于它没有去跟Claude Code、Codex这些选手正面拼谁更懂代码而是把AI的能力从终端搬到了浏览器里。用浏览器的人可比在终端里敲命令的人多太多了。这篇文章我就想把这套思路掰开揉碎聊一聊为什么Kimi要在这个时间点做浏览器插件所谓网页操作一秒变Skill到底在技术上怎么落地如果你也想做一款类似的浏览器插件开发过程中有哪些绕不开的环节和坑。1. Code赛道已经卷到头Kimi却换了一条路1.1 终端编程工具的Skill化内卷Claude Code刚火的时候大家觉得终于有个AI能在终端里读懂整个代码仓库了兴奋劲儿还没过Codex Skill又来了再往后各种所谓Skill机制满天飞。本质上这批工具都在做同一件事把Agent的能力封装成可复用的技能包。你今天让它分析这个项目的依赖关系明天让它帮我跑一遍测试再修掉报错这些重复性操作一旦沉淀成Skill调用成本确实低了很多。但问题也随之而来这条赛道越来越挤而且挤进来的人高度同质化。终端型Agent的门槛天然就高用户得先会用命令行得懂Git得知道什么叫ESLint、什么叫CI普通人看到终端窗口第一反应是关掉。换句话说Claude Code们卷来卷去卷的还是程序员群体还是那一小撮能让终端Agent发挥价值的用户。不是说这个方向不好而是说当所有人都挤在一个入口抢同一个用户群时边际收益会迅速下降。这时候如果有一个玩家突然抽身转向一个用户量级完全不同、但同样高频的场景去布局那它抢的可能不是同一块蛋糕而是下一块蛋糕。1.2 浏览器插件是AI落地的另一个超级入口浏览器是什么是绝大多数人每天花时间最多的数字工具。你可以不懂命令行但你不可能不用浏览器。逛网页、填表单、查资料、订机票、做报表、扒数据这些动作天天在发生而且绝大多数是重复劳动。Kimi做浏览器插件本质上就是把这些网页里的高频重复操作变成可以被AI理解和执行的Skill。你录一遍操作插件把动作抽成结构化的技能描述下次你说一句话它就照着Skill自动跑一遍。这比让用户去学一套自动化脚本语言友好太多了。关键还得看清这步棋的意图做终端Code是在收割存量程序员市场做浏览器插件是在捕捉所有网页使用者的行为数据。网页操作一旦结构化就相当于给AI提供了海量的真实数字行为样本这比在模拟环境里练出来的Agent行为认知值钱得多。所以我说这不是战术性的产品补位而是战略性的场景包围——不跟你拼谁更会写代码跟你拼谁更懂普通用户怎么用浏览器。2. 网页操作变Skill核心玩法到底怎么拆2.1 Skill是什么把大脑和肌肉记忆分开很多人一听到Skill就头大觉得是什么高深的东西。其实用一个生活里的例子就讲透了你刚学开车的时候挂挡、踩离合、看后视镜全靠脑子一个个想手忙脚乱。三个月之后这些动作变成肌肉记忆你甚至不用过脑子上车就走。Skill就是AI的肌肉记忆。注意Skill和Agent是两回事。Agent是那个能理解复杂问题、拆解任务、动态决策的大脑Skill是某个特定任务的标准操作流程。我在实际开发中的感受是如果什么都让Agent临时发挥速度慢、不稳定、还容易跑偏如果你提前把高频操作封装成SkillAgent接到指令后直接调用又稳又快。Kimi的思路就是把浏览器里的高频网页操作——比如网页内容提取、表单自动填写、多页面数据抓取——提前封装成Skill让大模型不用每次重新理解一遍。这和Claude Code里的Skill机制逻辑一致只是落地场景从终端换成了浏览器。终端Skill操作的是文件系统、Shell命令和代码仓库浏览器Skill操作的是DOM、事件和页面状态。载体变了结构化思路完全相通。2.2 网页操作怎么变成Skill四个关键环节如果让我复刻Kimi这套网页操作一秒变Skill的流程我会把它拆成四个环节。第一个环节是感知也就是录制。插件需要在网页里监听点击、输入、滚动这些关键事件把用户的操作轨迹完整地记录下来。这里遇到的第一道坎是记录不是简单存我点了哪里而是要知道我点的是页面上的什么元素。同一个按钮刷新页面后它的位置可能变但它的身份标识不会变所以录制的本质是在提取元素的稳定身份。第二个环节是抽象也就是生成Skill。原始操作记录只是一堆时间戳和事件类型得把它们归纳成结构化的步骤序列。比如点了A按钮→输入了B内容→点了C提交可以抽象成点击元素A→填充字段B→提交表单。这一步非常考验抽象粒度太粗了Skill泛化能力差换个页面菜单长得不一样就跑不动太细了又没法迁移每个页面都要单独录一条。第三个环节是执行也就是回放。Skill文件生成后AI根据用户一句话触发回放插件在页面里定位这些元素、执行对应动作、按步骤推进。回放不是简单模拟点击因为真实页面里有弹窗、有异步加载、有等待回放引擎必须处理这些动态因素。第四个环节是反馈也就是校验。跑完Skill之后系统要检查每一步是否真的生效了比如表单是不是提交成功、下一页是不是真的跳转了。没有反馈闭环的Skill就是盲操作跑一次失败一次你还不知道为什么。Kimi这种产品要想做到一秒变Skill还能稳定复现这四个环节缺一不可。2.3 为什么轻量Skill比全自动Agent更适合浏览器场景我知道有人会问既然大模型这么强为什么不直接让Agent自己看网页然后操作非要先录成Skill说实话全自动的网页Agent我也试过效果是真的不够稳。原因在于浏览器页面是一个复杂的动态环境广告遮挡、弹窗劫持、组件懒加载都能让AI的判断失灵。如果完全依赖大模型的实时视觉理解一次操作要调好几次模型延迟上去了成本也上去了。Skill的优势在于把高频路径固化下来。AI每次执行同样的Skill消耗的推理资源远小于临场发挥速度和稳定性却高出一大截。用延时换推理成本的下降这笔账怎么算都划算。而且对用户来说能看得见的操作流程比黑盒Agent更可信出了错也知道在哪一步排查。3. 浏览器插件开发绕不开的硬核技术点3.1 Manifest V3架构一个扩展的三个世界如果你要做一个网页操作类插件首先得理解Chrome扩展的架构。很多人上来就写代码结果在Content Script里调不到页面上的函数在Popup里访问不到网页DOM卡了半天才发现是架构理解出了问题。Chrome扩展在Manifest V3下有几个互相隔离的上下文。Content Script运行在页面上下文里能访问DOM但它是跑在隔离世界中的访问不到页面本身的JavaScript变量Background Service Worker是扩展的核心调度层生命周期非常短不能长时间在线等消息Popup页面是插件的UI跟页面DOM没有直接关系。这三个世界之间靠消息传递通信也就是Extension里的Postman——chrome.runtime.sendMessage和chrome.tabs.sendMessage。做一个网页操作Skill插件最常见的架构是Popup负责展示Skill列表和触发按钮Content Script负责在页面里监听用户操作、执行DOM注入动作Background负责协调Skill的存储和调度再配合chrome.scripting.executeScript做全局注入。你要做的不是让代码在一个地方跑完所有事而是把任务拆到合适的世界里然后搭好消息桥。3.2 调用页面JS函数的核心手法标题里有个热词叫浏览器插件如何调用页面JS函数这确实是很多入门开发者卡住的地方。Content Script虽然跑在页面里但它的执行环境是隔离的直接window.someFunction()大概率是拿不到的。推荐的MVP3做法是四条路。路径一通过chrome.scripting.executeScript向页面注入一个脚本这个脚本能自动访问页面上下文环境中的变量和函数。路径二在Content Script里创建一个script标签把要执行的函数代码挂载进去这样代码在被注入后会运行在页面的主上下文。路径三用window.postMessage从Content Script向页面环境发消息页面侧监听并响应。路径四也常见就是让页面自定义一个事件Content Script派发事件页面监听执行。四条路没有绝对好坏取决于你要调用的函数是页面暴露出来的全局方法还是模块内部闭包里的逻辑。我踩过的一个坑是很多网站现在会做严格的CSP内容安全策略内联脚本注入会被直接拦掉。这种情况下postMessage配合页面自定义事件反而是最稳的方案因为消息传递不触发CSP限制。另外提醒一句网上有很多让用户在DevTools控制台粘贴脚本的教程千万不要碰——控制台里粘贴你读不懂的代码相当于把浏览器后台权限交给陌生人这是我在项目里反复给团队立的规矩。3.3 录制与回放让Skill稳定的细节沉积录制与回放是本项目的灵魂也是最容易翻车的地方。先说录制。监听click、input、change这些事件不难难的是生成稳定的元素定位器。我的策略优先级是这样优先用元素的id没有id就用data-testid或data-cy这类测试专用属性再退一步用name属性加标签名实在不行才用类名或XPath——因为前端改版时类名和XPath的变更频率远超id和数据属性。再说回放。网页里的元素不是一秒内全部就绪的尤其是富应用页面你得等。等的方法也讲究不要用固定sleep最好用显式等待循环查询目标元素直到出现或超时。我在实际操作中给回放引擎加的细节是每次动作执行后不仅检查元素是否存在还检查它是否可点击、是否被遮挡。别小看这一步页面里最坑的就是某些元素渲染出来了但被一个透明遮罩层盖住点击落到了遮罩上Skill白跑一趟。还有一个细节是输入事件。直接给input元素的value赋值然后dispatchEvent很多框架是能识别change但识别不了input最终表单验证过了但数据没提交上去。正确做法是模拟原生setter然后触发input事件操作完用requestAnimationFrame或者微任务再检查一遍值有没有真的写进去。这些细节不处理录制回放就是玩具。3.4 最小权限原则别让插件一上来就要全部权限浏览器插件的一个天然风险点是权限。用户装插件时看到读取浏览历史访问所有网站数据没有几个人敢点确认。Kimi这种面向大众用户的插件权限申请一定会非常克制这也是开发网页操作类插件时最容易被忽略的产品细节。技术上Manifest V3的permissions和host_permissions是分开的。如果插件只是用户主动去某个标签页执行操作host_permissions用activeTab就够了不要一上来就申请all_urls的全站权限。activeTab这份权限在你点插件图标的那一刻才会授予用户心理压力小得多商店审核也更容易过。另外一个容易被忽略的点是页面内容的安全校验。Skill本质上是一段描述要操作哪个元素、填什么值的数据但数据里如果夹带了脚本逻辑呢所以回放引擎永远不要用innerHTML直接渲染Skill里带的模板内容变量插值要做转义执行动作只能走白名单。这不是小题大做浏览器插件一旦被恶意Skill利用就是稳定窃取用户输入的通道。4. 实操从零搭一个网页操作Skill插件4.1 项目骨架与Manifest配置下面这套就是我基于常见实践搭的一个最小项目结构你可以直接抄作业。目录大概是这样的web-action-skill/ ├── manifest.json ├── background.js ├── content/ │ └── recorder.js ├── popup/ │ ├── popup.html │ └── popup.js └── skills/ └── sample-skill.jsonManifest V3的配置文件是这么写的{ manifest_version: 3, name: Web Action Skill Recorder, version: 0.1.0, permissions: [scripting, activeTab, storage], host_permissions: [all_urls], background: { service_worker: background.js }, content_scripts: [ { matches: [all_urls], js: [content/recorder.js], run_at: document_idle } ], action: { default_popup: popup/popup.html, default_title: Web Action Skill } }host_permissions里暂时保留全站权限是为了开发方便但发布前我强烈建议改成按需授权或者干脆删掉让用户手动点允许。商业产品要是想面向大众这块权限务必要抠到最小。4.2 录制模块捕捉稳定的元素身份Content Script里的录制逻辑核心就一件事把用户操作翻译成不依赖页面布局的元素定位规则。我给出的代码只保留最关键的骨架但已经能跑通基本录制。const trackedEvents [click, input, change, keydown]; const steps []; function getStableSelector(el) { if (!el || el.nodeType ! 1) return null; if (el.id) return #${CSS.escape(el.id)}; const dataTestId el.getAttribute(data-testid) || el.getAttribute(data-cy); if (dataTestId) return [data-testid${dataTestId}]; if (el.getAttribute(name)) { return ${el.tagName.toLowerCase()}[name${el.getAttribute(name)}]; } const withClass el.tagName.toLowerCase() . Array.from(el.classList).join(.); return withClass || null; } function capture(e) { const target e.target; const selector getStableSelector(target); if (!selector) return; const step { stepId: steps.length 1, action: e.type click ? click : e.type input || e.type change ? fill : press, selector, value: target.value ?? null, url: location.href }; steps.push(step); console.log([recorder] step recorded:, step); } trackedEvents.forEach((eventType) { document.addEventListener(eventType, capture, true); });这里有个特别容易踩的坑键盘事件。如果监听keydown用户每敲一个字母都会记一条Skill里全是噪声。实际做法是keydown只监听Enter和Tab这类有语义的按键普通输入交给input事件去记并且input事件要防抖避免用户在连续输入时产生大量中间态记录。我在项目里给input事件加了一个300毫秒的防抖窗口这样整个输入动作最后只会沉淀成一次fill步骤回放时也只用填一次最终值。4.3 Skill描述与回放引擎录制完成后把steps序列转化成Skill描述文件。我一般会根据页面URL把Skill拆分比如在报表系统填月度表单和在报表系统导出图表是两条Skill绝不混在一起。Skill其实就是一份JSON关键不在于格式多花哨而在于它能不能被回放引擎稳定执行。{ skill: fill-monthly-report, name: 月度报表填写, matchedUrlPattern: */report/monthly, description: 自动填写月度营收与成本表单并提交, steps: [ { action: click, selector: #nav-report }, { action: wait, timeout: 800 }, { action: fill, selector: #revenue, value: {{revenue}} }, { action: fill, selector: #cost, value: {{cost}} }, { action: click, selector: button[typesubmit] } ], timeout: 10000 }{{revenue}}和{{cost}}是Skill参数回放时由用户输入或上游数据填充这样一条Skill就能服务不同数据场景不需要每次重录。回放引擎的骨架逻辑如下async function replaySkill(skill, variables) { for (const step of skill.steps) { if (step.action wait) { await sleep(step.timeout); continue; } const el await waitForSelector(step.selector, skill.timeout); if (!el) throw new Error(元素未找到: ${step.selector}); switch (step.action) { case click: el.click(); break; case fill: setNativeValue(el, interpolate(step.value, variables)); el.dispatchEvent(new Event(input, { bubbles: true })); el.dispatchEvent(new Event(change, { bubbles: true })); break; } } }注意回放引擎里我用了setNativeValue而不是直接赋值这是为了绕开React和Vue的受控组件。直接改value再触发change很多人改完发现页面UI没变数据也没提交其实就是框架压根不知道你改了值。用原生setter配合input事件是常规实践里最稳的一招。5. 常见问题与避坑清单5.1 高频问题速查表问题现象根本原因解决思路回放时元素找不到页面改版或元素为动态加载选择器降级策略从id到data属性到XPath逐级尝试点击无效但元素存在被透明遮罩或弹窗遮挡回放前检查元素可见性与遮挡状态先关闭干扰弹窗表单填了但提交为空框架受控组件未识别赋值使用原生setter事件要同时触发input和changeSPA页面操作错乱路由异步渲染元素时序不一致显式等待关键元素出现等路由切换完成再继续iframe内操作失效子页面与父页面脚本隔离用chrome.scripting.executeScript指定frameIds执行插件一装上就被卸载权限申请过重用户不信任聚焦最小权限activeTab起步按需要动态申请5.2 iframe跨域和动态渲染两大拦路虎网页操作插件最怕两类页面一是嵌套iframe的二是前端渲染全靠JavaScript的SPA。iframe的问题在于Content Script默认只能访问顶层页面的DOM嵌入的跨域iframe里的元素你是拿不到的。解决方案我记得前面提过就是给chrome.scripting.executeScript传入frameIds把录制和回放脚本注入到指定iframe里去执行。但如果iframe跨域且页面不配合这条路也走不通这种场景我目前的处理方式是直接告诉用户这条Skill只支持页面主文档内的操作。SPA的问题则是另外一个维度。很多单页应用的路由切换不是传统整页刷新而是JS动态替换视图。录制的步骤间隔、路由挂载时间都不可控回放时经常差一步就崩。我的对策是增加一个前置等待每次触发点击后通过history和location的状态变化或者一个标志性元素的出现来判断路由是否真的完成切换而不是傻等固定秒数。这个细节做扎实之后SPA上的Skill成功率能提升一大截。5.3 安全意识和DevTools控制台警告最后必须单独说一条安全底线。开发调试过程中你会在浏览器DevTools控制台里看很多日志但永远不要粘贴你不知道来源的代码。网上某些教程会教你在控制台里执行一段脚本去破解网页限制或者加载某个辅助脚本这些玩意的本质是把页面上下文暴露给外部脚本。轻则你的浏览器被塞进一堆跟踪代码重则Cookie和密码直接被截走。做浏览器插件的人更应该懂这条线一个能注入任意代码的插件和一段能任意执行的脚本本质上是同一种能力用好了是工具用歪了就是漏洞。Kimi这种面向大众的浏览器插件如果连基础的安全边界都没守住那网页操作Skill做得再顺滑也没有意义。这也是我在团队里反复强调的产品越大众安全标准越高。做网页操作Skill插件这一路摸索下来我个人最大的体会是Kimi反打浏览器插件这步棋既不是技术炫技也不是市场噱头而是把Skill这个概念从开发者终端搬到了普通用户的日常。真正值钱的不是那个录脚本的插件本身而是任何一种网页操作都可以被沉淀成可复用技能这套认知。我自己在项目里复刻最小实现的时候最明显的感受是平台到底是什么没那么重要Claude Code也好、Kimi浏览器插件也好、自己写的Chrome扩展也好核心都是抓住高频场景把重复劳动打包成一块肌肉记忆。掌握这套思路之后你会发现AI竞争的下半场比的也许不是谁的模型更强而是谁更早把模型塞进用户离不开的那个日常窗口里。