首页
/ gstack /open-gstack-browser:启动 GStack Browser,让 AI 的浏览器操作全程可见可控

gstack /open-gstack-browser:启动 GStack Browser,让 AI 的浏览器操作全程可见可控

2026-09-06 15:20:38作者:宣利权Counsellor

open-gstack-browser 是 gstack 技能包中的一个"有头浏览器"入口技能:它通过编译产物 browse CLI 以 headed 模式启动重品牌化的 GStack Browser(Playwright 托管的 Chromium),自动加载侧边栏扩展、应用反自动化隐身补丁,让你能实时观察并交互 Claude 的每一次浏览器操作。读完本篇,你将掌握该技能的完整调用流程(SETUP 检查、前置清理、连接、校验、Side Panel 引导、演示与侧边栏聊天),并能对照 browse CLI 源码浏览器管理器扩展清单 理解其端口约定、状态文件与守护进程保护机制。

1. 技能定义:frontmatter 与触发方式

技能定义文件为 open-gstack-browser/SKILL.md(由 SKILL.md.tmplbun run gen:skill-docs 自动生成,文件头注释明确标注 "AUTO-GENERATED from SKILL.md.tmpl — do not edit directly")。其 frontmatter 声明如下:

字段 取值 说明
name open-gstack-browser 技能名,即 /open-gstack-browser
preamble-tier 1 使用第 1 档共享 preamble
version 0.2.0 技能版本
description Launch GStack Browser — AI-controlled Chromium with the sidebar extension baked in 触发路由依据
triggers open gstack browserlaunch chromiumshow me the browser 文本触发词
allowed-tools BashReadAskUserQuestion 技能运行期允许的工具

SKILL.md.tmpl 的 frontmatter 还能看到 voice-triggers: "show me the browser"(语音输入别名)。文档 "When to invoke" 一节列出的口语化触发语包括:"open gstack browser"、"launch browser"、"connect chrome"、"open chrome"、"real browser"、"launch chrome"、"side panel"、"control my browser"。技能声明的效果是:打开一个可见浏览器窗口,侧边栏提供实时活动流(activity feed)与聊天,内置反机器人隐身

2. Preamble:每次技能运行前必须先执行的环境探测

SKILL.md 中 "Preamble (run first)" 是一段必须最先执行的 bash 脚本(所有 gstack 技能共享该段落,由模板中的 {{PREAMBLE}} 占位符注入)。它完成的环境探测对本技能运行至关重要,关键输出变量包括:

  • BRANCH:当前 git 分支(git branch --show-current);
  • PROACTIVE / PROACTIVE_PROMPTED:是否允许主动建议技能;
  • SKILL_PREFIX:为 true 时技能以 /gstack-* 名称呈现,磁盘路径仍是 ~/.claude/skills/gstack/[skill-name]/SKILL.md
  • REPO_MODE(经 gstack-repo-mode 探测)与 SESSION_KINDspawned / headless / interactive,非法值回落 interactive);
  • CONDUCTOR_SESSION:当运行在 Conductor 宿主(CONDUCTOR_WORKSPACE_PATHCONDUCTOR_PORT 非空)且非 headless 时输出 true——此时 AskUserQuestion 不可靠,技能改用纯文本呈现决策;
  • MODEL_OVERLAY: claudeGSTACK_PLAN_MODEactive/inactive,依据 CLAUDE_PLAN_FILEGSTACK_PLAN_MODE 等变量判定)、UPDATE_CHECKTELEMETRYEXPLAIN_LEVELCHECKPOINT_MODE/CHECKPOINT_PUSH 等。

Preamble 还会:写会话心跳文件 ~/.gstack/sessions/$PPID 并清理 120 分钟以上的过期会话;在 telemetry != off 时向 ~/.gstack/analytics/skill-usage.jsonl 追加一条 skill-usage 记录;经 gstack-slug 解析项目标识后加载该项目的 learnings.jsonl(超过 5 条时自动 gstack-learnings-search --limit 3);并执行 "Artifacts Sync" 段——读取 ~/.gstack-artifacts-remote.txt(或旧的 ~/.gstack-brain-remote.txt)、按 24 小时节流执行 gstack-brain-sync --once、在 gbrain 已配置时打印 worktree 级 pin 状态(<repo>/.gbrain-source)。

