尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

gstack Browser-Skills v1 设计解析:把重复的浏览器操作固化成确定性 Playwright 脚本

gstack Browser-Skills v1 设计解析:把重复的浏览器操作固化成确定性 Playwright 脚本 gstack Browser-Skills v1 设计解析把重复的浏览器操作固化成确定性 Playwright 脚本【免费下载链接】gstackUse Garry Tans exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack在 gstackGarry Tan 的 Claude Code 工具集中Browser-Skills 是一种过程层能力把 agent 反复执行的浏览器流程抓取页面数据、操作表单固化成独立运行的确定性 TypeScript 脚本通过$B skill run以约 200ms 返回 JSON替代 agent 每次通过$B原语重新探索页面所耗费的数十秒。本篇基于仓库内的设计文档 BROWSER_SKILLS_V1.md结合 browse/src 下的实际实现与 browser-skills/hackernews-frontpage 参考技能完整拆解其架构决策、三层存储模型、spawn 时的作用域令牌信任模型以及 Phase 2 的/scrape/skillify自动化固化工具链。读完本文你可以掌握Browser-Skill 的目录契约与 frontmatter 字段定义、三层查找project → global → bundled的解析逻辑、$B skill list/show/run/test/rm五个子命令的底层实现与输出协议、作用域令牌的签发/吊销生命周期、原子写入的技能固化流程以及如何为hackernews-frontpage这类参考技能编写 fixture 测试。一、Browser-Skill 是什么一个目录就是一套可复现的浏览器流程按 BROWSER_SKILLS_V1.md 的定义Browser-Skill 是per-task 目录把一个重复的浏览器流程固化成确定性脚本。每个技能的标准目录结构为browser-skills/name/ ├── SKILL.md # frontmatter prose contract ├── script.ts # deterministic logic ├── _lib/browse-client.ts # vendored copy of the SDK ├── fixtures/host-date.html # captured page for tests └── script.test.ts # parser tests against the fixture其中SKILL.md是唯一的事实来源Single source of truth——frontmatter 承载host、triggers、args、version、source、trusted正文是人类可读的契约说明script.ts是确定性逻辑通过 vendored SDK 调用 daemon_lib/browse-client.ts是 SDK 的逐字节副本保证技能完全自包含拷走目录即可运行fixtures/存放抓取的页面快照供解析器测试离线回放script.test.ts是纯解析器测试不依赖 daemon。创建一次之后后续调用直接运行脚本返回 JSON。设计文档给出的动机是未来调用约 200ms 返回结果而 agent 通过$B原语重新探索需要约 30 秒这是设计文档中的目标描述而非基准测试数据。与 Domain-Skills 的分工记忆事实 vs 固化过程仓库中已有 domain-skills 机制v1.8.0.0两者共享按 host、三级作用域的心智模型但解决的是不同层次的问题维度Domain-SkillsBrowser-Skills本质agent 记住关于某网站的事实agent 把流程固化成确定性脚本形态按 hostname 索引的 JSONL 笔记per-task 目录注入方式会话启动时注入 prompt通过$B skill run执行隔离机制状态机quarantine → active → globalspawn 时按次签发的作用域令牌设计文档明确指出过程层procedure layer的生产力收益更大因为它把抓取和表单操作从潜在空间latent space即 LLM 的即时推理推到了可复现的代码里。二、为什么脚本必须跑在 daemon 之外设计文档特别解释了这条路径与早期 P1 计划agent 自写$B命令的区别原 P1 被 Codex 的 T1 意见否决——agent 编写的 TypeScript 无法在 daemon进程内安全执行ambient globals、构造器小工具、审批与执行之间的 top-level-await TOCTOU 竞态。正确的方案是进程外 worker 隔离 能力传递 IPC那是一个可能永远不会落地的硬工程。Browser-Skills 通过把脚本作为独立的 Bun 进程运行在 daemon 之外绕开了整个问题daemon 从不 import 或 eval 任何技能代码技能通过 loopback HTTP 与 daemon 通信使用的与任何外部客户端相同的 wire format。从源码结构看spawn 的完整生命周期核心实现位于 browser-skill-commands.ts 的spawnSkill()其五步流程与设计文档逐条对应generateSpawnId()生成 8 字节随机 hex 的 spawn idmintSkillToken()签发作用域令牌TTL 超时时间 30s 缓冲buildSpawnEnv()按 trusted/untrusted 构造环境变量以bun run script.ts -- args在技能目录下 spawn 子进程捕获 stdout上限 1MB与 stderr强制超时finally块中无条件吊销令牌——即使脚本超时或崩溃令牌也会被回收。三、三层查找模型project → global → bundled存储层实现于 browser-skills.ts三个层级目录按优先级解析Tier路径说明projectproject/.gstack/browser-skills/name/项目级覆盖优先级最高需 git 仓库检测项目根global~/.gstack/browser-skills/name/用户级/skillify的默认落盘层级bundledgstack-install/browser-skills/name/随 gstack 安装只读分发包含hackernews-frontpage关键实现细节均可在 browser-skills.ts 中核对没有 INDEX.json直接遍历目录。listBrowserSkills()L322-L361每次调用都重新遍历三层目录并解析每个 SKILL.md 的 frontmatter50 个技能约 5-10ms。这消除了索引与磁盘漂移这一整类 bug。First-hit-wins 且结果可见。遍历时 project 层优先seen.has(entry)即跳过同名技能高优先级层获胜。为消除first-hit-wins 不透明的问题Codex 审查发现 #4$B skill list输出中每个技能名旁边内联打印解析出的 tierNAME / TIER / HOST / DESC四列表格为什么运行的是那一份不再是调试谜团。项目根与 bundled 根的自动探测。detectProjectRoot()通过git rev-parse --show-toplevel检测项目根2 秒超时detectBundledRoot()则判断process.execPath是否匹配/browse/dist/browse$来定位安装目录源码/开发模式则回退到从当前文件向上两级的目录L110-L124。Tombstone 软删除。tombstoneBrowserSkill()把 user 层project/global技能移动到该层.tombstones/name-ts/$B skill list忽略.tombstones目录。bundled 技能不允许 tombstone只读要用全局/项目条目覆盖它。Frontmatter唯一的 SchemaparseSkillFile()L132-L166用自研的迷你 frontmatter 解析器支持标量、字符串列表、args的 mapping 列表必填字段为name缺省时用目录名兜底与hosttriggers与args可省略空列表合法trusted仅当显式写trusted: true才为真默认 untrusted。解析失败的技能在listBrowserSkills中被静默跳过构建期由 skill-validation.test.ts 捕获契约违规。参考技能 hackernews-frontpage 的 SKILL.md 展示了完整的 frontmatter 形态--- name: hackernews-frontpage description: Scrape the Hacker News front page (titles, points, comment counts). host: news.ycombinator.com trusted: true source: human version: 1.0.0 args: [] triggers: - scrape hacker news frontpage - scrape hn frontpage - get hn top stories - latest hacker news stories ---四、信任模型spawn 时的作用域令牌而不是 env-scrub 当沙箱这是设计文档中决策 #6也是整个方案里最容易被误解的部分。信任模型有两条正交的轴轴机制默认daemon 侧能力每次 spawn 签发绑定readwrite作用域的令牌17 条浏览器驱动命令减去eval/js/cookies/storage等 admin 命令clientId 编码技能名 spawn idspawn 退出即吊销始终作用域化绝不使用 daemon root token进程侧 env 访问trusted: true通过process.env去掉GSTACK_TOKENtrusted: false默认丢弃除最小允许列表LANG、LC_ALL、TERM、TZ、锁定 PATH之外的一切并显式剥离 secret 模式键Untrusted必须显式 opt in令牌层skill-token.tsbrowse/src/skill-token.ts 包装token-registry关键行为mintSkillToken()L74-L83clientId 为skill:name:spawnId作用域固定[read, write]tabPolicy: shared技能可以切换标签页rateLimit: 0不限流过期时间 spawn 超时 30 秒缓冲TOKEN_TTL_SLACKrevokeSkillToken()幂等——吊销已吊销的令牌返回 false 但不算错误注释中明确解释了为何排除 admin 作用域agent 编写的技能不应获得任意 JS 执行Phase 2 可能为真正需要 eval/js 的人类编写技能提供admin: truefrontmatter 标志但会在 skillify 时接受更强的审查。这一层是真正可执行的边界技能即使尝试调用evaladmin 作用域也会被 daemon 返回 403——即便 SDK 把这个方法暴露出来了。能力边界放在了正确的位置。env 层卫生hygiene不是沙箱buildSpawnEnv()browser-skill-commands.ts L416-L455实现两条路径trusted透传process.env但永远剥离GSTACK_TOKEN纵深防御防止父进程的 root token 传播缺失 PATH 时补最小 PATHuntrusted只保留UNTRUSTED_ALLOWLISTLANG、LC_ALL、LC_CTYPE、TERM、TZL404-L408PATH 用解析出的 bun 所在目录 系统目录拼接再经SECRET_KEY_PATTERNS二次过滤L391-L397const SECRET_KEY_PATTERNS [ /TOKEN/i, /KEY/i, /SECRET/i, /PASSWORD/i, /CREDENTIAL/i, /^AWS_/, /^AZURE_/, /^GCP_/, /^GOOGLE_APPLICATION_/, /^ANTHROPIC_/, /^OPENAI_/, /^GITHUB_/, /^GH_/, /^SSH_/, /^GPG_/, /^NPM_TOKEN/, /^PYPI_/, ];最后GSTACK_PORT与GSTACK_SKILL_TOKEN总是最后注入父进程无法通过预置同名环境变量来劫持它们。设计文档对此有诚实的边界声明Bun 没有内置 FS 沙箱untrusted 技能仍然可以import fs读取 OS 用户可读的任何文件如~/.ssh/id_rsa。env scrub 是卫生措施而非沙箱真正的 OS 级隔离macOSsandbox-exec、Linux namespaces/seccomp是 Phase 4 的工作且可以干净地插入现有 trusted/untrusted 契约背后。原始计划把 env-scrub 称为沙箱被 Codex 批评为security theater安全戏法修订后的计划如实描述它是尽力卫生 纵深防御真正的边界在 daemon 侧作用域令牌。五、$B skill子命令实现与输出协议五个子命令由 browser-skill-commands.ts 的handleSkillCommand()分发list、show name、run name [--arg kv]... [--timeoutNs]、test name、rm name [--global]。输出协议设计决策 #9对齐gh/kubectl/docker惯例stdout 单个 JSON 文档stderr 流式日志退出码 0/非零默认超时 60 秒DEFAULT_TIMEOUT_SECONDS 60--timeoutNs覆盖stdout 上限1MBMAX_STDOUT_BYTES超出则截断并以非零退出码上报。handleRun()的失败语义值得注意L146-L173退出码非零、超时、或截断三者任一都会抛出带 stderr 尾部前 4096 字节的错误。一个工程细节为什么不用管道而用临时文件runToFiles()L249-L290把子进程的 stdout/stderr 指向临时文件而非管道。源码注释记录了完整的调查结论高负载父进程下Bun 中第一个piped spawn 会间歇性丢失 stderr$B skill test的bun test恰好把报告拆到两个流banner 走 stdout、通过/失败摘要走 stderr丢失 stderr 会把结果悄悄降级成只剩 banner而Bun.spawnSync虽能可靠捕获但同步等待会死锁——因为被 spawn 的技能要回调同一个 daemon通过GSTACK_PORT。写文件则让内核保证子进程退出前所有字节已落盘退出后读取必然完整。六、SDKbrowse-client.ts 与每技能一份副本的分发模型规范 SDK 位于 browse/src/browse-client.ts约 260 行每个技能在_lib/browse-client.ts携带一份逐字节副本参考技能的副本见 browser-skills/hackernews-frontpage/_lib/browse-client.ts。这是设计决策 #4Option E技能完全自包含——把目录拷到任何地方都能运行版本漂移不可能——SDK 冻结在技能编写时的版本无 npm 发布流程、无固定路径 tilde import磁盘成本约 3KB/技能。SDK 的认证解析在resolveBrowseAuth()L65-L102两级回退envGSTACK_PORTGSTACK_SKILL_TOKEN——由$B skill runspawn 时注入令牌是按次签发的作用域能力state fileBROWSE_STATE_FILE环境变量或git-root/.gstack/browse.json中的porttokendaemon root token。这条路径只用于开发者直接bun run script.ts的场景——你的权限不是 agent 的。两者皆无则抛出带明确指引的错误。SDK 只暴露 daemon 现有的POST /commandHTTP 面goto/wait导航、text/html/links/forms/accessibility/attrs/media/data读取、click/fill/select/hover/type/press/scroll交互、snapshot/screenshot快照以及兜底的command(cmd, args)。默认请求超时 30 秒timeoutMs。设计文档特别澄清了它与 cli.ts 中既有 HTTP 客户端的关系cli.ts的sendCommand()与 CLI 进程强耦合process.stdout.write、process.exit、server-restart 恢复逻辑不能作为库复用browse-client.ts镜像其 wire format 但是 library-shaped——这正是 Codex 审查发现 #3新 SDK 冗余被验证为不成立的原因。七、参考技能深读hackernews-frontpage设计决策 #11 选择 HN 首页作为参考技能的理由无登录、HTML 结构稳定、输出确定、适合 fixture 测试。解析器实现browser-skills/hackernews-frontpage/script.ts 的核心是导出的纯函数parseStoriesFromHtml(html): Story[]主正则匹配每个tr.athing行捕获id属性与行体L57从行体内的span.titleline a提取标题与 URL并做 HTML 实体解码amp;、quot;等subtext 边界限定L72-L82评论数与分数从下一个tr中取但必须把搜索范围限定在tr.spacer或下一条tr.athing之前。源码注释说明了这个 bug不界定的话招聘帖无分数会把下一条故事的分数泄漏进来招聘帖job postings没有分数与评论points/comments返回nulldiscuss链接视为 0 评论。输出协议在文件头注释中明确stdout 为单个 JSON 文档{ stories: Story[], count }stderr 用于日志解析/网络失败以非零退出。无 daemon 的 fixture 测试browser-skills/hackernews-frontpage/script.test.ts 直接加载 fixtures/hn-2026-04-26.html 并对parseStoriesFromHtml断言fixture 中 5 条故事、1-based 排名按文档顺序、id 与tr.athing[id]一致、实体解码正确、招聘帖返回 null 字段、discuss计 0 评论、空 HTML 与无 story 行返回[]、缺少titleline的tr.athing行不伪造故事。SKILL.md 正文点出了 fixture 测试的哲学当 HN HTML 改版、我们的选择器失效时测试会在用户注意到之前对着捕获的 fixture 先失败。这就是重点。运行方式$B skill test hackernews-frontpage # 即 cd 到技能目录后 bun test script.test.ts八、Phase 2a/scrape /skillify 与原子写入Phase 2av1.19.0.0 交付提供两个技能模板/scrape intent是拉取页面数据的单一入口——新 intent 首次调用通过$B原语做原型并返回 JSON后续匹配的 intent 路由到已固化的 browser-skill约 200ms/skillify把最近一次成功的原型固化为磁盘上的永久技能。变更型流程的姊妹命令/automate推迟到 Phase 2b。v1.19.0.0 计划评审锁定了四条决策ID决策锁定行为D1/skillify溯源守卫向前回溯至多 10 个 agent turn 寻找一次清晰边界的/scrape调用原型 intent 行 其尾部 JSON 输出找不到就拒绝并提示先运行 /scrape 再说 /skillify无静默回退D2合成输入切片模板指示 agent 只提取产出用户接受 JSON 的最终一轮$B调用 用户陈述的 intent 字符串丢弃失败的选择器尝试、无关聊天、更早会话的内容。关闭了 Codex 发现 #6选择从 agent 自身上下文重提示而非结构化录制器D3原子写入纪律写入~/.gstack/.tmp/skillify-spawnId/对临时目录运行$B skill test仅在成功 用户批准后 rename 到最终 tier 路径测试失败或拒绝审批则整体删除临时目录从未批准的技能不留 tombstoneD4测试范围5 个 gate-tier E2Escrape 匹配、scrape 原型、skillify 正常路径、溯源拒绝、审批门拒绝 1 个原子写入助手失败清理单测 1 个人工验证的冒烟测试变更型 intent 拒绝D3 的实现browser-skill-write.tsbrowse/src/browser-skill-write.ts 提供三个函数对应 Codex 发现 #5原子技能打包与符号链接防御stageSkill()L67-L94把候选技能文件写入~/.gstack/.tmp/skillify-spawnId/name/——外层skillify-spawnId/包裹目录按 spawn 隔离并发/skillify互不冲突files 映射中的相对路径若含/开头或..直接拒绝纵深防御commitSkill()L119-L172原子 rename 到最终 tier 路径且——拒绝覆盖已存在的同名技能审批门必须在调用前暴露命名冲突lstat检查 staged 目录不是符号链接拒绝跟随realpath解析 tier 根并检查目的地未逃逸出 tier 树discardStaged()删除 staged 目录及空的 wrapper幂等、尽力而为。技能名验证L34-L45同样严格/^[a-z][a-z0-9]*(-[a-z0-9])*$/——小写字母/数字/连字符字母开头无连续或尾部连字符≤64 字符。Carry-overs 与 /automate 草图默认 tier 为 global流程类技能倾向全局/skillify时可按项目覆盖镜像 domain-skill 的 scopeBun 运行时分发Codex 发现 #7保持 openPhase 2a 假设 Bun 在 PATH 上gstack 的 setup 已要求 Bun写入/skillify的 SKILL.md Limits 节真正的修复在 Phase 4。Phase 2b 的/automate是/scrape的变更型姊妹复用/skillify与 D3 助手区别在于非固化运行时每个变更步骤都要 UNTRUSTED 包裹的摘要 AskUserQuestion确认门固化后脚本可无人值守运行固化脚本明确枚举了要执行哪些$B click/fill/type调用。九、Phase 3 / 4 草图与验证方式Phase 3resolver 注入会话启动时按 host 发现并注入技能镜像 domain-skill 在server.ts:722-743的注入模式renderBrowserSkillsForHost(hostname, projectSlug)读三层、过滤host匹配的条目、输出 UNTRUSTED 包裹的块追加到 system prompt。配套gstack-config browser_skillify_prompts开关默认关开启后活动流显示某 host 上 ≥N 条命令且该 hostintent 尚无技能时/qa、/design-review等任务结束会给出要不要固化成技能的提示。Phase 4LLM-judge 评估agent 是否用了技能而不是重新探索、fixture 陈旧检测bundled fixture 与线上页面对比、untrusted spawn 的 OS 级 FS 沙箱、以及$B skill upgrade name规范 SDK 变更时重新生成 sibling 副本。Phase 1 的验证基线设计文档 Verification 节bun test通过以下测试文件——browse/test/skill-token.test.ts15 断言、browse/test/browse-client.test.ts26 断言、browse/test/browser-skills-storage.test.ts31 断言、browse/test/browser-skill-commands.test.ts29 断言、browser-skills/hackernews-frontpage/script.test.ts13 断言、test/skill-validation.test.ts 新增 7 个 bundled 技能契约断言。daemon 运行下的端到端验证$B skill list # 显示 hackernews-frontpage (bundled) $B skill show hackernews-frontpage # 打印 SKILL.md $B skill run hackernews-frontpage # 返回 30 条故事的 JSON $B skill test hackernews-frontpage # 运行 script.test.ts十、Codex 审查的八项发现与处置设计文档完整记录了/codex外部审查的 8 项发现其处置方式是判断该方案成熟度的最佳窗口#发现Phase 1 处置1没有 FS 沙箱时信任模型是假的由决策 #6作用域令牌关闭2Phase 1 对单个 bundled 技能过度设计承认但保留——用户选择完整 Phase 1 以在 Phase 2 落地 agent 编写之前锁定架构每个子系统小到可以干净移除3cli.ts:398的既有客户端模式可能使 sibling SDK 冗余验证为不成立——实际 HTTP 客户端是sendCommand()与 CLI 强耦合不可作库复用4first-hit-wins 查找不透明由$B skill list/show内联打印 tier 缓解5原子技能打包比索引问题更重要符号链接防御Phase 1 关闭bundled 只读天然原子Phase 2 的writeBrowserSkill采用临时目录 rename realpath/lstat纪律6Phase 2 从活动流合成弱有损环形缓冲Open issue活动流是遥测不是重放 IRPhase 2 需结构化录制器或从 agent 上下文重写后者已在 D2 采纳7Bun 运行时回归独立 Bun 脚本重新引入 Bun 依赖Open issue 留给 Phase 2 分发决策随技能附带 Bun 二进制 / 编译为自包含可执行文件 / Node.js cli.tsHTTP 模式8file://fixture 无法证明时序/认证/导航/懒加载水合文档化为已知限制Phase 2/automate需要更丰富的 fixturemock daemon 带时序、HAR 回放等结语不变的部分同样重要设计文档专门列出什么不变这也是集成安全边界的声明domain-skills 的存储/状态机/注入完全未动tunnel-surface 允许列表server.ts:118-123仍是同一批 17 条命令L1-L6 安全栈不受影响——Phase 1 的技能不向 prompt 注入文本Phase 3 的 resolver 注入也将沿用既有 UNTRUSTED 包裹。Browser-Skills v1 的整体设计哲学可以概括为把能力边界放在 daemon 侧可执行的作用域令牌把进程侧的 env scrub 诚实地定位为卫生而非沙箱把技能做成完全自包含的目录sibling SDK fixture 测试用每次 spawn 一签一发一吊销取代长期凭证用临时目录 测试 审批 原子 rename取代直接落盘。这套结构让agent 把成功流程固化成可复现脚本成为一条不需要进程内执行未受信代码、且失败时零残留的安全路径。【免费下载链接】gstackUse Garry Tans exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表