---
name: ego-jev
description: 用 Jev（TypeSafe System One）给 ego-browser 的浏览器操作加速的**附加技能**。多步点击、翻页、搜索表单、导航跳转这类线性任务，可在单个进程内由 Jev 闭环决策，不再每走一步都退回大模型慢思考。同时记录了 ego 内嵌运行时的两条静默失败限制（静态 import 内置模块会无声退出、不能起服务也不能访问 loopback）、凭证为何必须落盘、实测对照数据与失效边界。当任务涉及 ego-browser 多步操作，或要写 ego-browser nodejs 脚本（尤其用到 await import / fetch）时读它。
metadata:
  version: "1.0.0"
  date: "2026-09-19"
  requires: ego-browser
---
# ego-jev — 给 ego-browser 装一个 Jev 决策闭环

这是一个**附加技能（外挂）**：它只存在于 Agent 的技能目录里（如 `~/.agents/skills/ego-jev/`），
**不修改 ego lite 应用包里的任何文件**，因此 ego lite 升级不会把它冲掉。

本技能的组成（都在本 SKILL.md 所在目录内）：

| 路径 | 作用 |
| --- | --- |
| `scripts/ego-jev.mjs` | 引擎（Jev 决策闭环、元素表解析、退出判定） |
| `scripts/ego-jev` | 命令行入口（会自动在 同目录 / 仓库根 / `~/.agents/lib/` 里找引擎） |
| `examples/bench/` | 对照基准脚本（A 组 Jev 闭环 vs B 组经典循环） |

命令行两种调用方式，任选其一：

```bash
ego-jev --url "…" "目标"                        # 已链接进 PATH 时
"<本技能目录>/scripts/ego-jev" --url "…" "目标"  # 直接用技能内的入口
```

基础用法、Space/Page/选择器/收尾纪律仍以 `ego-browser` 技能为准；本技能只负责「让 Jev 加速」这件事。

## 加速能力

多步按钮点击、翻页、搜索表单、导航跳转这类「下一步做什么很明确」的线性任务，不要每走一步都
退出来交给大模型慢思考。可以让 Jev（TypeSafe System One）在单个进程内闭环决策执行。

实测（2026-09-19, jev-1.13）：单步决策约 1.0–1.5s。**对照实测**（同任务、同元素表、同验证器，
交替 3 轮取中位数）：

| 任务 | Jev 单进程闭环 | 经典循环（每步一进程 + 大模型思考） | 结果 |
| --- | ---: | ---: | --- |
| HN 两步复合导航 | 中位 **4.9s**（1 进程，12 次浏览器调用） | 中位 **9.7s**（3 进程） | Jev 快 **~2.0×** |
| 维基百科搜索（两组都要生成文本） | 中位 **5.4s**（1 步） | 中位 **10.1s**（2 进程） | Jev 快 **~1.9×** |

差异主要来自**决策延迟**：Jev 约 1.0–1.5s/次，而可用大模型 1.6–4.5s/次（实测 kimi-k3 1.8s、
minimax-m3 1.6s、glm-5.3 3.2s、qwen3.8-max 3.5s、deepseek-v4-pro 4.5s；gpt-5.6-luna 与
grok-4.6 端点 503 不可用）。进程启动实测只占约 250–350ms/次，**不是**主要成本。

样本只有 3 对/任务且**方差很大**（经典组单轮 7.3s–22s，上述是不同批次中更好的那批），
不构成基准；只能说量级上 Jev 闭环约为经典循环的一半时间。基准脚本在 仓库的 `examples/bench/`
（`run-pair.sh` + `arm-a.js` / `arm-b-step.js`）。

**Jev 快在哪里、不快在哪里**：省的是**决策往返**（Jev 1.0–1.5s/次 vs 大模型 1.6–4.5s/次）
和**每步退出浏览器上下文**的开销。引擎自身也已重构（详见仓库 `PORT-REPORT.md`）：观测改成一次
`page.evaluate` 自建 DOM 元素表（约 2ms，替代 110–130ms 的 `page.snapshot()`），动作改成**裸 CDP
`Input.dispatchMouseEvent`**（13–16ms，替代 788–1005ms 的 `page.click`），等待改成可观察条件。
HN 两步导航端到端因此从 4569ms 降到 1675ms。目标能用选择器写死时，直接写代码仍比两者都快。

