内置浏览器
智能体整天在给你链接:刚启动的开发服务器、引用的文档页、刚开的 pull request。在桌面应用里,这些页面直接在 Codeg 内部打开,作为文件面板里的标签页挨着你的代码,而不是把你甩到另一个应用去。页面由真正的浏览器引擎渲染——macOS 上和 Safari 同一个 WebKit,Windows 上是 WebView2——有自己的 cookie、自己的历史和自己的地址栏。没有任何东西是模拟的。
也正因为页面在 app 里,它同时是一块你和智能体共用的界面:你可以把整页、页面上的某个元素、一张截图或控制台里的报错递进对话,也可以把某个页面共享给智能体去读、去操作,而它做的每一步都写在那个标签页上,你看得见。
它的设置界面在 0.31.2 搬了家
下文提到的每一项都在 设置 → 浏览器 里 —— 自 0.31.2 起它是一个独立入口。在那之前,它是 设置 → 常规 底部折叠着的一个区块,旧说明会把你指到那儿。
桌面优先
在浏览器会话(codeg-server)里没有可嵌入的浏览器引擎:网页链接都在你正在用的浏览器里开新标签页,这里描述的设置项也不会显示。唯一的例外是跑在 Codeg 主机上的开发服务器——智能体打印出来的 http://localhost:3000——它会经由端口桥作为工作台里的标签页打开。
链接在哪里打开
app 里的每一个 http(s) 链接都经过同一个决定,在你点击的那一刻做出:
- 该主机的站点规则直接胜出(见下文站点规则)。写着阻止的规则会拦下这次点击并给出提示。
- 否则按链接来源的默认设置——对话消息、工具结果、终端、编辑器和通知各有自己的默认值,初始都是内置浏览器。在设置 → 浏览器 → 链接打开位置里逐项修改。
- ⌘ 点击(macOS)或 Ctrl 点击(Windows、Linux)让链接换一种方式打开,只此一次:默认是内置时用系统浏览器,默认是系统浏览器时开内置标签页。
链接上的右键菜单明确给出同样三个选择——在内置浏览器打开、在系统浏览器打开、复制链接——而非网页的链接保持原有行为:文件路径打开文件,mailto: 和 tel: 交给操作系统,未知的 scheme 会被拒绝而不是交给任何程序。
链接第一次在内置浏览器里打开时,Codeg 会提示一次,toast 里带一个始终使用系统浏览器按钮,留给更愿意维持原状的人。
终端还多一个选项:终端链接菜单(默认关)让普通点击先在指针处弹出一个小菜单——内置浏览器、系统浏览器、复制链接——而不是立即打开。⌘/Ctrl 点击仍会直接打开链接。
浏览器标签页里有什么
浏览器标签页只有一条小工具栏,其余全是页面:
- 地址栏。输入地址回车即可。裸主机名会补上
https://(回环与内网地址补http://);没有搜索兜底——地址栏就是地址栏。 - 后退、前进、刷新 / 停止、复制链接、在系统浏览器打开。
- 页内查找:⌘F / Ctrl-F——Enter 与 Shift+Enter 前后步进,Escape 关闭。搜索是引擎自己做的,页面既看不到也干扰不了。(没有「第 n / 共 m 个」计数:WebKit 只回答这一步找到了没有。)
- 页面内的链接。⌘/Ctrl 点击在当前标签页旁边开一个后台标签页。你点了什么之后页面开的窗口——OAuth 弹窗、
target="_blank"链接——会成为紧邻其开启者的标签页,并像在浏览器里一样保留window.opener与 referrer。没人要的弹窗(定时器、加载时的脚本)会被拦下并在工具栏下方的条里说明,带一个仍然打开按钮,把该地址作为普通标签页打开。自 0.31.1 起,办完事自己关闭的弹窗——Google 登录就是这么收尾的——会把自己的标签页一起带走,不再留下一个空的 Sign In 标签页。只有页面自己开出来的标签页才能这样自我关闭,普通标签页不行。 - 空白标签页是我们的,不是引擎的。自 0.31.1 起,空的浏览器标签页用应用自己的配色绘制——深色模式不再开出一张白纸,切换主题会把已经开着的空白标签页一起重绘,应用内的 toast 也能正常显示在它上面。
- 下载。页面提供的文件会以消毒后的文件名落到你的下载文件夹;已有文件绝不会被覆盖(你会得到
名字 (1).扩展名),也绝不会自动打开任何东西。下载条只提供在文件夹中显示。 - 打不开的页面。找不到主机、证书无效、服务器拒绝连接——各有自己的文案,措辞来自引擎,在引擎放弃的那一刻出现,带重试和在系统浏览器中打开按钮。被站点规则阻止的页面显示阻止页,只有重试(并指明规则所在)。
- 被拒的跳转。页面自己想去标签页不允许去的地方——
mailto:、自定义 scheme、被阻止的主机——页面停在原地,工具栏下方的条说明原因。对mailto:和tel:,条上提供用系统应用打开;其它 scheme 保持拒绝。
菜单、对话框和抽屉打开在页面之上:弹层打开期间,页面显示它的最后一帧,弹层一关即恢复实时。
有三样浏览器有、而这个首发版本还没有的东西:网站图标、文件下载过程中的进度(下载只在完成时汇报),以及在你切过去之前预加载标签页。
终端里起的服务
在 Codeg 终端里启动一个开发服务器——或者让智能体去启动——Codeg 会读它打印出来的地址。设置 → 浏览器 → 本机服务决定接下来发生什么:
- 提示我(默认)——一条 toast 写着有服务正在 … 运行,带一个打开按钮。
- 打开标签页——页面在后台标签页里打开,你正在看的东西继续占着这一栏。
- 什么都不做——完全不打扰。
只有本机地址算数,而且要等到那个地址上真的有东西在监听:有些框架在端口绑定之前就把地址打印出来,那样开出来的标签页只会加载失败。
无论这个设置怎么选,标签条末尾的 + 都会列出此刻正在运行的本机服务,旁边是开空白页的浏览器标签页——所以被你顺手关掉的、或者在你想起它之前就起好的服务,永远只隔着一个菜单。
HTML 文件:文档视图
在文件面板里打开的 .html 文件——智能体写的报告、生成的图表、静态站点的 index.html——同样由内置浏览器显示,以文档视图直接从文件所在文件夹提供内容,而不经过任何 web 服务器。它是一个有真实地址的真实页面,所以相对链接、图片、样式表以及页面之间的互相链接都能用;它不被允许的,是离开那个文件夹。
- 默认安全模式。文档以无脚本、无网络的方式渲染:只有它自己文件夹里的标记、样式、图片和字体,别无其它。这和内联预览一直以来的姿态相同。
- 启用脚本(视图头部的按钮)把这一个文件切换到动态模式,本次会话内有效:它的脚本可以运行,并能读取文件夹里的其它文件(
fetch('data.json')可用)。它们依然不能访问网络、不能开窗口、不能下载任何东西。这个文件夹是文件所在的工作区文件夹,所以在工作区内../shared/app.js也能用;不在任何工作区文件夹里的文件被限制在它自己的目录中。 - 你批准的才会运行。动态模式会记住你启用它的时刻。之后只要文档用到的任何文件发生变化——智能体重写了脚本、构建替换了资源——视图就会自行退回安全模式、重新加载,并说明是哪个文件变了,附一个再次启用按钮。批准之后才改动的内容,绝不会在脚本开启的状态下被提供。
- 指向外部的链接。指向网页的链接不会在视图内跟随;一条提示条提供打开链接,按 app 里任何链接的打开方式打开(你的默认设置与站点规则都适用)。
mailto:和tel:得到用系统应用打开。文档里无法下载。 - 编辑。视图显示的是已保存的文件;编辑器里未保存的修改在保存之前不会反映,已保存的文件会自动重新加载。
- 内联预览还在。视图的 ⋯ 菜单里有针对这个文件的使用内联预览;设置 → 浏览器 → HTML 文件预览可以对所有文件关闭文档视图(网页版始终使用内联预览)。
文档视图在 macOS 与 Windows 上可用;Linux 在其嵌入面验证之前继续使用内联预览。文档视图永远不会共享给智能体:它根本不会被列给智能体,也无法被授权,无论平台给了它什么样的地址。
把页面递给对话
浏览器标签页同时也是一条通往对话的路。地址栏右侧的发送到对话控件提供四样东西:
- 把这个页面发到对话——你正在看的东西,连同地址和标题。
- 点选一个元素…——指针变成拾取器;在页面上点中什么,递过去的就是那个元素。Escape 停止拾取。
- 发送一张截图——标签页此刻显示的画面。
- 发送控制台报错——页面打印了什么、什么出了错。什么都没出错时它会直说,而不是塞给你一个空列表。
每一样都会落进你当前打开的那个对话的输入框,成为一枚胶囊——页面元素、页面截图、3 条控制台报错——发送之前你仍然可以把它取下。没有打开的对话时,控件会说明它缺的是什么。
让智能体在页面上干活
反方向:智能体可以列出你打开的标签页、读取你已共享页面的结构、在上面动手——点击、悬停、输入、按键、选择选项——截图、读取页面打印到控制台的内容,以及打开、跳转和关闭标签页。
有两件事必须先成立。其中一件从关闭开始,另一件不是:
- 这个工具组对每个智能体默认关闭,直到你打开它。****设置 → 协作 → 会话内工具 → 读取并操作内置浏览器。与那张卡片里的多数开关不同,它在工具被调用的那一刻重新读取,而不是只在智能体启动时读一次——所以关掉它会拦住正在跑的会话,而不只是下一个。
- 页面必须已被共享——而开箱即用的情况下它会自己共享自己。每个标签页在地址栏左侧都有一个控件(只读、可读可操作,或者什么都不给),但默认共享级别这项设置的出厂值就是可读可操作,它会在页面加载时把标签页所到的每一个新站点授权出去,不会过问任何人。已共享的标签页会在地址栏上挂一枚徽标——已共享,或已共享 · 可操作——标签条上也有相应标记,一按即可收回。
所以真正的闸门是第 1 步的那个开关:在智能体拿到这个工具组之前,这一切不会交给任何人。
这一条值得想清楚
一旦某个智能体拿到了工具组,且常驻级别不是不共享,页面会在加载时被共享——包括它用 browser_open_tab 自己打开的页面——凡是那个标签页还没被你另行交代过的站点都算。如果你更希望每一次共享都是你在标签页里做出的决定,请把默认共享级别设为不共享。
共享覆盖什么,何时结束
一份授权属于一个标签页和一个 origin——也就是授权当时页面所在的那个站点。离开那个 origin 授权即结束;接下来会发生什么,取决于常驻级别:
- 设了常驻级别时(出厂默认),下一个 origin 会在页面加载时被授权,不会打断你——每次跨站点击都弹一条警告条只会变成噪音,而工具栏徽标本来就写着当前页面的共享级别。但标签页会按站点记住你做过的决定:你在某个站点上停掉的共享,这个标签页再回到那里时依然是未共享;你收窄到只读的站点,回来也还是只读。常驻级别只替那些这个标签页还没有任何决定的站点说话——包括它没去过的站点,以及它在常驻级别为不共享时到过的站点(那种情况刻意不记录,好让你之后把级别打开时能够到眼前这个已经开着的页面)。所以一次 SSO 跳出再跳回,洗不掉你已经结束的共享。这份记忆与标签页同寿:重启后恢复的标签页不带任何记忆,常驻级别会重新对它生效。缺口在另一头:如果标签页落到了没有任何授权能绑定的地方,共享就此结束,没有东西接上,也没有东西告诉你。
- 选了不共享时,离开 origin 会结束共享并由一条提示条说明,同时提供改为共享新地址的按钮。
localhost 地址上的共享还可能因为那个端口被另一个程序接走而结束——但仅限于 Codeg 在你共享时能识别出当时的服务程序。识别不出来的授权不会被复查,行为与没有这项功能时完全一样。
它做了什么你看得见
每一次读取、每一个动作、每一次被拒,都写在事情发生的那个标签页上——地址栏里的智能体在这里做了什么,旁边带计数。被拒的那些才是关键:智能体在你只给了只读的页面上尝试点击,会留下一行「已拒绝」,于是「共享给窄了」和「智能体什么也没干」看起来不一样。
有两件事不会留下记录,原因是同一个——没有标签页可以承载它,或者它压根不发生在某个页面上。关闭标签页是以「它消失了」来宣告成功的,所以只有被拒绝的关闭才会被记下;而列出你打开了哪些标签页,不是对任何一个页面做的动作。
智能体实际拿到什么
| 需要 | 工具 |
|---|---|
| 除工具组外无需条件 | browser_list_tabs、browser_open_tab——此时还没有标签页可以承载授权 |
| 标签页已共享 | browser_snapshot(页面的无障碍树,每个元素带一个 ref)、browser_console_messages、browser_screenshot |
| 标签页共享为可操作 | browser_click、browser_hover、browser_type、browser_press_key、browser_select_option——以及 browser_navigate、browser_close_tab,但标签页里没有需要保护的页面时(空白页,或显示着错误的页面)除外 |
| 共享为可操作,外加它自己的开关 | browser_eval——也是唯一一个你可以要求每段都摆给你看的工具 |
browser_list_tabs 给智能体的是每个标签页的 origin——带端口的 scheme://host,不含路径与查询串——以及它是否已共享,但在共享之前不给标题:标题是页面自己挑的文字,没共享的页面不该有机会自述;地址则照给,因为你要做共享决定就得看见它。被拒返回给智能体的是一个答案而不是错误,所以你没共享的标签页只会结束这次工具调用,不会结束整轮对话。
让它运行自己的代码
在内置浏览器中运行代码是另一个开关,默认关闭,而且没有上面那个开关它形同虚设。它同样要求页面共享为可操作,仅仅只读是不够的。
三样都齐了之后,代码就会运行——而你正是在那个开关上决定了这件事。到 0.31.1 之前,每一段代码都会改为先摆到你面前,无条件,而且答案从不被记住:打开过这个开关、又把标签页共享为可读可操作的人,其实早就做完了决定,而第一百个对话框是被条件反射答掉的,不是被读完的。于是这份分量往上挪了——在你做决定的时候问你一次,而不是在你干活的时候问你一百次。开关自己的说明文字现在也这么写。
没挪的是任何一道闸门。工具组、这个开关,以及标签页的可读可操作授权,都在请求被发起之前由后端读取,智能体一个也够不着。而静默运行不等于没人看见:它和其他动作一样,会写进那个标签页的智能体活动里。
想要回从前那种方式,一个设置就够:设置 → 浏览器 → 在页面上运行代码 → 每次都问我。此后每一段代码都会连同它将运行的站点原样摆出来,等你按运行这一次或不要运行——同意这一段绝不等于同意下一段,代码旁边也刻意没有「总是允许」按钮。回答的是拥有该标签页的那个窗口。被拒绝、或者根本没人回答的调用,回到智能体那里都是一次「未获批准」,并且它会被告知不要重试。
仅限桌面:浏览器会话里没有原生标签页,所以这些工具压根不会通告给那里的智能体。
站点规则
设置 → 浏览器 → 站点规则是一张小表,在任何默认设置之前按站点决定:
| 动作 | 含义 |
|---|---|
| 内置浏览器 | 始终在这里打开,无论来源默认是什么(⌘/Ctrl 点击仍可反转)。 |
| 系统浏览器 | 始终交出去,比如拒绝在内嵌视图里登录的站点。 |
| 阻止 | 绝不打开——链接不行,地址栏不行,页面链接或重定向过去不行,iframe 里不行,弹窗也不行。 |
模式可以是主机名(wiki.example.com)、通配后缀(*.example.com——匹配 a.example.com 和 a.b.example.com,不匹配 example.com 本身)或 *,后面可选 :端口(localhost:3000、[::1]:8080)。最具体的匹配生效:精确主机胜过通配,更长的后缀胜过更短的,* 排最后,钉住端口的在平手时胜出——所以这张表不需要排序。
由管理员设定的规则
管理员可以用一个小 JSON 文件为机器上的每位用户钉死规则,也可以整体关闭内置浏览器:
| 平台 | 路径 |
|---|---|
| macOS | /Library/Application Support/codeg/policy.json |
| Windows | %ProgramData%\codeg\policy.json |
| Linux | /etc/codeg/policy.json |
CODEG_POLICY_FILE 可以指向另一个文件。该文件在启动时读取一次:
{
"browser": {
"enabled": true,
"hostRules": [
{ "pattern": "*.internal.example", "action": "block" },
{ "pattern": "sso.example.com", "action": "system" }
]
}
}两个键都是可选的。管理员规则在设置表里带锁显示、不可编辑;它们先于用户自己的规则被查询,所以更具体的用户规则也解除不了管理员的阻止。"enabled": false 时每个链接都走系统浏览器,设置区块会说明原因。格式错误的规则会被跳过并记入日志;格式错误的文件会记入日志并被忽略,而不是悄悄解除或施加限制。
重启后的标签页
浏览器标签页像浏览器的一样能挺过重启。Codeg 按窗口记住每个标签页实际所在的页面和标题;下次启动时它们回到标签条上,淡显且未加载——切到某个标签页时它才开始加载,所以恢复二十个标签页只花二十条小记录,不占内存。⇧⌘T / Ctrl+Shift+T 重新打开刚关闭的浏览器标签页,回到它当时显示的那一页。历史、滚动位置和表单内容不跨重启保留。
还有一个可选的卸载后台标签页开关(默认关):在后台待满三十分钟的浏览器标签页会释放它的引擎,回来时重新加载,期间在标签条上淡显。
配置文件、浏览数据与代理
内置浏览器有自己的配置文件:cookie、缓存和站点存储与 Codeg 自身的数据分开存放,在 macOS 14 及更高版本上还有独立的存储。登录一次站点,重启后依然是登录状态。
配置文件可以不止一个——多一套 cookie 与登录状态,用来在同一站点登第二个账号,或把工作和个人的会话分开。设置 → 浏览器 → 配置文件列出它们:按名字新建、清除某一个的浏览数据(会让你从该配置文件里的站点登出,但不触碰 app 自己的设置)、删除某一个(它的标签页会被关闭、数据被删除),并选择新标签页使用哪个配置文件。标签页的配置文件在它的生命周期内固定不变:一旦存在默认之外的配置文件,标签页工具栏就会显示一枚带配置文件名字的标记,标记的菜单可以把同一页面在另一个配置文件里打开——作为旁边的新标签页。弹窗沿用打开它的标签页的配置文件;从某个标签页 ⌘-点击打开的标签页落在那个标签页的配置文件里;重启后恢复的标签页回到它们原来的配置文件(若那个配置文件已被删除,则回到默认)。默认之外的配置文件需要 macOS 14 及更高版本;更早的 macOS 上,该区块只显示一行浏览数据 → 清除…。
Google 登录兼容(默认开;macOS 与 Windows)让浏览器标签页只对 Google 的登录页面——accounts.google.com 与 accounts.youtube.com,仅此两处——呈现 Firefox 身份。Google 拒绝在它不认识的内嵌浏览器里登录;其他地方保持引擎自己的身份,因为反机器人检查会拒绝与发送方引擎不符的身份。有同样拒绝行为的其他登录提供方可以通过环境变量 CODEG_BROWSER_SIGN_IN_HOSTS 加进来(逗号分隔的主机名列表;子域名也算)。两个限制:只在嵌入式标签页里生效,独立窗口不适用;在 macOS 上,登录页把你跳转到别的站点时——比如登录提供方把你送回正在登录的应用——到那个站点的第一个请求仍带着登录身份,之后的请求都用引擎自己的(Windows 上身份按请求判定,跳转过去的第一个请求就已经是引擎自己的身份)。
浏览器标签页使用设置 → 系统 → 网络代理里配置的代理,与 app 和智能体用的是同一个。macOS 上这需要 14 及以上版本:在那之前浏览器标签页根本无法走代理,而且在配置了代理的情况下,标签页会直接打不开,而不是绕过代理去连网——设置里的那一行就是这么写的。在支持的情况下,开着的标签页在下一次连接时就换过去;Linux 上对之后打开的标签页生效;Windows 上需要重启(WebView2 在进程内第一个浏览器标签页创建时固定代理)。本机地址——localhost、127.0.0.1、::1——永远不走代理,所以无论代理怎么处理,开发服务器都照常可用。支持 http:// 与 socks5:// 代理;不支持 https:// 代理。
同一设置区块里的网页检查器开关默认是开的:它把打开检查器放进标签页的 ⋯ 菜单,并允许页面自己的右键菜单里出现「检查元素」。检查器开在自己的窗口里,而不是停靠进标签页——停靠会占掉半个文件面板、把页面挤到角落里,这对浏览器标签页是笔划不来的买卖。Codeg 是把它注册为偏好、而不是强制,所以你在检查器自己的工具栏上做的停靠或取消停靠会被引擎记住,并从那以后压过 Codeg 的选择。和它下面的标签页承载方式一样,这个开关对此后打开的标签页生效,因为 webview 创建之后就无法再改成可检查。标签页承载方式决定页面如何被承载:除非嵌入标签页在你的系统上表现异常,否则保持自动——独立窗口会把每个标签页开成独立窗口,Linux 用的也是这种方式。
浏览器会话里:端口桥
通过 codeg-server 使用 Codeg 时,链接里的 localhost 指的是服务器的回环地址,你的浏览器到不了。这类链接由服务器出面:它为每个开发服务器多绑定一个端口(取自它自己端口之后的十个端口,或 CODEG_BRIDGE_PORTS),把它转发到该开发服务器,工作台则把页面放在那个端口上的标签页里显示。没有任何改写——页面就像在主机上一样从 / 提供,所以模块导入、客户端路由和热更新都照常工作。
- 只桥接服务器回环地址上的纯
http://地址(localhost、127.0.0.1、[::1]、0.0.0.0)。公网地址仍在新浏览器标签页里打开;私网主机不归服务器转发。 - 标签页工具栏有重新加载、在新标签页打开(同一桥端口上的完整浏览器标签页,给 frame 显示不了的东西用)和复制地址。切走再切回该标签页会重新加载页面。
- 访问按会话计:打开页面时会铸造一个短凭据,标签页用它换取桥端口上的一个 cookie。最后一个标签页关闭一分钟后、或两小时没有请求后,桥端口关闭。没有先从已登录的工作台打开,谁也无法经由桥接触到开发服务器。
- 如果 frame 提示无法访问端口桥,说明端口在服务器上绑定了但没有发布到你的浏览器:开放该端口段(Docker:在
3080之外再发布3081-3090,或CODEG_BRIDGE_PORTS指定的范围),或在反向代理给桥换了主机名时设置CODEG_BRIDGE_PUBLIC_HOST。通过 HTTPS 提供的工作台,桥端口也需要放在 HTTPS 之后。
除非 CODEG_BRIDGE_PUBLIC_HOST 给桥指定了独立的主机名,页面与工作台同处一个主机名。在同处一个主机名的情况下:Codeg 自己的 cookie 里,页面能看到语言 cookie,而绝对看不到你的访问令牌——后者存放在仅限工作台自身 origin 的存储里;而那个主机名上其它的 cookie,页面都读得到,桥也会随它的请求一起转发给开发服务器,Codeg 自己的那些则先被剥掉。同时打开的两个开发服务器也无法经由对方的桥端口互相访问:桥端口只回应浏览器标记为「来自该页面自身」的请求——浏览器发 Fetch Metadata 时(HTTPS,或本机)看它,否则看请求的 Origin 或 Referer。两个后果:纯 HTTP 部署下在地址栏手敲桥地址会被拒(请从 Codeg 打开);页面从别的站点回到开发服务器——比如登录提供方的跳转——同样被拒,需要从 Codeg 重新打开。
各平台的差异
| macOS | Windows | Linux | |
|---|---|---|---|
| 页面承载 | 嵌入工作区 | 嵌入工作区 | 每个标签页一个独立窗口 |
| 失败文案 | 按类型(找不到 / 不安全 / 连不上) | 按类型,域名找不到的除外 | 一种通用文案 |
| 弹层下的最后一帧 | 有 | 有 | 不适用 |
| 页内查找 | 有 | 有 | 尚无 |
| HTML 文档视图 | 有 | 有 | 尚无(内联预览) |
| 默认之外的配置文件 | macOS 14 及更高版本 | 有 | 有 |
| Google 登录身份 | 有(嵌入式标签页) | 有(嵌入式标签页) | 尚无 |
| 代理变更生效 | 开着的标签页 | 重启后 | 新标签页 |
把页面共享给智能体、把页面递给对话,以及本机服务菜单,三端行为一致。