跳到主要内容

数据截至 (上游 commit 544ec87f2ede)

第 2 章 扩展运行时 —— service worker、路由与分层

本章讲什么: 引擎住在 Chrome 扩展里,意味着它要遵守扩展的生存法则——service worker 会被浏览器随时挂起。本章讲扩展怎么组装、请求怎么一层层往下走、以及为「随时被挂起」做的设计。

1. 组装:service-worker.ts

startStagehandServiceWorker(packages/extension/service-worker.ts)把四样东西组起来:

  1. ChromeRuntimeClient(clients/chromeRuntimeClient.js)——扩展内部的 chrome 通道;
  2. RPCClient / RPCRouter——JSON-RPC 的收与派;
  3. StagehandRuntime(runtime.tscreateStagehandRuntime)——应用运行时,抱紧 understudy 引擎;
  4. 心跳(service-worker-lifecycle/heartbeat-manager.jsinstallServiceWorkerHeartbeat)——service worker 闲置会被 Chrome 杀掉,心跳让它在长任务(act/observ持续几十秒)里不被挂起。

2. 分层:controller 管「对不对」,service 管「怎么办」

请求的路由链:

JSON-RPC 请求
→ RPCRouter(rpcRouter.ts:48,按方法名派发)
→ controller(controllers/*.ts):
· 协议版本门(init 时 checkProtocolCompatibility)
· 日志级别、telemetry span 起讫
→ service(services/*.ts):
· 真正的 AI 编排(act/extract/observe)

stagehandController.tsrunOperation 是每步的公共骨架:包一层 logger span + telemetry 上下文再执行——每个 RPC 方法天然是可观测的一段 trace,不用各方法自己埋点。

controller 与 service 的分工一句话:controller 做「这个请求合不合法、记在哪个 span」,service 做「这个act 怎么抓快照、问模型、落动作」。

3. 缓存上下文:在 init 时就定下来

cacheService.buildCacheContext(initParams)(services/cacheService.ts:56)在 init 阶段就把缓存上下文建好:act/observe/extract 各自的缓存键由 buildActCacheData 等(:78-102)按「指令 + 参数」构造。缓存的灵魂没变——同一个指令在同一页面上,第二次可以不问模型直接重放(见第 3 章)。

4. service worker 的特殊约束

把引擎塞进扩展不是免费的,本章只点两个最要命的:

  • 会被挂起:Chrome 对闲置 service worker 直接杀掉——所以有心跳保活,以及把长任务的状态做成可恢复的;
  • 回调要批次化:callbackBatch.ts 把 progress 一类的回调攒成批再发,避免 service worker 和客户端之间高频小包。

这两条对读者自己写「扩展里跑长任务」的项目都适用。