与之配套的通用行为协议在 Preamble 之后成文,值得了解:

  • Plan Mode 安全操作:plan mode 下 $B$Dcodex exec/codex review、写 ~/.gstack/、写 plan 文件以及用 open 打开生成物均被允许;
  • 技能在 plan mode 中优先:技能文件被视为可执行指令,从 Step 0 逐步执行;PLAN MODE EXCEPTION — ALWAYS RUN 标记的命令(如遥测)照常执行;
  • 完成状态协议:技能结束必须报告 DONE / DONE_WITH_CONCERNS / BLOCKED / NEEDS_CONTEXT 之一;
  • 自我改进:结束前必须回顾会话并记录持久性学习(gstack-learnings-log),确无收获时显式输出 "No durable learnings this session";
  • 遥测(最后运行):将 gstack-timeline-log 完成事件写入本地时间线,并按 telemetry 开关决定是否追加本地 JSONL 与远端遥测。

3. SETUP:先确认 browse 二进制已构建

在任何 browse 命令之前,技能要求执行 SETUP 探测,确定 $B(browse CLI 路径):

_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
B=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/browse/dist/browse" ] && B="$_ROOT/.claude/skills/gstack/browse/dist/browse"
[ -z "$B" ] && B="$HOME/.claude/skills/gstack/browse/dist/browse"
if [ -x "$B" ]; then
  echo "READY: $B"
else
  echo "NEEDS_SETUP"
fi

解析顺序是:仓库本地 vendored 安装(<repo>/.claude/skills/gstack/browse/dist/browse)优先,其次全局安装($HOME/.claude/skills/gstack/browse/dist/browse)。若输出 NEEDS_SETUP

  1. 先询问用户 "gstack browse needs a one-time build (~10 seconds). OK to proceed?" 并停下等待确认;
  2. 确认后运行 cd <SKILL_DIR> && ./setup
  3. bun 未安装,按文档给出的脚本安装(固定 BUN_VERSION="1.3.10",对安装脚本校验 SHA-256 bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd,校验失败则拒绝执行)。

这对应 BROWSER.md Quick start 中的构建方式:bun install && bun run build 产出 browse/dist/browse(约 58MB 的编译 CLI),随后 B=./browse/dist/browse 一次设置、长期复用。browse CLI 是一个薄客户端:读取状态文件、向本地 Chromium 守护进程发 HTTP 请求、把纯文本响应打印到 stdout,单次往返约 100–200ms,不产生上下文 token 开销。

4. Step 0:连接前的清理(Pre-flight cleanup)

连接前先杀掉残留的 browse 服务并清理崩溃后可能滞留的 Chromium 配置文件锁,避免 "already connected" 的假阳性与 profile 锁冲突:

# Kill any existing browse server
if [ -f "$(git rev-parse --show-toplevel 2>/dev/null)/.gstack/browse.json" ]; then
  _OLD_PID=$(cat "$(git rev-parse --show-toplevel)/.gstack/browse.json" 2>/dev/null | grep -o '"pid":[0-9]*' | grep -o '[0-9]*')
  [ -n "$_OLD_PID" ] && kill "$_OLD_PID" 2>/dev/null || true
  sleep 1
  [ -n "$_OLD_PID" ] && kill -9 "$_OLD_PID" 2>/dev/null || true
  rm -f "$(git rev-parse --show-toplevel)/.gstack/browse.json"
fi
# Clean Chromium profile locks (can persist after crashes)
_PROFILE_DIR="$HOME/.gstack/chromium-profile"
for _LF in SingletonLock SingletonSocket SingletonCookie; do
  rm -f "$_PROFILE_DIR/$_LF" 2>/dev/null || true
done
echo "Pre-flight cleanup done"

