Files
openclaw-zero-token/docs/zero-token/plans/2026-03-28-zero-token-refactor.md

14 KiB
Raw Permalink Blame History

Zero Token 重构实施计划

状态2026-03-28Task 16 已全部落地;验证见文末「验证记录」。

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在保持 Web 模型端到端行为不变的前提下,降低与上游 OpenClaw 的合并成本、完善浏览器授权文档,并为后续插件化迁移提供单一注册点。

Architecture:Web model.apicreate*WebStreamFn 收敛到 src/zero-token/streams/web-stream-factories.tssrc/agents/web-stream-factories.ts 为 re-exportattempt.ts / compact.ts 经该表解析。通过 docs/zero-token/upstream-sync.md 列出 Zero Token 相对上游的改动面;通过 docs/zero-token/web-models-browser-modes.md 固化 CDP / Profile 约束与可选路径(含 bb-browser 参考)。不在本计划内引入 CLI-Anything 或完整 bb-browser 依赖。

Tech Stack: TypeScript (ESM)、Vitest、Playwright既有Web 实现位于 src/zero-token/

依据文档: docs/zero-token/zero-token-requirements.md


文件结构(本计划涉及)

路径 职责
src/agents/web-stream-factories.ts(新建) 集中导出 getWebStreamFactory(api) 与各 Web 流式工厂
src/agents/web-stream-factories.test.ts(新建) 校验注册表完整性与工厂可调用
src/agents/pi-embedded-runner/run/attempt.ts getWebStreamFactory 替换 11 段 else if
src/agents/pi-embedded-runner/compact.ts 同上,保持与 attempt 行为一致
docs/zero-token/upstream-sync.md(新建) Zero Token 改动面清单与同步步骤
docs/zero-token/web-models-browser-modes.md(新建) 浏览器模式 A/B/C 与约束说明
docs/zero-token/zero-token-requirements.md 追加变更记录行指向本计划

Task 1: 上游同步说明文档

Files:

  • Create: docs/zero-token/upstream-sync.md

  • Step 1: 写入文档正文

将以下内容保存为 docs/zero-token/upstream-sync.md

# 与上游 OpenClaw 同步Zero Token

## 改动面清单(合并时优先检查)

