内置浏览器
VMark 可以在文档窗口内部托管一个真正的网页浏览器——网页会成为与你的 Markdown 文档并列的一等标签页。它是货真价实的原生 webview(macOS WKWebView),既不是外部的 Chrome 窗口,也不是嵌入式的 frame。
实验性功能
内置浏览器是一项早期功能,在本版本中仅支持 macOS。Windows 和 Linux 的支持将在之后加入——在这些平台上,下文提到的设置项根本不会出现。
工作区导轨
启用实验性的工作区导轨后,浏览器页面是窗口级全局的:它们在窗口内的每个工作区中都可以访问,永远不会绑定到某个工作区的标签页上。
关闭它
在 macOS 上,浏览器默认开启。新建浏览器标签页位于文件菜单中 (Alt + Mod + Shift + B),也可在命令面板中找到——无需先启用任何东西。
要关闭它,请前往设置 → 高级 → macOS,关闭内置浏览器。这同时会关闭所有已打开的 浏览器标签页,并撤下下文所述的 AI 自动化接口。
两项 AI 姿态设置紧挨在该开关下方,且仅在其开启时才出现。两者的出厂设置都很保守,并且 不会因浏览器被启用而改变:
| 设置项 | 默认值 | 含义 |
|---|---|---|
| AI 会话 | 沙盒 | AI 驱动的页面获得独立会话,而不是共用你已登录的会话 |
| 允许回环地址 | 关闭 | AI 导航到 localhost / 私有网络地址会被拒绝 |
站点权限不在设置里——它们位于浏览器侧栏中,在拥有它们的那个窗口内。
使用它
浏览器标签页在编辑区打开,与你的文档并列——侧栏、标签栏、终端和状态栏都保持原位。它的控件位于页面上方:在 macOS 上,它们共用窗口的标题栏,因为标题栏是 VMark 自己绘制的。在由系统绘制标题栏的平台上(Windows、Linux),它们位于窗口内、页面上方,就像其他所有桌面浏览器的布置方式一样。
| 控件 | 操作 |
|---|---|
| ‹ / › | 后退 / 前进。无处可去时置灰 |
| ⟳ / ✕ | 重新加载,或停止正在进行的加载 |
| 地址栏 | 一个多功能框(omnibox):输入 URL 前往该地址,输入其他内容则进行搜索 |
| ☆ / ★ | 收藏此页面 |
地址栏会自动跟踪页面:如果站点发生重定向,或某个链接把你带到别处,地址栏会更新,显示你实际所在的位置。
侧栏跟随标签页
当浏览器标签页处于活动状态时,侧栏会显示浏览历史和书签。当你切回文档时,它会再次显示文件浏览器、大纲和文件历史——这一切都是自动的。没有第二种需要手动保持同步的模式,而且两侧都会记住你上次打开的内容,所以瞥一眼浏览器标签页不会让你丢失正在使用的文件树。
历史记录是按窗口划分的,且只在当前会话内存在:它绝不会写入磁盘。(仍然有一个清除按钮——"退出时它会消失"和"你现在就能清掉它"并不是一回事。)重新加载不会添加重复条目,而将你重定向的站点记录的是你想要访问的页面,而不是沿途的每一跳。
书签则会持久保存。它们以你收藏时的确切 URL 存储——同一页面的不同小节(#install 与 #usage)算作两个书签,而 VMark 不会悄悄"整理"URL 的查询参数,因为被改写过的 URL 未必能带你回到你所看到的内容。
窗口在页面周围转为中性色
VMark 的主题是刻意带有色调的——Paper 是暖灰色,Mint 和 Sepia 则更甚。这用来写作很惬意,却不适合去包裹别人的网页:带颜色的边框会改变你对其中每一种颜色的观感,这正是没有一款真正的浏览器会给自己的界面外框上色的原因。
所以当浏览器标签页获得焦点时,周围的窗口会切换为素净的中性色——在浅色主题下为白色,在深色主题下为深色——并在你回到文档的那一刻切换回来。你的主题没有改变;改变的只是网页周围的部分。
终端遵循同样的规则。 如果你在浏览器标签页旁边开着终端,它会采用与之匹配的中性色,而不是保持你主题的颜色,这样窗口的两半就能协调一致,而不是在一条可见的接缝处相接。深色主题得到的是深色终端,而不是白色的——终端里的颜色是针对其背景调校的,强行改成白色会让深色主题的输出难以阅读。
如果页面崩溃
如果某个页面的网页内容进程终止,标签页会显示一个带有重新加载按钮的**"此页面已崩溃"**覆盖层,而不是空白或冻结的视图。对于短暂的崩溃,VMark 会自动重新加载几次;如果某个页面在加载时反复崩溃,它会停下来等你手动重新加载,这样你就永远不会卡在重新加载的循环里。
它是如何构建的(以及为什么它在设计上就注重隐私)
VMark 自行创建平台 webview,并将其作为窗口的原生子级添加进来——它不会向应用框架索要一个。这对隐私很重要:由框架创建的 webview 会向每个页面注入一个内部消息桥,等于把通往应用的通道交给了任何站点。因为 VMark 拥有的是一个全新构造、没有这种桥的 webview,被浏览的页面没有通往 VMark 的通道。页面是被严格单向驱动的(应用可以读取页面并对其操作;页面无法反向触及应用)。
会话(登录状态、cookie)按配置文件持久保存在操作系统 webview 自己的数据存储中,所以每个站点你只需登录一次。VMark 自身不存储任何凭据。
用 AI 驱动浏览器
通过 MCP 连接的 AI 助手可以操作浏览器标签页:
- 读取(Read)——获取页面的结构化无障碍快照(每个可交互或结构性元素以角色 + 可访问名称表示,外加一个稳定的**引用(ref)**句柄,如
e5)。 - 操作(Act)——点击或输入某个目标,可以依据先前读取得到的精确引用(ref),也可以依据 ARIA 的角色 + 可访问名称(例如,点击名为"Learn more"的链接)。ref 只在操作已获授权时才被采信;任何需要你批准的操作都使用角色 + 名称,这样提示才能向你展示一个可读的元素。点击会核实它确实命中了:它会把目标滚动到可见范围内,要求目标已可见渲染——折叠区块内的重复按钮会被跳过,而不会被点击——并对点击点做命中测试,因此被覆盖层遮挡的目标会以"被……遮挡"的形式回报,而不会被穿透点击。AI 被告知的是实际发生了什么,而不仅仅是它尝试过,所以它无法悄悄地对错误的东西下手却回报成功。
- 滚动(Scroll)——将某个元素(依据 ref)滚动到可见范围内,或按像素量滚动。属于操作类(与点击一样受批准约束)。
- 按键(Key)——向获得焦点的元素或某个 ref 发送一次按键(
Enter、Escape、Tab、方向键,可选配 Ctrl/Shift/Alt/Meta)——例如提交表单或关闭对话框。属于操作类。注意:按键和滚动都是合成的 DOM 事件,因此只信任真实硬件输入的站点可能会忽略它们。 - 查询(Query)——通过 CSS 选择器进行结构化 DOM 探测,用于无障碍快照无法命名的内容(表格、计算值、属性)。属于读取类。
- 提取(Extract)——将页面转为阅读模式的 Markdown(标题、署名、正文散文,去除样板内容),用于 AI 想要阅读而非操作的页面。站点插件会按来源精细化提取——内置的 Wikipedia 插件会按名称剥除维基界面框架——并以一个通用阅读器作为后备。页面只导出字节;提取过程在 VMark 内运行。属于读取类。
- 样式(Style)——CSS 操作(关闭阻挡视线的覆盖层、高亮某个目标),方式是设置内联样式、切换类,或注入一个
<style>块(作用于整页,而非局限于选择器)。属于操作类,且批准会绑定确切的样式——在你允许之后,它不能被换成其他 CSS。 - 执行 JS(Execute JS)——最后的应急手段:运行脚本以完成结构化动词无法表达的事情。它运行在隔离的内容世界中(DOM + CSS,绝不触及页面自己的 JavaScript),逐次批准(从不记忆——它没有"在此站点上允许"这一项),且其结果被视为不可信。批准提示会向你展示确切的脚本,而运行的正是那段脚本——AI 无法诱使你批准一段脚本却运行另一段。请优先使用查询 / 样式;只有在它们力有不逮时才动用这一项。
- 会话保存 / 加载(Session save / load)——将标签页的当前会话保存在一个句柄(handle)下(一个由你批准的名称),之后再恢复它,让某个流程一开始就已处于登录状态——而 AI 从头到尾都看不到你的 cookie 或令牌。这些值存储在操作系统钥匙串(keychain)中(静态加密),AI 只收到句柄和一份计数摘要。保存和加载都逐次批准,而对某个句柄的批准不能用到另一个句柄上。恢复只对与其保存时同源的页面生效。这是按引用传递凭据:AI 说出会话的名字,秘密由 VMark 掌握。
- 控制台(Console)——读取页面捕获的
console.*输出(log/warn/error……),外加未捕获的错误和未处理的 promise 拒绝——这是页面在自身脚本出错时发出的信号,普通的console日志从不会显示它——从而让 AI 能够调试它正在驱动的页面。只读,且输出被视为不可信的页面数据。这一点是为保住"设计上注重隐私"的保证而建的:捕获会写入页面自己的 DOM,VMark 从那里读取,因此不会有消息通道被反向打通回应用。
会话保存/加载——作用范围
一个已保存的会话涵盖 localStorage 和 cookie,两者都被限定在你保存时页面所提交到的 来源。cookie 通过原生 cookie 存储读取和回放,并且是双向按域限定的——保存永远不会复制 你整个 cookie 罐,恢复也永远不会把 cookie 种到某个无关的站点下。
- 打开(Open)——创建一个 AI 拥有的标签页并加载一个 HTTP(S) URL。
- 导航(Navigate)——对一个 AI 拥有的标签页进行导航,并等待它的导航凭据。当加载出来的页面读起来像是一道关卡、而非所请求的内容时——登录墙、同意插页、人机验证挑战(reCAPTCHA/Turnstile),或限流提示——结果会如实说明,并且 AI 被要求把你牵涉进来,而不是试图绕过它。检测以精确为先:提到"$429"的价格,或写着"Cloudflare"的页脚,都不会误触它。
- 等待(Wait)——等待某个特定的导航凭据,而不发起另一次加载。
- 等待条件(Wait for)——轮询直到某个条件成立(依据 ref 或角色 + 名称的元素、一段可见文本,或标签页的 URL 包含某个子串——最后一种可确认由点击触发的导航已经完成),或直到超时,并报告是否匹配。它让多步骤流程变得确定——操作,然后等待结果,再读取——而不是靠猜。
- 截图(Screenshot)——获取页面当前渲染的一张 JPEG 图片,让 AI 能看到无障碍快照未曾命名的布局和渲染状态。和读取一样,它不会改动页面:在 AI 拥有的标签页上允许使用,而在人类标签页上只有当你已附加它时才允许。
- 运行工作流(Run a workflow)——将一段简短的、已保存的步骤序列(点击 / 输入 / 导航 / 提取,以一种小型文本语法编写,并作为
source传入)作为一次异步运行来回放:它会立即返回一个运行 id,然后由你轮询其状态,因为多步骤的运行会比单个请求活得更久。其中的每一步都像手动下达的操作一样逐一受批准约束——工作流不是绕过提示的手段——而那些 AI 无法确定性执行的步骤(自由散文式的"目标"、一次"确认")会暂停运行,交由它手动处理。重新运行会跳过已经成功的步骤,所以在暂停之后重新运行绝不会重复提交。运行是有界的,每个标签页一次只有一个,并且可以取消——取消始终被允许,而你自己接管浏览器也会停止运行。 - 录制工作流(Record a workflow)——你不必手动编写这套语法,而是可以录制一个:在你批准的前提下(每次都会重新征询——录制从不是长期权限),VMark 会捕获你在标签页上执行的点击和字段编辑,并交回可直接运行的工作流文本。它在构造上就不含任何值:你键入的任何内容都不会被保存——每个字段都会变成一个具名的
{input},由你在回放时填入,密码字段会变成一道手动的confirm:步骤,而 URL 会被剥离到仅剩来源 + 路径。它记录的是你触碰了哪些控件,而绝非你输入了什么。
AI 浏览器姿态在设置 → 高级 → 内置浏览器下配置:
- 沙盒(Sandbox)(推荐)使用一个共享的、非持久的 AI webview 存储。它与其他沙盒标签页 共享 cookie,但不与人类标签页共享。
- **共享配置文件(Shared profile)**使用人类的 webview 存储,并在每次 AI 导航前请求对目标 地址的批准,除非该来源已有匹配的
navigate授权。
AI 创建的标签页是临时的,重启后不会恢复。它们的 URL、模式、标题、代数和加载状态会出现在 session.get_state 中;凭据会从 MCP 响应中被抹除。
操作受批准约束:你未授权的操作不会被执行——AI 会被告知需要批准并等待。文件上传对 AI 绝不允许(由 AI 选择的文件上传会成为一条数据外泄路径);那些操作严格由人类驱动。
批准一次操作
当 AI 请求操作时,VMark 会弹出一个提示并暂停页面。它准确地告诉你三件事——站点、操作,以及元素(它的角色和可访问名称,例如 button "Publish"):
- 允许一次(Allow once)——恰好授权那一个操作,作用于那个元素、那个页面。它会被立即用掉,不会变成长期权限。
- 在此站点上允许(Allow on this site)——AI 可以在那个站点上执行那项操作而无需再次询问。它不会扩大到其他操作或其他站点。
- 拒绝(Deny)——什么都不会发生。按
Escape,或者干脆按Enter,同样是拒绝:这个提示被刻意设计得倾向于拒绝。
提示向你展示的是对操作的描述,而不是页面的图片——这是有意为之。网页掌控着自己的像素,所以一个恶意页面可以把"Delete everything"按钮的样式做得看起来像"Publish"。VMark 向你展示的,正是安全门所强制执行的那个确切对象,它取自浏览器引擎,而不是取自页面对自身的说辞。
权限还会在页面导航时失效。一个提示描述的是对某个特定页面的操作;如果在你做决定的过程中页面发生了变化,请求会被丢弃,而不会被套用到取而代之加载的任何内容上。一个尚未用掉的"允许一次"也会以同样的方式被作废。
这也包括页面内部的导航。大多数现代站点在各视图之间切换时从不加载新页面——地址变了,内容被改写了,但站点从未离开。这在此处很重要,因为站点和来源保持不变,而你批准的那个 button "Publish" 可能已经不再是叫那个名字的按钮了。所以 VMark 对页面内导航一视同仁:授权会随它所针对的那个视图一同失效,而不仅仅是随页面失效。
不过,真正承担分量的是描述符本身。一个站点可以在任何时刻改写自己的内容而根本不进行导航,且没有任何浏览器引擎会报告这一点。所以一个"允许一次"所授权的,恰恰是一项操作,作用于由角色和可访问名称标识的一个元素,在一个站点上——并且它会被立即用掉。真正需要三思的是"在此站点上允许":它是对那个站点上那项操作的长期权限,而你授予它的站点,就是你在这件事上信任的站点。
查看和撤销权限
设置 → 高级 → 站点权限会列出你已授权的每个站点,以及它可以做什么。撤销会立即收回权限——那个站点上的下一次 AI 操作会重新询问。
站点权限只保存在内存中:它们绝不会写入磁盘,并在 VMark 退出时失效。让 AI 跨重启保有在某个站点上点击的能力,是一个比看上去更重的承诺,所以 VMark 不会悄无声息地做出这个承诺。
当 AI 以人类创建的标签页为目标时,VMark 会先询问是否要把 AI 访问附加到该标签页上。这次附加会绑定到当前的导航代数。允许一次在一次成功的读取或操作后即被用掉;允许直到导航为止会在下一次完整导航或页面内导航、关闭、禁用或重启时失效。
AI 导航默认拒绝回环地址、私有局域网、链路本地、元数据、格式错误以及不受支持协议的目标。DNS 重绑定仍是 WebKit 自身的一项局限;VMark 并不声称能消除它。
协同驾驶:从终端里看着 AI 驱动浏览器
浏览器是一个窗格,而不是一种模式。这让一种特定的工作流成为可能:在浏览器标签页旁边打开一个终端(`Ctrl + ``),在其中运行一个 AI 智能体,看着页面随它的工作而响应。
终端和浏览器并排摆放——浏览器会调整大小来腾出空间,而不是被遮住。所以在智能体操作页面的整个过程中你都能看到它,而它采取的每一个操作仍然都得经过你这一关(见上文批准一次操作)。
这就是 VMark 中 AI 浏览器用法所设想的形态:智能体提议,页面可见,由你批准。它不是智能体在一个你看不见的窗口里干活。
夺回控制权只需一个动作。 当一次 AI 工作流运行正在驱动某个标签页时,它的界面框会显示一个**"AI 正在控制 — 点击接管"**指示器。点击它——或者干脆自己与页面或它的地址栏交互——都会立即收回标签页并停止运行。你永远不必在智能体的终端里去找一个停止按钮;触碰浏览器本身就是停止按钮。
当页面加载失败时
网络离线、主机名错误、证书被拒,或连接被拒绝,都会在浏览器窗格中产生一条说明出了什么问题的消息,并附带一个重试按钮。早期版本显示的是空白窗格,那与仅仅是加载缓慢的页面无从区分。
当前限制
- 本版本仅支持 macOS。
- JavaScript 的
confirm()/prompt()对话框暂时被抑制(只有alert()会呈现);弹出窗口(window.open)会被拦截,而不是作为新标签页打开。 - 下载、打印,以及按请求的网络策略尚未实现。
这些正在逐步补齐;上文所述的是目前已能使用的功能。