从源码结构看,这段清理与 CLI 内部机制是呼应的:browse/src/cli.ts 中定义了 chromiumProfileDir()(返回 ~/.gstack/chromium-profile)与 cleanChromiumProfileLocks()(删除 SingletonLock/SingletonSocket/SingletonCookie 三个锁文件,注释标注对应 issue #1781——曾出现上次 Chromium 的 SingletonLock 阻塞自动重启导致"自伤性 crash-loop"),并有 killOrphanChromium() 在启动前清理仍持有锁的孤儿进程。

5. Step 1:$B connect 启动 GStack Browser

$B connect

按技能文档,connect 会以 headed 模式启动 GStack Browser(重品牌化的 Chromium),具备:

  • 一个可见窗口——不是你的日常 Chrome,日常 Chrome 完全不受影响;
  • gstack 侧边栏扩展经 Playwright 的 launchPersistentContext 自动加载
  • 反自动化隐身补丁(文档称 Google、NYTimes 这类站点无需验证码即可加载;见下文边界说明);
  • 自定义 user agent 与 Dock/菜单栏中的 "GStack Browser" 品牌;
  • 一个侧边栏 agent 进程,用于执行聊天命令。

connect 会自动从 gstack 安装目录发现扩展,并固定使用端口 34567 以便扩展自动连接。连接后应把完整输出展示给用户,并确认输出中出现 Mode: headed;若报错或 mode 不是 headed,先运行 $B status 并把输出交给用户。

源码层面,browse/src/cli.tsconnect 的实现印证了以上行为:它设置 BROWSE_HEADED: '1'BROWSE_PORT: '34567' 启动服务器,并注释说明"使用一个众所周知的端口以便 Chrome 扩展自动连接"。同时它显式禁用父进程看门狗(watchdog)——因为 connect 模式下用户掌控着可见浏览器窗口生命周期,而 CLI 在 connect 后立即退出,若保留看门狗会在约 15 秒后误杀服务器;清理改由浏览器 disconnect 事件或 $B disconnect 触发。browse/src/browser-manager.ts 则把 tokenport(默认 34567)安全写入 ~/.gstack/.auth.json,供后续调用鉴权。

两个保护机制值得注意(均来自 browse/src/cli.ts):

  • 拒绝覆盖存活守护进程refuseHeadedOverLiveDaemon,issue #2219):若已有健康的 daemon 在运行,connect 不会静默杀掉它(会丢失其 tabs/cookies/logins),而是报错提示先 browse disconnect 或显式 --force-restart
  • 配置哈希一致性检查(D2):状态文件中保存 (proxyUrl + headed 标志) 的哈希,已有 daemon 配置与新调用不一致时同样拒绝并提示先 disconnect——避免静默重启丢失会话状态。

6. Step 2:校验状态与端口

$B status

确认输出 Mode: headed,再从状态文件读取实际端口:

cat "$(git rev-parse --show-toplevel 2>/dev/null)/.gstack/browse.json" 2>/dev/null | grep -o '"port":[0-9]*' | grep -o '[0-9]*'

端口应为 34567;若不同需记录下来——用户可能要在 Side Panel 中手动输入。接着定位扩展安装路径(供后续手动加载时使用):

_EXT_PATH=""
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
[ -n "$_ROOT" ] && [ -f "$_ROOT/.claude/skills/gstack/extension/manifest.json" ] && _EXT_PATH="$_ROOT/.claude/skills/gstack/extension"
[ -z "$_EXT_PATH" ] && [ -f "$HOME/.claude/skills/gstack/extension/manifest.json" ] && _EXT_PATH="$HOME/.claude/skills/gstack/extension"
echo "EXTENSION_PATH: ${_EXT_PATH:-NOT FOUND}"

extension/manifest.json 显示这是一个 Manifest V3 扩展 "gstack browse":side_panel.default_path 指向 sidepanel.htmlbackground.service_workerbackground.jscontent.js/content.css 注入所有页面(负责页面叠加层与 gstack 角标);权限为 sidePanelstorageactiveTabscriptingtabs,host 权限限定 http://127.0.0.1:*/ws://127.0.0.1:*/——即通信只走本机回环。manifest 还通过固定 key 字段钉住扩展身份(BROWSER.md 指出 v1.63 因此令既有 unpacked 安装获得新的扩展 ID,侧边面板本地状态如已存端口会重置一次)。

7. Step 3:引导用户打开 Side Panel

技能在此使用 AskUserQuestion 引导用户:

Chrome is launched with gstack control. You should see Playwright's Chromium (not your regular Chrome) with a golden shimmer line at the top of the page.

  1. 工具栏找拼图块图标(Extensions)——扩展加载成功时可能已显示 gstack 图标;
  2. 点拼图块 → 找到 gstack browse → 点 pin 图标固定;
  3. 点击工具栏上固定的 gstack 图标;
  4. 右侧 Side Panel 打开,显示实时活动流。

Port: 34567(自动探测——Playwright 托管的 Chrome 中扩展自动连接)

选项:A) 能看到 Side Panel;B) 看到 Chrome 但找不到扩展;C) 出问题了。

