claw-code 源码详细分析:compat-harness——对接编辑器生态时,兼容层该吞掉哪些「历史包袱」?

涉及源码rust/crates/compat-harness/src/lib.rsrust/crates/claw-cli/src/main.rsdump_manifests);关联 rust/crates/commandsrust/crates/toolsrust/crates/runtimeBootstrapPlan


1. compat-harness 在本仓库里实际做什么

compat-harness 是一个 小而专 的 crate:在本地若存在「上游式」目录布局时,读取旧版 TypeScript 入口附近的源文件 ,用 行级启发式 抽出:

  • 命令清单commands::CommandRegistryBuiltin / InternalOnly / FeatureGated
  • 工具清单tools::ToolRegistryBase / Conditional
  • 启动阶段草图runtime::BootstrapPlanBootstrapPhase 序列)
rust 复制代码
// 88:98:rust/crates/compat-harness/src/lib.rs
pub fn extract_manifest(paths: &UpstreamPaths) -> std::io::Result<ExtractedManifest> {
    let commands_source = fs::read_to_string(paths.commands_path())?;
    let tools_source = fs::read_to_string(paths.tools_path())?;
    let cli_source = fs::read_to_string(paths.cli_path())?;

    Ok(ExtractedManifest {
        commands: extract_commands(&commands_source),
        tools: extract_tools(&tools_source),
        bootstrap: extract_bootstrap_plan(&cli_source),
    })
}

它不是 通用「编辑器插件协议」实现;它是历史树形与源文件习惯 挡在 Rust 产品之外的 适配器 ,让 CLI 能在 --dump-manifests 一类路径上对比/调试清单,而不强迫主实现依赖 TS 构建链。


2. 兼容层应吞掉的包袱(按类别)

2.1 目录与命名约定:写死的 src/commands.ts 等路径

rust 复制代码
// 34:47:rust/crates/compat-harness/src/lib.rs
    pub fn commands_path(&self) -> PathBuf {
        self.repo_root.join("src/commands.ts")
    }

    pub fn tools_path(&self) -> PathBuf {
        self.repo_root.join("src/tools.ts")
    }

    pub fn cli_path(&self) -> PathBuf {
        self.repo_root.join("src/entrypoints/cli.tsx")
    }

吞掉什么 :编辑器/单仓/多仓布局里「命令聚合文件叫啥、放在哪」的历史决策。Rust 侧不必在 claw 主逻辑里到处 join("src/commands.ts");只认 UpstreamPathsCLAW_CODE_UPSTREAM

2.2 仓库定位启发式:谁在「上游根」

rust 复制代码
// 57:86:rust/crates/compat-harness/src/lib.rs
fn resolve_upstream_repo_root(primary_repo_root: &Path) -> PathBuf {
    let candidates = upstream_repo_candidates(primary_repo_root);
    candidates
        .into_iter()
        .find(|candidate| candidate.join("src/commands.ts").is_file())
        .unwrap_or_else(|| primary_repo_root.to_path_buf())
}

fn upstream_repo_candidates(primary_repo_root: &Path) -> Vec<PathBuf> {
    let mut candidates = vec![primary_repo_root.to_path_buf()];

    if let Some(explicit) = std::env::var_os("CLAW_CODE_UPSTREAM") {
        candidates.push(PathBuf::from(explicit));
    }

    for ancestor in primary_repo_root.ancestors().take(4) {
        candidates.push(ancestor.join("claw-code"));
    }

    candidates.push(primary_repo_root.join("reference-source").join("claw-code"));
    candidates.push(primary_repo_root.join("vendor").join("claw-code"));
    ...
}

吞掉什么

  • 开发机上前端/插件仓与 claw-code相对位置../claw-codevendor/reference-source/);
  • 显式覆盖环境变量;
  • 「找不到就当 workspace 父目录」的 失败兜底

主产品代码保持「我只问 extract_manifest,不关心你磁盘上怎么摆」。

2.3 TS/JS 模块语法与 feature 门:用正则级解析,而非 TypeScript 编译器

extract_commands 识别:

  • INTERNAL_ONLY_COMMANDS 数组块 → CommandSource::InternalOnly
  • import ... fromBuiltin
  • feature('...') 且路径含 ./commands/FeatureGated
rust 复制代码
// 100:147:rust/crates/compat-harness/src/lib.rs
pub fn extract_commands(source: &str) -> CommandRegistry {
    let mut entries = Vec::new();
    let mut in_internal_block = false;

    for raw_line in source.lines() {
        let line = raw_line.trim();

        if line.starts_with("export const INTERNAL_ONLY_COMMANDS = [") {
            in_internal_block = true;
            continue;
        }
        ...
        if line.starts_with("import ") {
            for imported in imported_symbols(line) {
                entries.push(CommandManifestEntry {
                    name: imported,
                    source: CommandSource::Builtin,
                });
            }
        }

        if line.contains("feature('") && line.contains("./commands/") {
            if let Some(name) = first_assignment_identifier(line) {
                entries.push(CommandManifestEntry {
                    name,
                    source: CommandSource::FeatureGated,
                });
            }
        }
    }

    dedupe_commands(entries)
}

工具侧同理:import./tools/ 且名以 Tool 结尾 → Basefeature + ToolConditional

吞掉什么

  • bundler/feature-flag 的 历史写法
  • 不把 tsc/swc 拉进 Rust 构建;
  • 不完整语义(只做清单级近似,不做类型检查)。

代价与边界:上游若改字符串模式,extractor 会 静默漂移------这正是「包袱应留在 compat crate + 测试」的原因。

2.4 CLI 启动链的字符串考古:extract_bootstrap_plan