**凭证**：`ego-browser nodejs` 内嵌运行时只继承最小化登录环境（HOME/PATH 等），shell 里
export 的变量不会传进去。所以 API Key 必须落盘到 `~/.config/typesafe/api_key`
（或 `TYPESAFE_API_KEY_FILE` 指向的文件，内容为 Key 一行，权限 600）。文件缺失时会直接报错。

**决策结构**（对齐 [browser-use/jev-ultrafast](https://github.com/browser-use/jev-ultrafast) 的
dynamic operation + target）：每次观测产出「索引化元素表」，每个可交互元素一个 `ref`，并携带
当前值；一次请求同时问 **operation** 和各操作的 **target**（推测性问题，彼此不可见）；
每个 target 头只列出与它兼容的元素，executor 只消费命中操作对应的那个头。

```
ref=2 | textbox | "Query Field" | 当前值="hello world"
ref=4 | checkbox | "Subscribe Newsletter" | 未勾选
ref=6 | combobox | "Country Select" | 当前值="Japan" | 选项=[Switzerland|Japan|Germany]
ref=10 | anchor | "Next Page" | /next

operation: click / type_text / type_text_submit / select / scroll_up / scroll_down / wait / done / blocked
           └─ 只列出当前页面受支持的操作
click_target      ← 只含可点元素（按钮/链接/复选框，不含输入框与原生下拉）
type_text_target  ← 只含可输入元素（textbox/searchbox/textarea）
select_target     ← 原生下拉，候选写成 元素#选项序号（如 ref=6#2）
```

`done` / `blocked` 就是 operation 之一，不另设概率阈值；复选框/单选不会被当成可编辑元素。
原生下拉的选项索引与勾选态快照不暴露，由引擎额外做**一次**批量页面调用补齐。

1. **终端一行命令直接驱动**（在任何项目与路径下可用）：

```bash
# 简单目标
ego-jev --url "https://example.com" "点击登录按钮并聚焦输入框"

# 需要输入文本：候选由调用方给，Jev 只负责选字段和选哪段文本
ego-jev --url "https://en.wikipedia.org/wiki/Main_Page" --text "Jev" \
  --until "/wiki/JEV" "在页面顶部的搜索框中输入并提交搜索"

# 让模型自己写文本（已配置好，无需 --text）
ego-jev --url "https://en.wikipedia.org/wiki/Main_Page" "在维基百科搜索框里搜索哥德尔不完备定理"

# 复用已有 space，并在完成后保留 space 供人工检查
ego-jev --space 3 --keep-space --steps 15 "点击未发送帖子并保存"
```

`--until <substr>` 给确定性退出条件（URL 包含该子串即判成功），比依赖 Jev 自评 `done` 可靠得多，
强烈建议带上。退出码 0 表示成功，1 表示未达成目标（此时 space 会被保留供排查）。

#### 文本生成配置（`~/.config/typesafe/text_model.json`，当前指向 opencode-go）

```json
{
  "baseUrl": "https://opencode.ai/zen/go/v1",
  "apiKeyJson": { "file": "~/.pi/agent/auth.json", "path": "opencode-go.key" },
  "model": "deepseek-v4.1-flash",
  "sessionHeader": "x-opencode-session",
  "sessionId": "ego-jev",
  "userAgent": "ego-jev/1.0",
  "headers": { "x-opencode-client": "ego-jev" }
}
```

- `apiKeyJson` **引用已有凭证文件**而非复制密钥；也可直接写 `apiKey`。
- `opencode-go` 必须带 `x-opencode-session`（稳定会话 id，用于路由与提示缓存），
  缺它直接 400 `MissingSessionID`。
- 实测 `deepseek-v4.1-flash` 会输出 `reasoning_content`，**`reasoning:{enabled:false}` 无效**，
  生成耗时 1.3–1.9s；答案在 `content` 字段，引擎只读它。带生成的整步约 3.8s。

2. **在任何 `ego-browser nodejs` 脚本中导入复用**：

```js
import { runJevAutonomousLoop } from "<本技能目录>/scripts/ego-jev.mjs";

// Jev 在当前页面自主连续操作，直到 check 通过或 Jev 判定 done
const result = await runJevAutonomousLoop(page, "依次打开 new 页面，再打开 comments 页面", {
  maxSteps: 8,
  text: ["Jev"],                                              // 可填文本候选（可选）
  check: async (p) => (await p.url()).includes("/newcomments"), // 确定性成功条件（推荐）
});
console.log(result); // { success, reason, steps, history }
```

配套导出：`askJev(state, questions, options)` 做单次判断、`parseActionTargets(snapshot)`
把快照解析成元素表（含 kind/当前值）、`enrichTargets(page, targets)` 一次批量调用补齐下拉选项与
勾选态、`buildQuestions(targets, { texts, hasTextSource })` 组装操作/目标问题、
`buildActionMenu()` / `extractCandidateRefs()` 构建动作菜单与提取候选目标、
`validateChoice(response, questions)` 响应校验（拒收非 argmax 或概率和不一致的响应）、
`generateText(input, options)` / `loadTextModelConfig()` 文本生成、`runJevStep(page, goal, options)` 走一步。

### 边界（实测）

- Jev 只发一次并行判断、**无跨请求记忆**：复合目标（A 然后 B）依赖引擎回填的「已完成步骤」，
  已内置并已验证（两步导航、已填字段改写、下拉改选、勾选均通过）。更长链路未做专项评估。
- 元素表由**一次 `page.evaluate` 在页面内自建**（默认 `maxTargets` 60 项、可见文本 `maxText` 2500
  字符），元素每次观测都会**重新编号**。定位不交给选择器：引擎掌握节点身份，动作走裸 CDP
  （`Input.dispatchMouseEvent`）。两层陈旧防护：① **陈旧校验**：只执行与本次元素表一致的 `ref`，
  不一致记为 `staleTarget` 跳过；② **执行前守卫**：命中测试 + 可见/可用性检查，失败记为
  `guardRejected`（原因有 `node_gone`/`disconnected`/`disabled`/`invisible`/`readonly`/
  `offscreen`/`covered`/`not_select`/`option_unavailable`）。
- **视口外目标（有界滚动揭示）**：元素表只覆盖当前视口，目标可能在下方。当某步“选了需目标的
  动作却没解析出目标”（或元素表为空）时，引擎会**滚动约一屏后重新观测**（默认最多 4 次，
  `maxReveals` 可调），并在候选多于预算时**优先列出本次尚未展示过的元素**，因此滚动总能露出
  下一批而不是反复只看同一批。**`maxTargets` 默认值不变**（元素表大小不变，载荷不增长）。
  滚动到边界、或滚动后没有带来任何新元素时立即停止，并把该次计入 `no_progress`。
- **文本来源**三选一：`--text` 候选（Jev 从中选，优先）→ `~/.config/typesafe/text_model.json`
  的模型（**当前已配置为 opencode zen / `minimax-m3`**；严格只接受恰好一个非空 `text`
  字段的 JSON，不合格则判 `text_model_failed` 而不是猜值）→ 都没有时不提供输入操作。
  可用 `textModel: null` 显式禁用，或传自定义 `async (input) => string` 函数接入其他模型。
- 由模型生成的文本会让落地 URL 不可预测（中文搜索词会被编码），这类任务不要指望 `--until`，
  靠 `jev_done` 或 `check` 判成功。
- 退出条件。成功：`check_passed` / `check_passed_after_step`（`check` 通过，推荐）或 `jev_done`
  （Jev 判定完成）。失败：`no_progress`（连续 5 次变更类动作页面无变化）、`stuck`（同一动作连续
  3 次无变化，含反复滚动）、`target_missing`（选了需目标的动作却没解析出目标，**且已用完有界滚动
  揭示额度**，连续 2 次）、
  `no_targets`（连续 3 次元素表为空）、`guard_rejected`（执行前守卫拒绝，决策已陈旧或元素不可用，
  该步未执行）、`invalid_response`（Jev 响应校验不通过，未执行）、`blocked`、`text_model_failed`、
  `no_text_source`、`action_failed`、`max_steps_reached`。
  失败时先看 `result.reason` 再决定是否重试；`guard_rejected` 与 `target_missing` 表示**什么都没执行**，重试前需要重新观测。
- `blocked` 由 Jev 判断（验证码/登录墙/无可用推进手段/反复无进展）。合成拦截页已验证：
  纯拦截页首步即判 `blocked`，登录墙试一次后判 `blocked`。**真实**验证码站未测。
- 比较「页面是否变化」时忽略 URL 的 `#hash`：点锚点链接不算有进展。
- 每步浏览器调用：默认 **1 次 `page.evaluate`**（在页面内自建元素表）+ url/title；
  `page.snapshot()` 仅在显式 `observe: "snapshot"` 时使用。实测（HN 首页）：自建元素表约
  **2ms / ~1.8k 字符**，`page.snapshot()` **110–130ms / 27484 字符**。
- 下面两条关于「快照」的实测限制（下拉选项、复选框覆盖）适用于 `observe: "snapshot"` 这条旧路径。
- 真实站点实测：原生下拉能读到全选项（wikipedia.org 语言选择器 77 项，1 步改选成功）；
  但**并非所有控件都进快照**——DuckDuckGo 设置页 DOM 有 18 个复选框，快照里是 0 个，
  这类元素引擎无从感知。httpbin 表单的复选框在快照里既无 loc 也无名称，靠「文档顺序」
  兜底补齐名称与勾选态；仅当数量完全一致时才敢用，否则显示「勾选态未知」而不会谎报。
- 动作后不用固定延迟：先短静默，再用 `waitForLoadState("load")` 兜底捕捉**延迟导航**
  （实测下拉改选触发的跳转会晚于 400ms，若只用固定延迟会把“已跳转”误判成“未变化”，
  导致 Jev 重复执行同一动作）。
- **已知弱点：改选后需再点确认按钮的原生下拉**（如 wikipedia.org 语言选择器）不稳定：
  Jev 可能重复改选同一选项，被 `stuck` 拦住。实测带 `--until` + 足够 `--steps`（≥6）时
  约 2/3 成功，失败时报 `stuck` 而非静默错误。这类任务要么给 `--until`，要么把两步动作写死。
- Jev 走完不等于业务正确：仍要按本 Skill 的观察纪律复核最终页面状态。
- 该站可能开启自动翻译（实测 Chrome 把注入的英文表单译成中文，`Switzerland`→`瑞士`），
  元素名与选项名可能与目标语言不一致；Jev 跨语言选择正常，但用**字符串比较**做 `--until` 或
  `check` 会误判，请改用 URL 路径或 DOM 状态。

## ego 内嵌运行时的两条静默失败限制（实测）

写 `ego-browser nodejs` 脚本时踩过，两条都是**无声失败**，排查很坑：

1. **静态 import 内置模块会让整个脚本无声退出**：`import { createServer } from "node:http"`
   不报错、无输出、exit 0。**必须用 `await import("node:http")`**。
   本地文件与引擎同理，用 `await import("/绝对/路径.mjs")`。
2. **运行时不能起服务、也不能访问 loopback**：`server.listen()` 的回调永不触发；
   `fetch("http://127.0.0.1:…")` 会挂起后 exit 0。外部 HTTPS `fetch()` 正常。
   要测本地代码，把服务起在 ego 之外，用普通 node 进程去测。

配套的两条环境事实：

- **自定义环境变量一律不传入**（不止 `TYPESAFE_API_KEY`）：`export FOO=bar` 在运行时里读不到。
  需要传配置时，在**父进程把值替换进脚本文本**再送进去（`ego-jev` CLI 就是这么处理凭证的）。
- **`process.cwd()` 是 `/`**，不是 shell 的工作目录。脚本里不要依赖相对路径。

## 不要改应用包里的文件

有需要时不要在
`/Applications/ego lite.app/Contents/Frameworks/ego Framework.framework/Versions/*/Resources/ego-skills/ego-browser/SKILL.md`
里加内容：那是供应商受签名的应用包，升级会替换该目录（`Versions/0.5.0.32` → 新版本号），
改动会丢失。要扩展就放到包外的 `~/.agents/skills/<自己的技能>/`。