B 的排障路径:在地址栏输入 chrome://extensions,确认 "gstack browse" 已列出并启用;若未固定则回任意页面点拼图块固定;若完全没有列出,点 "Load unpacked",在文件选择框按 Cmd+Shift+G 粘贴 Step 2 得到的 {EXTENSION_PATH} 后选择;加载后固定并点图标打开 Side Panel;若 Side Panel 徽标仍为灰色(未连接),点 gstack 图标手动输入端口 34567

C 的排障路径:1) 运行 $B status 查看输出;2) 服务器不健康则重跑 Step 0 清理 + Step 1 connect;3) 服务器健康但浏览器不可见则尝试 $B focus;4) 仍失败则向用户确认看到的现象(报错、白屏等)。

从安全设计看,browse/src/server.ts/extension-token 端点除绑定 127.0.0.1 外还做纵深防御:解析 Host 头中的 hostname(而非拿带端口的原始头做字面比较)以抵御 DNS rebinding;扩展经该端点换取 token 后,活动流由 SSE(/activity/stream)推送,接受 Bearer token 或 30 分钟有效的 HttpOnly gstack_sse 会话 cookie(POST /sse-session 签发)——这些细节在 BROWSER.md "Side Panel + sidebar agent" 一节与 docs/designs/SIDEBAR_MESSAGE_FLOW.md 中有完整描述。

8. Step 4–5:活动流演示与侧边栏聊天

用户确认 Side Panel 工作后,跑一个快速演示:

$B goto https://news.ycombinator.com

等待 2 秒后:

$B snapshot -i

然后告诉用户:"Check the Side Panel — you should see the goto and snapshot commands appear in the activity feed. Every command Claude runs shows up here in real time."(查看 Side Panel——gotosnapshot 会出现在活动流中,Claude 执行的每条命令都会实时显示。)

随后介绍侧边栏聊天(Step 5):Side Panel 还有 chat 标签,输入如 "take a snapshot and describe this page" 这类消息后,一个**侧边栏 agent(子 Claude 实例)**会在浏览器中执行请求,命令实时出现在活动流里。该 agent 可导航页面、点击按钮、填写表单、读取内容,每个任务最长 5 分钟,运行在隔离会话中,不会干扰当前 Claude Code 窗口。对应实现组件见 BROWSER.md 的侧边栏架构表:extension/sidepanel.jsextension/sidepanel-terminal.js(Side Panel UI)、extension/background.js(Background SW)、extension/content.js(页面叠加层)、browse/src/terminal-agent.ts(PTY 生命周期与鉴权)。

9. Step 6:连接完成后的能力总览

技能收尾向用户说明已连上 Chrome 后可做的事:

  • 实时观看 Claude 工作:运行任意 gstack 技能(/qa/design-review/benchmark),每个动作都发生在可见 Chrome 窗口 + Side Panel 活动流中;无需 cookie 导入——Playwright 浏览器共享自己的会话;
  • 直接控制浏览器:侧边栏聊天(自然语言,由侧边栏 agent 执行,如 "fill in the login form and submit");browse 命令 $B goto <url>$B click <sel>$B fill <sel> <val>$B snapshot -i——全部在 Chrome 与 Side Panel 中可见;
  • 窗口管理$B focus 随时把 Chrome 带到前台;$B disconnect 关闭有头 Chrome 回到 headless 模式;
  • 技能在 headed 模式下的表现/qa 在可见浏览器中跑完整测试套件(每次页面加载、点击、断言都可见);/design-review 在真实浏览器中截图——所见即所得;/benchmark 在有头浏览器中测性能。

完成引导后,技能继续处理用户最初请求的任务;若用户未指定任务,则询问想测试或浏览什么。