cli.tsx搜子串 推断存在哪些 fast path,再拼 BootstrapPlan

rust 复制代码
// 182:217:rust/crates/compat-harness/src/lib.rs
pub fn extract_bootstrap_plan(source: &str) -> BootstrapPlan {
    let mut phases = vec![BootstrapPhase::CliEntry];

    if source.contains("--version") {
        phases.push(BootstrapPhase::FastPathVersion);
    }
    if source.contains("startupProfiler") {
        phases.push(BootstrapPhase::StartupProfiler);
    }
    if source.contains("--dump-system-prompt") {
        phases.push(BootstrapPhase::SystemPromptFastPath);
    }
    if source.contains("--claude-in-chrome-mcp") {
        phases.push(BootstrapPhase::ChromeMcpFastPath);
    }
    ...
    phases.push(BootstrapPhase::MainRuntime);

    BootstrapPlan::from_phases(phases)
}

吞掉什么

  • 旧 CLI 里 flag 名称与分支实现细节 (含品牌/历史命名如 --claude-in-chrome-mcp);
  • 「启动profiler / daemon / remote-control / bg session」等 产品史 在源码里的痕迹。

Rust 自己的默认计划可单独维护(BootstrapPlan::claw_default()),与「从上游扫出来的计划」分离:

rust 复制代码
// 22:38:rust/crates/runtime/src/bootstrap.rs
    pub fn claw_default() -> Self {
        Self::from_phases(vec![
            BootstrapPhase::CliEntry,
            BootstrapPhase::FastPathVersion,
            ...
            BootstrapPhase::MainRuntime,
        ])
    }

学习点 :兼容层允许 扫描结果本仓 canonical 计划 并存;编辑器生态只依赖「能对比」,不强迫合并成一份真相。

2.5 调用侧:把 I/O 失败与「无上游」挡在 CLI 子命令里

rust 复制代码
// 469:482:rust/crates/claw-cli/src/main.rs
fn dump_manifests() {
    let workspace_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
    let paths = UpstreamPaths::from_workspace_dir(&workspace_dir);
    match extract_manifest(&paths) {
        Ok(manifest) => {
            println!("commands: {}", manifest.commands.entries().len());
            println!("tools: {}", manifest.tools.entries().len());
            println!("bootstrap phases: {}", manifest.bootstrap.phases().len());
        }
        Err(error) => {
            eprintln!("failed to extract manifests: {error}");
            std::process::exit(1);
        }
    }
}

吞掉什么 :文件不存在、路径错、编码问题------不污染 tools/commands 主注册表路径;只在 dump/对比工具里失败退出。


3. 兼容层 不该 吞掉的东西(边界)

不应塞进 compat-harness 应落在何处
真实工具执行、权限、沙箱 runtime + tools
面向用户的稳定 CLI 契约 claw-cli / commands
与编辑器通信的 LSP/协议细节 lsp + 上层集成
长期唯一的命令/工具真相源 Rust registry 或生成管线,而非永久依赖 TS 文本扫描

compat-harness 的定位是 桥接旧表面,不是第二套 runtime。


4. 测试策略:无上游时优雅跳过

rust 复制代码
// 311:321:rust/crates/compat-harness/src/lib.rs
    #[test]
    fn extracts_non_empty_manifests_from_upstream_repo() {
        let paths = fixture_paths();
        if !has_upstream_fixture(&paths) {
            return;
        }
        let manifest = extract_manifest(&paths).expect("manifest should load");
        assert!(!manifest.commands.entries().is_empty());
        ...
    }

学习点 :公开 CI 未必有上游树;兼容层测试 允许缺席,避免绑架贡献者环境。


5. 小结

compat-harness 应吞掉的历史包袱可概括为:

  1. 路径与仓库拓扑 (固定文件名、CLAW_CODE_UPSTREAM、vendor/reference 约定)。
  2. TS 模块与 feature-flag 行文(用轻量解析换清单,不引编译器)。
  3. 旧 CLI 字符串与 flag 考古(bootstrap fast path 的启发式还原)。
  4. 失败与无上游(局限在 dump/对比入口,不污染核心产品路径)。

不应吞掉 :执行语义、权限、会话、长期 registry 真相------那些应留在 runtime / tools / commands 的 definitive 实现中。


相关推荐
阿菜ACai8 小时前
Claude 和 Codex 在审计 Skill 上性能差异探究
ai·代码审计
SharpCJ9 小时前
Android 开发者为什么必须掌握 AI 能力?端侧视角下的技术变革
android·ai·aigc
俊哥V11 小时前
每日 AI 研究简报 · 2026-04-09
人工智能·ai
Agent产品评测局11 小时前
企业数据处理自动化落地,抓取分析全流程实现方案 —— 2026企业级智能体选型与技术路径深度解析
运维·人工智能·ai·自动化
大强同学11 小时前
对比 VS Code:Zed 编辑器编码体验全面解析
人工智能·windows·编辑器·ai编程
熊猫钓鱼>_>11 小时前
从“流程固化“到“意图驱动“:大模型调智能体调Skill架构深度解析
ai·架构·大模型·llm·agent·skill·openclaw
xyz_CDragon12 小时前
OpenClaw Skills 完全指南:ClawHub 安装、安全避坑与自定义开发(2026)
人工智能·python·ai·skill·openclaw·clawhub
2501_9481142412 小时前
DeepSeek V4 全面实测:万亿参数开源模型的工程落地与成本推演
人工智能·ai·开源
南师大蒜阿熏呀12 小时前
openclaw 多智能体协同简易版案例实战
ai·openclaw