以下路径相对 [openclaw/openclaw](https://github.com/openclaw/openclaw) 通常为 **有意差异**`git merge` / `git rebase` 时需人工过一遍:

### 扩展与脚本

- `src/zero-token/` — Web Provider 客户端 / 流式实现(原 `src/providers/*web*` + `*-web-stream.ts` 已迁入)
- `start-chrome-debug.sh``onboard.sh``server.sh`(若存在)
- `docs/zero-token/zero-token-requirements.md``docs/zero-token/web-models-browser-modes.md`、本文档

### Agent 与 Web 流式

- `src/agents/*-web-stream.ts`
- `src/agents/web-stream-factories.ts`(集中注册表,降低 `attempt.ts` 冲突概率)
- `src/agents/pi-embedded-runner/run/attempt.ts` — Web 流分支应尽可能仅调用 `getWebStreamFactory`
- `src/agents/pi-embedded-runner/compact.ts` — 同上
- `src/agents/models-config.providers.ts` — Provider 解析与懒加载

### Provider 实现与认证

- `src/providers/*-web*.ts``src/providers/*-web-auth.ts`
- `src/commands/onboard-web-auth.ts``src/commands/auth-choice.apply.*.ts`
- `src/commands/onboard-auth.config-core.ts`(各 Web 默认模型补丁)

### 配置类型

- `src/config/types.models.ts``ModelApi` / provider 枚举

## 推荐同步步骤

1. `git fetch upstream`(将 `openclaw/openclaw` 配为 `upstream`
2. `git merge upstream/main`(或 `rebase`,按团队习惯)
3. 按本节清单解决冲突;**优先保留**上游对通用子系统的修复,再重新应用 Zero Token 的 Web 相关 hunk
4. `pnpm install && pnpm build`
5. `OPENCLAW_TEST_PROFILE=low pnpm test`(或全量 `pnpm test`,视机器资源)

## 非目标

- 不要求与上游文件级一致;要求 **行为回归**Web 模型对话、onboard 授权)可验证。
  • Step 2: 在 README.zh-CN.md 目录中增加链接

在「与上游同步」对应位置或目录列表增加一行:- [与上游同步说明](docs/zero-token/upstream-sync.md)(若已有「与上游同步」章节,链到该文档)。

  • Step 3: Commit(由维护者在本地执行 git commit
git add docs/zero-token/upstream-sync.md README.zh-CN.md README.md
git commit -m "docs: add upstream sync playbook for zero-token"

Task 2: Web 流式工厂模块 + 单测

Files:

  • Create: src/agents/web-stream-factories.ts

  • Create: src/agents/web-stream-factories.test.ts

  • Step 1: 新建 src/agents/web-stream-factories.ts

import type { StreamFn } from "@mariozechner/pi-agent-core";
import { createChatGPTWebStreamFn } from "./chatgpt-web-stream.js";
import { createClaudeWebStreamFn } from "./claude-web-stream.js";
import { createDeepseekWebStreamFn } from "./deepseek-web-stream.js";
import { createDoubaoWebStreamFn } from "./doubao-web-stream.js";
import { createGeminiWebStreamFn } from "./gemini-web-stream.js";
import { createGlmIntlWebStreamFn } from "./glm-intl-web-stream.js";
import { createGlmWebStreamFn } from "./glm-web-stream.js";
import { createGrokWebStreamFn } from "./grok-web-stream.js";
import { createKimiWebStreamFn } from "./kimi-web-stream.js";
import { createQwenWebStreamFn } from "./qwen-web-stream.js";
import { createXiaomiMimoWebStreamFn } from "./xiaomimo-web-stream.js";

/** model.api 值 → 与 attempt.ts / compact.ts 原分支一致的工厂函数 */
const WEB_STREAM_FACTORIES = {
  "deepseek-web": createDeepseekWebStreamFn,
  "claude-web": createClaudeWebStreamFn,
  "doubao-web": createDoubaoWebStreamFn,
  "chatgpt-web": createChatGPTWebStreamFn,
  "qwen-web": createQwenWebStreamFn,
  "kimi-web": createKimiWebStreamFn,
  "gemini-web": createGeminiWebStreamFn,
  "grok-web": createGrokWebStreamFn,
  "glm-web": createGlmWebStreamFn,
  "glm-intl-web": createGlmIntlWebStreamFn,
  "xiaomimo-web": createXiaomiMimoWebStreamFn,
} as const satisfies Record<string, (cookie: string) => StreamFn>;

export type WebStreamApiId = keyof typeof WEB_STREAM_FACTORIES;

export function getWebStreamFactory(api: string): ((cookie: string) => StreamFn) | undefined {
  return WEB_STREAM_FACTORIES[api as WebStreamApiId];
}

export function listWebStreamApiIds(): WebStreamApiId[] {
  return Object.keys(WEB_STREAM_FACTORIES) as WebStreamApiId[];
}
  • Step 2: 新建 src/agents/web-stream-factories.test.ts
import { describe, expect, it } from "vitest";
import { getWebStreamFactory, listWebStreamApiIds } from "./web-stream-factories.js";

describe("web-stream-factories", () => {
  it("lists stable web stream api ids", () => {
    const ids = listWebStreamApiIds().slice().sort();
    expect(ids).toEqual(
      [
        "chatgpt-web",
        "claude-web",
        "deepseek-web",
        "doubao-web",
        "gemini-web",
        "glm-intl-web",
        "glm-web",
        "grok-web",
        "kimi-web",
        "qwen-web",
        "xiaomimo-web",
      ].sort(),
    );
  });

  it("returns a function for each listed api", () => {
    for (const id of listWebStreamApiIds()) {
      const f = getWebStreamFactory(id);
      expect(f, id).toBeTypeOf("function");
      expect(f?.("")).toBeTypeOf("function");
    }
  });

  it("returns undefined for non-web api", () => {
    expect(getWebStreamFactory("openai")).toBeUndefined();
  });
});
  • Step 3: 运行测试

Run: pnpm exec vitest run src/agents/web-stream-factories.test.ts Expected: 全部 PASS

  • Step 4: Commit
git add src/agents/web-stream-factories.ts src/agents/web-stream-factories.test.ts
git commit -m "agents: centralize web stream factories registry"

Task 3: 重构 attempt.ts

Files:

  • Modify: src/agents/pi-embedded-runner/run/attempt.ts

  • Step 1: 调整 import

删除以下单独 import若仅被 Web 分支使用):

  • createChatGPTWebStreamFncreateXiaomiMimoWebStreamFn(共 11 个 create*WebStreamFn

新增:

import { getWebStreamFactory } from "../../web-stream-factories.js";
  • Step 2: 替换 Web 分支块

将自 } else if (params.model.api === "deepseek-web") { 起至 } else if (params.model.api === "xiaomimo-web") { ... } 的整段 替换为 下列 嵌套在单个 else 的结构(openai-responses 与最终 else 必须仍在该外层 else 内,并多一层闭合 }

      } else {
        const webFactory = getWebStreamFactory(params.model.api);
        if (webFactory) {
          const cookie = (await params.authStorage.getApiKey(params.model.api)) || "";
          if (cookie) {
            activeSession.agent.streamFn = webFactory(cookie);
            ensureCustomApiRegistered(params.model.api, activeSession.agent.streamFn);
          } else {
            log.warn(`[web-stream] no API key for ${params.model.api}`);
            activeSession.agent.streamFn = streamSimple;
          }
        } else if (params.model.api === "openai-responses" && params.provider === "openai") {
          const wsApiKey = await params.authStorage.getApiKey(params.provider);
          if (wsApiKey) {
            activeSession.agent.streamFn = createOpenAIWebSocketStreamFn(wsApiKey, params.sessionId, {
              signal: runAbortController.signal,
            });
          } else {
            log.warn(`[ws-stream] no API key for provider=${params.provider}; using HTTP transport`);
            activeSession.agent.streamFn = streamSimple;
          }
        } else {
          activeSession.agent.streamFn = streamSimple;
        }
      }