10. 深入:有头模式的关键实现细节与边界

GStack Browser 是什么、不是什么。BROWSER.md "Real-browser mode" 一节:它不是日常 Chrome,而是 Playwright 托管、在 Dock 与菜单栏带自定义品牌(.app 名、Dock 图标、托盘,而非 UA 字符串)的 Chromium;UA 报告底层 Chromium 版本的普通 Chrome/<version> 字符串——早期的 GStackBrowser 后缀本身就是一个高熵特征,已被移除。窗口顶部有金色微光条、右下角有悬浮 "gstack" 角标,方便分辨哪个 Chrome 窗口在被控制。

隐身补丁的边界要如实理解。 Layer C 反自动化隐身对所有 context 常开:掩盖 navigator.webdriver、恢复 window.chrome.* 形状、对齐 Notification.permission、按宿主硬件画像报告 hardwareConcurrency/deviceMemory、清扫已知 Selenium/Phantom/Playwright 全局变量、并用 Function.prototype.toString 代理让所有被补丁的 getter 报告 [native code]。但它不伪造 navigator.plugins/navigator.languages(现代指纹库会交叉校验这些字段,合成固定值反而更暴露);最底层的 CDP 协议级检测仍可能识破(BROWSER.md 明确指出 "Google can still trigger captchas")。SKILL.md 中 "sites like Google and NYTimes work without captchas" 应理解为"多数 JS 可观察的自动化特征被掩盖后许多站点可干净加载"的概括表述,而非无条件承诺。

守护进程生命周期BROWSER.md Architecture 一节):首次调用时 CLI 在 <project>/.gstack/browse.json 找不到运行中的服务,就在后台拉起 browse/src/server.ts,随机选 10000–49151 区间端口(刻意低于 macOS 临时端口池,避免 OS 端口冲突)、生成 bearer token、以 chmod 600 写状态文件,约 3 秒完成;后续调用读状态文件、带 token POST、纯文本回显,100–200ms;30 分钟空闲自动退出;Chromium 崩溃时 daemon 立即退出(不自愈),下次调用重建;进程存活但不响应 HTTP 被视为 "busy" 而非 "dead"——CLI 给 /health 约 8 秒有界探测后才报 busy,绝不 kill 存活进程,只有显式 --force-restart 才替换(tabs/cookies/登录状态会丢失)。状态文件结构在 browse/src/cli.ts 中定义,除 port/token/pid/modelaunched|headed)外,还包括 configHash(代理/有头配置一致性)与 xvfbPid/xvfbDisplay(Linux 无 DISPLAY 时自动拉起 Xvfb 子进程,供 disconnect 时按 cmdline + 启动时间双重校验后安全清理)。

何时用有头模式(BROWSER.md 建议场景):想看着 Claude 点击应用走查的 QA 测试、需要看到 Claude 所见像素的设计评审、headless 与真实 Chrome 行为不一致的调试、屏幕共享演示,以及 pair-agent 远端代理会话。处于有头模式时,/qa/design-review 等 CDP-aware 技能会自动跳过 cookie 导入提示与 headless 绕行——有头浏览器里已有你登录好的会话。v1.28.0.0 起还支持 --headed/--proxy(SOCKS5 走本地 127.0.0.1 认证桥)/download --navigate(浏览器原生下载)组合,以及 GSTACK_STEALTH=extended 等高级开关;这些是 daemon 启动级配置,运行中改配置会触发前述的 configHash 拒绝逻辑,需先 $B disconnect

11. 小结与延伸阅读

/open-gstack-browser 的价值在于把 gstack 默认 headless 的浏览器操作切换为可观察、可交互、可接管的有头模式:SETUP 检查 → 预清理 → connect(固定端口 34567)→ status 校验 → Side Panel 引导与排障 → 活动流演示 → 侧边栏聊天 → 能力总览,每一步都有明确的命令、验证点和失败分支,配合 "拒绝覆盖存活 daemon""配置哈希一致性""profile 锁清理"等源码级保护,使"让 AI 在你眼皮底下操作浏览器"成为一条可复现、可排障的操作链路。

延伸阅读(均在当前仓库内):

登录后查看全文
热门项目推荐
相关项目推荐