<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>人机协作 on AI博士 万戈</title>
        <link>https://www.yesmiracle.net/tags/%E4%BA%BA%E6%9C%BA%E5%8D%8F%E4%BD%9C/</link>
        <description>AI博士万戈的技术博客，聚焦 Agentic AI、AI Infra 与 Agent Security，分享 AI 基础设施与工程落地实践。</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <managingEditor>admin@yesmiracle.net (万戈)</managingEditor>
        <webMaster>admin@yesmiracle.net (万戈)</webMaster>
        <lastBuildDate>Sat, 24 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.yesmiracle.net/tags/%E4%BA%BA%E6%9C%BA%E5%8D%8F%E4%BD%9C/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Neko 源码级拆解：把浏览器变成一条视频流，以及「只出像素」为什么是关键</title>
        <link>https://www.yesmiracle.net/post/20261024-neko-virtual-browser-teardown/</link>
        <pubDate>Sat, 24 Oct 2026 00:00:00 +0000</pubDate>
        <author>admin@yesmiracle.net (万戈)</author>
        <guid>https://www.yesmiracle.net/post/20261024-neko-virtual-browser-teardown/</guid>
        <description>&lt;img src="https://www.yesmiracle.net/post/20261024-neko-virtual-browser-teardown/cover.svg" alt="Featured image of post Neko 源码级拆解：把浏览器变成一条视频流，以及「只出像素」为什么是关键" /&gt;&lt;p&gt;Neko 在 GitHub 上的自我介绍是「一个自托管的虚拟浏览器，跑在 Docker 里，用 WebRTC」。22.5k star，Apache-2.0，Go 写的，2020 年 3 月建仓，到现在 13 个 release、59 个 tag。&lt;/p&gt;
&lt;p&gt;按这个描述，它很容易被归到「远程浏览器」或者「看片派对工具」那一类里去。但如果你仔细看它的架构，会发现它做的动作比那两句话大得多，而且这个动作恰好补上了 agent 流水线里一个空位。&lt;/p&gt;
&lt;h2 id=&#34;一核心动作把桌面降维成一条媒体流&#34;&gt;一、核心动作：把桌面降维成一条媒体流&lt;/h2&gt;
&lt;p&gt;Neko 的架构文档里有一句关键描述：它把一个&lt;strong&gt;虚拟桌面环境&lt;/strong&gt;从 Docker 容器里串流到浏览器，走 WebRTC。注意是虚拟桌面，不是只串一个浏览器窗口。&lt;/p&gt;
&lt;p&gt;系统分四层：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;层&lt;/th&gt;
          &lt;th&gt;职责&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;客户端&lt;/td&gt;
          &lt;td&gt;Vue.js 单页应用，负责把流解码播放，把用户的输入事件回传&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;服务端&lt;/td&gt;
          &lt;td&gt;Go 写的进程，管会话、管房间、管权限&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;媒体采集管线&lt;/td&gt;
          &lt;td&gt;从虚拟桌面抓画面、编码、广播给所有观看者&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;桌面控制&lt;/td&gt;
          &lt;td&gt;把输入事件送进虚拟 X 桌面&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;仓库里的文件直接对应这套分层，读一遍就能看出重心在哪：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;路径&lt;/th&gt;
          &lt;th&gt;职责&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;server/internal/capture/manager.go&lt;/code&gt;、&lt;code&gt;broadcast.go&lt;/code&gt;、&lt;code&gt;streamsink.go&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;采集与广播管线，整个项目最核心的一块&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;server/internal/webrtc/peer.go&lt;/code&gt;、&lt;code&gt;metrics.go&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;WebRTC 连接与质量指标&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;server/internal/session/manager.go&lt;/code&gt;、&lt;code&gt;session.go&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;会话与房间&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;server/internal/desktop/manager.go&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;虚拟桌面控制&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;server/pkg/types/codec/codecs.go&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;编解码器类型&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;server/cmd/serve.go&lt;/code&gt;、&lt;code&gt;server/neko.go&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;启动入口&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;client/src/store/remote.ts&lt;/code&gt;、&lt;code&gt;client/src/components/video.vue&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;前端输入回传与视频播放&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;三个 &lt;code&gt;capture&lt;/code&gt; 文件加一个 &lt;code&gt;codec&lt;/code&gt; 目录，占了最重的分量。这说明 Neko 本质上是&lt;strong&gt;一个媒体系统，附带了一个浏览器&lt;/strong&gt;，反过来看会理解错它的重心。&lt;/p&gt;
