Metadata-Version: 2.4
Name: xiaoyu-agent
Version: 0.21.0
Summary: 小羽 — a harness coding agent
Project-URL: Repository, https://github.com/pholex/zhinu
Project-URL: Issues, https://github.com/pholex/zhinu/issues
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: openai==2.52.0
Requires-Dist: tree-sitter>=0.22
Requires-Dist: tree-sitter-bash>=0.21
Provides-Extra: tui
Requires-Dist: prompt_toolkit==3.0.53; extra == "tui"
Requires-Dist: rich==14.3.4; extra == "tui"
Provides-Extra: browser
Requires-Dist: playwright==1.62.0; extra == "browser"

# 小羽 · Xiaoyu

[![ci](https://github.com/pholex/zhinu/actions/workflows/ci.yml/badge.svg)](https://github.com/pholex/zhinu/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/xiaoyu-agent)](https://pypi.org/project/xiaoyu-agent/)

> Weaving code, connecting dots, and showing you the best harness architecture.

一个自建的 harness coding agent。运行依赖极简（openai SDK + tree-sitter-bash，
后者做权限判定的 bash 语法白名单解析、缺了会自动降级），
Windows / macOS / Linux 全平台，`pip install xiaoyu-agent` 即用。

命名是两层：**织女 Zhinu 是织坊，小羽 Xiaoyu 是梭子**。项目伞名叫
**Zhinu Coding Agent | Token Weaver of the Universe**——织女织布，这里织代码。

而且这不是比附：**harness 本身就是织机的部件**（提综装置，控制经线升降的那套框架），
"织女 + harness"是同一个词。harness 提综开出梭口，梭子带着 token 当纬线穿行，
代码就这么织出来。

梭子叫**小羽**：羽毛在传统语义里是飞升与轻盈，对应这个 agent 想要的手感——
行云流水、轻量、无负担。织女不下凡，下凡干活的是小羽，所以你装的、喊的都是小羽：
`pip install xiaoyu-agent`，命令 `xiaoyu` / `xy`。

## 能做什么

- **接任意 OpenAI 兼容端点**（LiteLLM / vLLM / 各家官方 API），流式输出；
  token 用量按**路由**（`provider/model`）分开记账，`/usage` `/context` 随时查
- **完整的编码工具组**：读文件、正则搜索、glob 列文件、精确替换编辑、写文件、
  bash（Windows 上自动换 PowerShell）、任务清单；`explore` 子 agent 把检索
  委托给便宜的只读模型，帮主模型省一半上下文；`web_search` 联网搜索
  （借厂商内置搜索，配了 `DEEPSEEK_API_KEY` 即自动可用；
  `XIAOYU_SEARCH_PROVIDER=xai` 可切 grok-4.5，搜索更强但更贵）
- **编辑不出岔子**：改文件必须先完整读过；替换目标不唯一、或文件读后被外部
  改动，都会被打回而不是硬写；行尾空白、弯引号这类无关差异自动容错
- **默认安全**：写文件与执行命令逐条人工确认（附模型自述的用途和 diff 预览）；
  `allow` / `deny` 权限规则可免确认或强制拦截（deny 连 `--yolo` 都拦得住）；
  `rm -rf /`、fork bomb 等不可逆命令任何模式下都不执行；
  **bash 命令跑在内核级沙箱里**（macOS Seatbelt / Linux bubblewrap，默认开启）——
  只能写工作区、临时目录和构建缓存，家目录文件删不掉、`~/.zshrc` 覆写不了，
  `--no-network` 可连网络一起断
- **长会话不断片**：上下文快满时自动分层回收（先无损回收旧的大块工具输出，
  不够再摘要压缩），原始任务永久保留；Ctrl-C 中断随时能继续，
  `xiaoyu resume` 恢复历史会话
- **出错自己扛**：限流、瞬时错误自动退避重试；配 `XIAOYU_FALLBACK_MODELS`
  后主模型持续故障自动切备用模型，同一份会话原样接着跑
- **多 provider 合并，网关不再是单点**：直连国产大模型（填一个
  `DEEPSEEK_API_KEY` 就能跑，不需要网关）与 OpenAI 兼容网关的模型清单
  自动合并，同名模型直连优先——少一跳、不加价、key 不过第三方；
  网关上那份不消失而是**降级为兜底**，直连限流/失效时自动接手，会话不断
- **可扩展**：SKILL.md 技能（与 Anthropic / agentskills.io 同形态，
  扫 `~/.agents/skills/` 规范库，按需加载不吃常驻上下文）；
  第三方工具包 `pip install` 即挂载（entry point 组 `xiaoyu.tools`）；
  **MCP server**——在工作区 `.mcp.json` 或用户配置目录 `mcp.json` 里声明
  （`mcpServers` 格式与 Claude Code / Cursor 通用，支持 `${env:VAR}` 展开），
  server 工具以 `mcp__名字__工具` 挂载，schema 缓存让工具秒级可见、进程按
  首次调用启动，`/mcp` 查状态；纯 stdlib 实现，远程 HTTP server 用
  `mcp-remote` 桥接。安全内建：子进程环境白名单（API key 不外流）、
  npx/uvx 包启动前查 OSV 恶意包库、内联攻击脚本形状的配置拒绝启动、
  工具描述/schema 变更自动隔离（防 rug-pull，`/mcp approve` 批准）、
  主进程被杀后 server 自动回收不留孤儿
- **浏览器操作**（可选 `[browser]`，Playwright 引擎）：打开网页、读页面快照、
  点击、填表——纯文本交互，便宜模型也能用。装法：
  `pip install "xiaoyu-agent[browser]"` 后跑一次 `playwright install chromium`。
  默认无头启动独立浏览器（无登录态）；设
  `XIAOYU_BROWSER_CDP=http://127.0.0.1:9222` 可接管以
  `--remote-debugging-port=9222` 启动的本机 Chrome，处理需要登录态的页面。
  每个动作都过人工确认——它能以你的身份点任何按钮，审批就是它的沙箱
- **TUI 增强界面**（可选 `[tui]`）：斜杠命令补全、跨会话输入历史、粘贴折叠、
  diff 语法高亮、深浅主题自适应终端背景色；对话留在终端原生
  scrollback，可滚动/复制/grep；管道/CI 环境自动退回明文 REPL，行为一致
- **跨平台**：Windows / macOS / Linux，CI 三平台 × 两 Python 版本矩阵验证

## 安装

```bash
pip install xiaoyu-agent      # 或 pipx install xiaoyu-agent
pip install "xiaoyu-agent[tui]"   # 推荐：带补全/历史/粘贴折叠的增强界面（可选）
xiaoyu --version              # 验证装上了（多 Python 并存时也能确认升级生效在哪个环境）
```

升级：

```bash
xiaoyu update    # 等价 pip install --upgrade xiaoyu-agent；未装 [tui] 时自动补上
```

也可以手动 `pip install --upgrade xiaoyu-agent`（pipx 装的用 `pipx upgrade xiaoyu-agent`）。

开发模式：

```bash
git clone https://github.com/pholex/zhinu.git && cd zhinu
python3 -m venv .venv
.venv/bin/pip install -e .
```

## 配置

首次使用直接跑配置向导（全平台，写到固定的用户级路径，从此不用找 `.env` 放哪）：

```bash
xiaoyu config            # 交互向导：直连 key / 网关端点、模型、key
xiaoyu config --show     # 查看生效配置与各项来源（key 永不回显）
xiaoyu config --path     # 打印用户级配置文件路径
xiaoyu config --set XIAOYU_MODEL=deepseek-v4-pro   # 非交互写入，可重复
```

用户级配置文件的位置：macOS / Linux 在 `~/.config/xiaoyu/.env`（跟随 `$XDG_CONFIG_HOME`），
**Windows 在 `%APPDATA%\xiaoyu\.env`**。

也可以手动在任意工作目录放 `.env`（零依赖自解析，仓库里的已被 `.gitignore` 排除）。
**最短路径是直连国产大模型——填一个 key 就能跑，不需要任何网关：**

```ini
DEEPSEEK_API_KEY=<你的-deepseek-key>
```

或者走 OpenAI 兼容网关（LiteLLM、vLLM、各家官方 API…）：

```ini
XIAOYU_BASE_URL=https://<你的网关>/v1
XIAOYU_MODEL=bedrock-claude-sonnet-5
XIAOYU_API_KEY=<你的-key>
```

优先级：**真实环境变量 > 当前目录 `.env` > 项目根 `.env` > 用户级 `.env`**，所以临时覆盖很方便：

```bash
XIAOYU_MODEL=bedrock-claude-opus-5 xiaoyu
```

macOS 上 key 也可以不落盘，改用 Keychain（`.env` 里留空即可，会自动回退去读；Windows 上请用 `.env` 或环境变量）。
service 名就是变量名本身，`.env` / 环境变量 / Keychain 三处同名：

```bash
security add-generic-password -a "$USER" -s "XIAOYU_API_KEY" -U -w     # 网关
security add-generic-password -a "$USER" -s "DEEPSEEK_API_KEY" -U -w   # DeepSeek 直连
```

| 变量 | 默认值 | 说明 |
|---|---|---|
| `DEEPSEEK_API_KEY` | — | DeepSeek 直连 key（厂商原生名，别家工具已配过的直接复用） |
| `XIAOYU_BASE_URL` | — | 任意 OpenAI 兼容 `/v1` 端点：LiteLLM、vLLM、各家官方 API… |
| `XIAOYU_API_KEY` | — | 网关的 API key（也认 LiteLLM 生态惯用的 `LITELLM_API_KEY`） |
| `XIAOYU_MODEL` | `deepseek-v4-pro` | 见下方"选模型" |
| `XIAOYU_FALLBACK_MODELS` | —（不降级） | 备用模型降级链，逗号分隔；主模型重试耗尽后依次自动切换 |
| `XIAOYU_PROVIDERS` | 直连 → 网关 | 覆盖 provider 优先级，逗号分隔（如 `gateway,deepseek` = 临时全走网关） |
| `XIAOYU_ENV_FILE` | — | 指定 `.env` 路径，等价于 `--env-file` |

直连和网关**至少要有一个**（两个都配也可以，见下节）。

### 多 provider：直连优先，网关兜底

直连和网关同时配时，小羽会把两边的模型清单**合并**：

- **同名模型直连赢**——`deepseek-v4-pro` 走 DeepSeek 官方，少一跳、不加价、key 不过第三方。
- **网关那份不消失，降级为兜底**——直连限流 / 5xx / key 失效时自动切到网关同名模型，
  会话原样继续。网关从此不是单点。
- 网关**通配**：任何没被直连认领的名字（`bedrock-claude-opus-5` 等）照旧转发过去。

`/model` 无参可以看到合并后的清单和每个模型的来源：

```
  deepseek-v4-pro    ← 直连 deepseek（同名可兜底：网关）
  deepseek-v4-flash  ← 直连 deepseek（同名可兜底：网关）
  其余任意模型名     ← 网关（转发，不枚举）
降级链：deepseek/deepseek-v4-pro → gateway/deepseek-v4-pro → …
```

`provider/model` 是显式寻址，用来点名走哪一家：`/model gateway/deepseek-v4-pro`。
点名之后不再自动兜底——既然指定了，就不该被偷偷换掉。

未内置的厂商用通用变量接入（`<NAME>` 自取，大写）：

```ini
XIAOYU_PROVIDER_MOONSHOT_BASE_URL=https://api.moonshot.cn/v1
XIAOYU_PROVIDER_MOONSHOT_API_KEY=<key>
XIAOYU_PROVIDER_MOONSHOT_MODELS=kimi-k3,kimi-k3-turbo   # 留空 = 通配
```

## 用

```bash
xiaoyu                                  # 交互模式（xy 是等价缩写）
xy "把 utils.py 里的类型注解补全"          # 一次性执行
xiaoyu --model bedrock-claude-opus-5    # 指定模型

# 脚本化 / 无人值守（对应 claude -p 的用法）
git diff | xy "写一条 commit message"     # 管道内容当材料，参数当任务
xy --output-format json "总结这个仓库"     # 末尾输出一个 JSON（result/usage）
xy --output-format stream-json "跑测试"   # 每个事件一行 JSON（NDJSON）
xy resume --last "继续把测试修完"          # 续上最近会话执行一条指令后退出
```

无人值守时没人能按确认键：需要写文件/执行命令的任务，先用 `/allow` 配好允许
规则，或加 `--yolo`（危险）；否则受限工具会被自动拒绝并让模型改道/说明。

REPL 里：`/help` `/tools` `/skills` `/model` `/usage` `/context` `/compact` `/clear` `/exit`

## 选模型

默认 **主模型 `deepseek-v4-pro` + 摘要 `deepseek-v4-flash`**，国产便宜模型优先。

依据是 12 个候选模型 × 4 个 case 的实测（`xiaoyu/evals/results/*sweep.json`）：

| 模型 | 相对输入单价 | 4 case | 单个 case 成本 |
|---|---|---|---|
| deepseek-v4-flash | 1× | 4/4 | $0.0026 |
| qwen3.7-plus | 2× | 4/4 | $0.0052 |
| **deepseek-v4-pro** | **3×** | **4/4** | **$0.0061** |
| glm-5.2 | 8× | 4/4 | $0.011 |
| gpt-5.6-luna | 7× | 4/4 | $0.016 |
| qwen3.7-max | 12× | 4/4 | $0.030 |
| kimi-k3 | 20× | 4/4 | $0.034 |
| gpt-5.6-terra | 19× | 3/4 | $0.037 |
| bedrock-claude-sonnet-5 | 20× | 4/4 | $0.062 |
| gpt-5.6-sol | 37× | 4/4 | $0.099 |
| bedrock-claude-opus-5 | 37× | 4/4 | $0.124 |
| bedrock-claude-fable-5 | 68× | 4/4 | $0.147 |

同一个任务，**最贵的比最便宜的高 57 倍**，而通过率没差别 —— 所以默认取便宜的。
（注意这些 case 偏简单、几乎全员满分，这张表主要看**成本差异**，
不构成"便宜模型什么活都够用"的证据——详见 [ROADMAP](ROADMAP.md) 里 eval 集的待办。）

**硬活手动升级**，别指望默认模型包打天下：

```bash
xiaoyu --model bedrock-claude-opus-5     # 启动时指定
# 或 REPL 里随时切：/model bedrock-claude-opus-5
```

## ⚠️ 安全

`bash` 工具会在你机器上执行模型给出的任意命令。默认每条都要你确认，这是主要防线；
`deny` 权限规则和危险命令硬拦截在 `--yolo` 下仍然生效，但覆盖面有限。
`--yolo` 会关掉逐条确认——只在一次性、可丢弃的目录里用。

**已经踩过一次**：eval 是无人值守 + `--yolo` 跑的，某个模型跑 pytest 失败后执行了
`pip install pytest`，装进了系统 Python 的 site-packages（那个目录 admin 组可写、免 sudo）。
现在 eval 会注入 `PIP_REQUIRE_VIRTUALENV=true` 挡住这条路，但要清楚：
**这只堵了一个具体出口，不是沙箱**。`read_file` / `str_replace` / `write_file` 有工作区边界检查，
`bash` 的真隔离靠的是内核级沙箱那层（macOS Seatbelt / Linux bwrap，默认开启）——
在沙箱不可用的平台上它仍能写你有权限的任何地方。

## 更多文档

- [ROADMAP.md](ROADMAP.md) — 路线图与待办（按价值排，不按容易排）
- [DEVELOPMENT.md](DEVELOPMENT.md) — 设计取舍全量清单、测试与 eval 方法、
  explore 实测数据、仓库结构、发版流程