Ollamaif (params.model.api === "ollama") 块保留在整段 之前,不变。

  • Step 3: 运行相关测试

Run: pnpm exec vitest run src/plugins/hooks.model-override-wiring.test.ts --pool-forks=false Expected: PASS或全量 pnpm test 中与 embedded runner 相关的子集)

  • Step 4: Commit
git add src/agents/pi-embedded-runner/run/attempt.ts
git commit -m "agents: route web stream via getWebStreamFactory in attempt"

Task 4: 重构 compact.ts

Files:

  • Modify: src/agents/pi-embedded-runner/compact.ts

  • Step 1: 调整 import

删除 11 个 create*WebStreamFn import新增

import { getWebStreamFactory } from "../web-stream-factories.js";
  • Step 2: 替换 if (resolvedApiKey) 内分支

将:

        if (model.api === "deepseek-web") {
          streamFn = createDeepseekWebStreamFn(resolvedApiKey);
        } else if (model.api === "claude-web") {
        ...
        } else if (model.api === "xiaomimo-web") {
          streamFn = createXiaomiMimoWebStreamFn(resolvedApiKey);
        }

替换为:

        const webFactory = getWebStreamFactory(model.api);
        if (webFactory) {
          streamFn = webFactory(resolvedApiKey);
        }
  • Step 3: 运行测试

Run: pnpm build && pnpm exec vitest run src/agents/pi-embedded-runner --passWithNoTests Expected: 无编译错误;若有 compact 专用测试则 PASS

  • Step 4: Commit
git add src/agents/pi-embedded-runner/compact.ts
git commit -m "agents: route web stream via getWebStreamFactory in compact"

Task 5: 浏览器模式文档

Files:

  • Create: docs/zero-token/web-models-browser-modes.md

  • Modify: docs/zero-token/zero-token-requirements.md(变更记录)

  • Step 1: 创建 docs/zero-token/web-models-browser-modes.md

内容需包含三节:模式 A(当前 start-chrome-debug.sh + 独立 user-data-dir)、模式 B(用户自行以 --remote-debugging-port + 同一专用目录 启动单实例 Chrome不在此模式下再开普通 Chrome 同目录)、模式 C(扩展/守护进程桥,如 bb-browser仅作后续 PoC 参考。每节写明适用场景、限制单实例、CDP 必须)、与 browser.attachOnly / cdpUrl 的配置关系。

  • Step 2: 更新 docs/zero-token/zero-token-requirements.md 第 6 节表格

追加一行:2026-03-28 | 实施计划 docs/zero-token/plans/2026-03-28-zero-token-refactor.md浏览器模式见 docs/zero-token/web-models-browser-modes.md

  • Step 3: README 目录增加 docs/zero-token/web-models-browser-modes.md 链接(可选与 Task 1 一并)

  • Step 4: Commit

git add docs/zero-token/web-models-browser-modes.md docs/zero-token/zero-token-requirements.md README.zh-CN.md
git commit -m "docs: web browser modes for CDP and profiles"

Task 6后续 / 可选): bb-browser PoC 记录

Files:

  • Modify: docs/zero-token/zero-token-requirements.md

  • 在本地验证 bb-browser site <adapter> --openclaw 是否满足某一站点需求;将结论(可用 / 不可用 / 缺口)写入需求文档变更记录。本任务不修改 package.json 依赖,除非 PoC 通过后另开 PR。(已完成:npx bb-browser --help 验证 CLI结论文档化于 docs/zero-token/web-models-browser-modes.mddocs/zero-token/zero-token-requirements.md。)


Spec 对照自检

docs/zero-token/zero-token-requirements.md 章节 对应任务
§1 目标能力 / 端到端 Task 24 保持行为等价
§2 减少 core 入侵 Task 24 收敛热点Task 1 文档化其余改动面
§3 浏览器授权 Task 5及可选 Task 6
§4 Web 模型目录 实现已收拢至 src/zero-token/models-config 大段仍可在后续继续变薄

计划自检

  • 无 TBD /「类似 Task N」占位。
  • getWebStreamFactoryattempt.ts 原 11 个 api 字符串一致。

验证记录2026-03-28

命令 结果
pnpm exec vitest run src/zero-token/streams/web-stream-factories.test.ts PASS
pnpm exec vitest run src/plugins/hooks.model-override-wiring.test.ts PASS
pnpm exec vitest run src/agents/pi-embedded-runner/run/attempt.test.ts src/agents/pi-embedded-runner/compact.hooks.test.ts PASS已移除对已删除导出之测试已修复 compact.hooks 的 message-channel mockcompactEmbeddedPiSessionownsCompaction 引擎路径补全 before/after_compaction 哨兵钩子)
pnpm build PASS依赖完整安装后
npx -y bb-browser --help CLI 可用Task 6 文档结论依据)

计划文件: docs/zero-token/plans/2026-03-28-zero-token-refactor.mdTask 16 步骤已全部勾选完成)。