&lt;h3 id=&#34;为什么是桌面而不是浏览器&#34;&gt;为什么是「桌面」而不是「浏览器」&lt;/h3&gt;
&lt;p&gt;这个区别不是措辞上的讲究。因为串的是整个虚拟桌面，容器里可以放任何 GUI 程序，浏览器只是其中最常见的一个。这解释了它的镜像为什么按 &lt;code&gt;&amp;lt;application&amp;gt;&lt;/code&gt; 组织、还带 &lt;code&gt;&amp;lt;flavor&amp;gt;&lt;/code&gt; 变体：&lt;code&gt;firefox&lt;/code&gt; 是默认别名，此外还有基于其他浏览器、甚至其他应用镜像的版本。你想让用户通过一条流访问的东西，可以是一个网页，也可以是一个桌面软件，架构上不需要改任何东西。&lt;/p&gt;
&lt;p&gt;也正因为是桌面，输入事件的处理必须是通用的（鼠标、键盘、剪贴板），抽象层次停在坐标上。这跟浏览器自动化工具差了一整层：后者知道什么叫「一个按钮」，前者只知道「坐标 (x, y) 上发生了一次点击」。这个层次差别决定了后面所有的能力边界，包括为什么它能接住一个真人在操作。&lt;/p&gt;
&lt;h2 id=&#34;二这个选择解锁了什么&#34;&gt;二、这个选择解锁了什么&lt;/h2&gt;
&lt;p&gt;把桌面降维成媒体流之后，一堆原本很难做的能力变成了「流」的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多用户共享同一个浏览器实例。&lt;/strong&gt; 因为输出是一条流，扇出（fan-out）就是分发流，不需要为每个观看者复制一份浏览器。十个人看同一个浏览器，成本几乎等于一个人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;录制与转播。&lt;/strong&gt; 官方支持通过 RTMP 把房间内容推到 Twitch、YouTube 这类平台，而且&lt;strong&gt;没有人在线时流也照跑&lt;/strong&gt;，所以 24 小时无人值守广播是它能干的活。这条能力之所以成立，同样是因为内部已经是一条媒体管线，往外推一份到 RTMP 只是多接一个 sink。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;权限回收变成断流。&lt;/strong&gt; 访问控制在这个架构里很干净：不再给你推流，你的画面就停了。不需要撤销 cookie、不需要踢会话、不需要清理对方机器上的任何东西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;和 VNC 那类方案的分野也在这里。&lt;/strong&gt; 远程桌面协议的设计目标通常是「一个操作者控制一台机器」，所以它的抽象是控制通道。Neko 的抽象是媒体流，而流的天然属性是多人、可录制、可转发、可中断。同一份底层技术（抓画面 + 编码 + 传输），抽象选得不同，能长出来的产品形状就完全不同。&lt;/p&gt;
&lt;h2 id=&#34;三只出像素的信任模型&#34;&gt;三、「只出像素」的信任模型&lt;/h2&gt;
&lt;p&gt;这是我认为 Neko 最被低估的一点，而且它对 agent 场景的意义比它宣传的那个卖点更大。&lt;/p&gt;
&lt;p&gt;官方的说法是把这条当知识产权保护来讲：WebRTC 只共享视频和音频，不共享真实数据；别人没法反编译你的代码，因为代码根本不在他的机器上运行；访问权你可以随时收回。&lt;/p&gt;
&lt;p&gt;这个说法是对的，但放到 agent 场景里，它还有一层更实际的推论：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当人和 agent 协作时，人的那台机器上永远不会有页面代码、token、cookie。只有像素。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这件事为什么重要，看看替代方案就清楚了。如果让用户在自己的浏览器里登录一个站点，然后把会话交给 agent，那么要么你要求用户把凭据交给你（大多数人不该这么做，你也不该要），要么你让用户在自己的浏览器里操作、再想办法把会话搬出来（cookie 导出，安全上很脏）。&lt;/p&gt;
&lt;p&gt;而 Neko 这条路是：真浏览器在服务器上，用户看到的是它的画面，用户在画面上敲的东西变成输入事件回给那个浏览器。&lt;strong&gt;凭据从头到尾没有离开过服务器，用户的机器上只有一段视频。&lt;/strong&gt; 于是「让用户自己登录」第一次在安全上说得通。&lt;/p&gt;
&lt;p&gt;需要说清的边界：这条性质保护的是&lt;strong&gt;数据和代码的传输路径&lt;/strong&gt;，不保护&lt;strong&gt;画面内容&lt;/strong&gt;。用户屏幕上看到的仍然是一个真实的浏览器窗口，所以页面上可见的东西（表单里已填的内容、页面上显示的验证码、错误提示）照样被看到。它解决的是「凭据不落地到用户机器」，不解决「画面里有没有敏感信息」。别把它当成一个更宽的承诺。&lt;/p&gt;
&lt;h2 id=&#34;四可拦截的自动化人和-agent-操作同一个实例&#34;&gt;四、可拦截的自动化：人和 agent 操作同一个实例&lt;/h2&gt;
&lt;p&gt;Neko 的官网在功能列表里有一行「Automated Browser」，写的是：装 Playwright 或 Puppeteer 来自动化任务，&lt;strong&gt;同时能够实时介入&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这一行值得展开，因为它对应的是 agent 出问题时最省事的一种兜底形态。&lt;/p&gt;
&lt;p&gt;常见的兜底做法是：agent 跑在无头浏览器里，遇到需要人的步骤就把状态导出，交给一个真浏览器让用户操作，用户做完再把会话导回来。这个流程里有两个进程、两次状态交接，每一步都可能丢东西（会话、页面状态、DOM 与视觉的不一致）。&lt;/p&gt;
&lt;p&gt;Neko 的形态把这件事消掉了：&lt;strong&gt;Playwright 跑在容器里，人看着同一条流，随时可以接管。&lt;/strong&gt; 人和 agent 操作的是同一个浏览器实例，不存在「交接」，因为从来就没有分开过。用户接手时页面已经在正确的状态上，用户做完 agent 继续，中间没有同步动作。&lt;/p&gt;
&lt;p&gt;对一个要求「agent 出错时人能马上接管」的产品，这个架构省掉的是整整一层状态同步逻辑。&lt;/p&gt;
&lt;h2 id=&#34;五代价单位成本与-codec-的隐藏复杂度&#34;&gt;五、代价：单位成本与 codec 的隐藏复杂度&lt;/h2&gt;
&lt;p&gt;Neko 的代价也很清楚，而且我认为比多数介绍里说的更实在。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;单位成本降不下来。&lt;/strong&gt; 每个实例里跑的是一个真浏览器加一个虚拟 X 桌面，再加视频编码。这套东西的内存占用不可能接近无头方案。所以它的合理位置是「需要人的那一段」，把它当成全流程方案会贵得没必要。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;codec 是隐藏的复杂度。&lt;/strong&gt; 从源码结构看，&lt;code&gt;pkg/types/codec/codecs.go&lt;/code&gt; 加整条 capture 管线是项目最重的一块，这不是偶然。WebRTC 串流要在画质、延迟、CPU 三者之间取舍，而这三者的最优解取决于你的场景（交互式操作要低延迟，看片要画质）。这块调优是运维层的主要工作，但在多数介绍里被一句「用 WebRTC」带过了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;镜像矩阵本身也是个决策。&lt;/strong&gt; 它的镜像命名规则是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ghcr.io/m1k1o/neko/[&amp;lt;flavor&amp;gt;-]&amp;lt;application&amp;gt;:&amp;lt;version&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;&amp;lt;application&amp;gt;&lt;/code&gt; 是浏览器或基础镜像，&lt;code&gt;&amp;lt;flavor&amp;gt;&lt;/code&gt; 是可选变体，版本走语义化（&lt;code&gt;latest&lt;/code&gt;、&lt;code&gt;MAJOR&lt;/code&gt;、&lt;code&gt;MAJOR.MINOR&lt;/code&gt;、完整版本号）。官方提供两个注册表：GHCR 有全部 flavor 和多架构的稳定版，Docker Hub 只有不带 flavor 的 AMD64 开发版。&lt;/p&gt;
&lt;p&gt;这里有一条容易被忽略的文档提示：&lt;strong&gt;Firefox 系的浏览器不需要额外的 capabilities 或更大的共享内存就能稳定运行，Chromium 系的需要。&lt;/strong&gt; 这不是小事，它直接决定你的容器配置和稳定性。官方把 &lt;code&gt;m1k1o/neko:latest&lt;/code&gt; 保留为 &lt;code&gt;firefox&lt;/code&gt; 的别名，多半也是因为这个默认更稳。&lt;/p&gt;
&lt;h2 id=&#34;六一个端到端的登录场景&#34;&gt;六、一个端到端的登录场景&lt;/h2&gt;
&lt;p&gt;把上面几节拼成一个具体流程，会更清楚它的位置。假设 agent 要替用户去一个需要登录的后台取数据。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;agent 先在便宜的那一层把能做的做完，跑到「需要登录」这一步停下。&lt;/li&gt;
&lt;li&gt;起一个 Neko 房间，里面是一个真浏览器和真桌面。&lt;/li&gt;
&lt;li&gt;让浏览器停在登录页，把房间链接给用户。&lt;/li&gt;
&lt;li&gt;用户在自己浏览器里看到的是画面，在画面上输入账号密码。凭据只存在于服务器上的那个浏览器里。&lt;/li&gt;
&lt;li&gt;用户说一声好了，agent 从&lt;strong&gt;同一个实例&lt;/strong&gt;继续，此时页面已经在正确的状态上。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个流程里最值得看的是第 3 步和第 5 步。如果实现方式是在无头层和真浏览器之间来回切换，那么第 3 步要复现页面状态、第 5 步要把会话搬回去，中间每一步都可能丢东西（cookie、已填的表单、DOM 与视觉的不一致）。而在「一开始就用真浏览器」的形态里，这两步都不存在，因为浏览器从来没有换过。&lt;/p&gt;
&lt;p&gt;代价是你为整个过程付了真浏览器的钱，包括那几步本来不需要人的。所以这里有个真实的取舍：&lt;strong&gt;要么付全价换掉交接逻辑，要么省成本但自己把交接写对。&lt;/strong&gt; 没有第三种。&lt;/p&gt;
&lt;h2 id=&#34;七接入自己的-agent-时要自己决定的三件事&#34;&gt;七、接入自己的 agent 时要自己决定的三件事&lt;/h2&gt;
&lt;p&gt;Neko 自己不是一个 agent 工具。它提供的是一个「可以被驱动的浏览器加一条流」，剩下的要你决定：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，谁驱动浏览器。&lt;/strong&gt; 官方给的路是在容器里装 Playwright 或 Puppeteer。这意味着 agent 的驱动逻辑要跑在容器内部，而不是从外面连进去。至于容器内浏览器的 CDP 端口能不能暴露给外部客户端，我在官方文档里没有找到明确说明，这一点要实测确认。如果只能内部驱动，你的 agent 框架就要有「把代码送进容器执行」的能力，而不是「连一个远程浏览器端点」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，流给谁看。&lt;/strong&gt; 房间是多人共享的，所以「用户看」和「审计看」可以是同一条流。需要留证的时候，接一个录制就同时满足了两边，不需要额外做一遍截图或录屏。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，接管期间 agent 干什么。&lt;/strong&gt; 官方说「可实时介入」，但接管时 agent 是暂停、还是继续操作、还是排队等待，这是产品语义，得自己定义。这块我没有在文档里找到现成的实现，属于「架构允许、但要自己搭」的部分。&lt;/p&gt;
&lt;p&gt;这三点合起来说明一件事：Neko 是一个很好的底座，但把它变成 agent 的人机协作界面，中间还有一层要自己写。&lt;/p&gt;
&lt;h2 id=&#34;八它该放在流水线的哪一段&#34;&gt;八、它该放在流水线的哪一段&lt;/h2&gt;
&lt;p&gt;把这些拼起来，我的判断是 Neko 的位置很明确：&lt;strong&gt;它不是 agent 的浏览器，它是 agent 需要人时的那个界面。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一次典型的 agent 任务里，绝大部分步骤不需要人：抓页面、点元素、填表单、提取数据。这些交给无头方案更便宜（同一批文档里，从零写的无头浏览器可以把峰值内存压到无头 Chrome 的十六分之一）。真正需要人的只有几个节点：登录、双因素、验证码、支付确认、以及 agent 判断不了的分叉。&lt;/p&gt;
&lt;p&gt;Neko 覆盖的就是这几个节点，而且覆盖得比别的方式干净：用户看到的是真浏览器画面，凭据不出服务器，agent 和人共用同一个实例所以不需要交接。&lt;/p&gt;
&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&lt;/h2&gt;
&lt;p&gt;我读完 Neko 架构的主要收获是，它的价值不在「远程浏览器」这个功能描述里，而在那个抽象选择上：把桌面降维成媒体流。这个选择让它同时能做看片派对、远程办公和 agent 的人在环，因为这三件事在底层都是「把画面推给人、把输入收回来」。&lt;/p&gt;
&lt;p&gt;另一个收获是它的信任模型比它的定位更值钱。「只出像素」在官方口径里是防抄袭，但对 agent 场景，它恰好解决了「用户凭据怎么不经过第三方」这个一直很难处理的问题。&lt;/p&gt;
&lt;p&gt;最后交代证据边界：以上来自 Neko 的官方文档、架构说明和仓库文件结构，&lt;strong&gt;我没有实机跑过&lt;/strong&gt;。要验证最小的一组是：起一个 &lt;code&gt;ghcr.io/m1k1o/neko/firefox:latest&lt;/code&gt; 容器，用两个浏览器标签页连进同一个房间，确认两边看到的是同一个画面；然后在容器里装 Playwright 跑一个多步任务，中途从另一个标签页接管，看接管时页面状态有没有丢。&lt;/p&gt;
&lt;p&gt;相关阅读：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.yesmiracle.net/post/20261024-lightpanda-headless-browser-teardown/&#34; &gt;《Lightpanda 源码级拆解：无头 Chrome 对 agent 贵在哪》&lt;/a&gt;：姊妹篇。那篇讲的是把浏览器压到最轻、砍掉一切与人有关的部分；这篇讲的是反过来，把浏览器整个变成一条给人看的流。两篇合起来是 agent 浏览器栈的两端。&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.yesmiracle.net/post/20260910-browser-agent-trio-comparison/&#34; &gt;《browser-use 114k⭐ 称王！浏览器 Agent 三强争霸》&lt;/a&gt;：那篇比的是「谁在驱动浏览器」，Neko 不在那个坐标系里，它是被驱动的那一层。&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.yesmiracle.net/post/20260721-ai-agent-attack-surface-panorama/&#34; &gt;《AI Agent 攻击面全景：从 Prompt 到内核的四层防御》&lt;/a&gt;：Neko 这种「凭据不出服务器」的形态，是那篇里「凭据层」问题的另一种解法。&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
