☰
Warp 编排配置中的 Harness 专属模型选择:从 UI 选择器到子进程环境变量的完整链路
2026/10/5 3:05:13 网站建设 项目流程
  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

Warp is an agentic development environment, born out of the terminal.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

导读

在 Warp 的编排(Orchestration)配置界面中,plan card 与 run_agents 确认卡允许用户为子 Agent 选择执行引擎(Harness)与模型。本指南以 QUALITY-643 技术规格为核心,深入剖析该功能的两大痛点——harness 选择器硬编码、模型选择器始终展示 Warp 内部 LLM 目录——以及对应的六项改造方案,覆盖从HarnessAvailabilityModel数据源、共享 picker 逻辑,到 Claude Code 的ANTHROPIC_MODEL环境变量与 Codex 的~/.codex/config.toml模型写入的完整调用链。读完本文,你将掌握 Warp 如何在桌面端与 Web 端保持一致、如何让第三方 harness CLI 真正收到所选模型,以及本地/云执行模式下模型传递的差异与安全约束。


一、背景:编排配置 UI 的 Harness 与模型选择现状

1.1 两张共享逻辑的卡片

编排配置 UI 由两个界面构成:

  • plan card(app/src/ai/document/orchestration_config_block.rs):用于规划阶段配置子 Agent 的执行参数;
  • run_agents 确认卡(app/src/ai/blocklist/inline_action/run_agents_card_view.rs):在真正启动子 Agent 前进行最终确认。

两张卡片共用app/src/ai/blocklist/inline_action/orchestration_controls.rs中的选择器逻辑,包括populate_harness_picker()(约 L380)、populate_model_picker_for_harness()(约 L314)、is_model_in_filtered_choices()(约 L353)、first_filtered_model_id()(约 L368)、sync_picker_selections()(约 L508)与matches_harness_filter()(约 L299)。

1.2 两个核心问题

规格文档明确指出现状的两个缺陷:

  1. Harness 选择器硬编码为[Oz, Claude, Codex]——既没有读取服务端下发的availableHarnesses列表,也没有包含 Gemini,更不尊重管理员在组织设置中的启用/禁用状态;
  2. 模型选择器始终展示 Warp 内部 LLM 目录,并按 Provider 过滤(Anthropic 对应 Claude、OpenAI 对应 Codex)。这些内部 ID(如claude-4-6-opus-high)无法被第三方 harness CLI 识别。服务端其实维护着各 harness 各自的模型目录,桌面客户端也已经在HarnessAvailabilityModel中拉取并缓存了这些数据,但编排 UI 一直没有使用。

1.3 模型 ID 无法送达子进程

除了 UI 层的问题,模型 ID 还存在两条“断链”:

  • Claude Code收不到ANTHROPIC_MODEL环境变量;
  • Codex通过~/.codex/config.toml传递模型,但这只适合云端/远程环境(文件系统隔离),本地子 Agent 绝不能触碰它——详见app/src/pane_group/pane/local_harness_launch.rsL143 附近的注释,本地 Codex 子进程必须依赖用户已有的本地配置,否则会覆盖用户全机器的~/.codex/config.toml并与并行 Agent 产生竞态。

二、数据源:HarnessAvailabilityModel与展示元数据

2.1 服务端目录的客户端缓存

app/src/ai/harness_availability.rs中的HarnessAvailabilityModel是单例(SingletonEntity),负责向服务端拉取并缓存可用 harness 列表:

pub struct HarnessAvailability { pub harness: Harness, pub display_name: String, pub enabled: bool, pub available_models: Vec<HarnessModelInfo>, } pub struct HarnessModelInfo { pub id: String, pub display_name: String, pub reasoning_level: Option<String>, }

其核心 API 包括:

  • available_harnesses()→&[HarnessAvailability]:完整 harness 列表(harness、display_name、enabled、available_models);
  • models_for(harness)→Option<&[HarnessModelInfo]>:某 harness 的模型列表(id、display_name),当列表为空时返回None;
  • is_harness_enabled(harness)→bool:判断某 harness 是否被管理员启用;
  • 变更时发出HarnessAvailabilityEvent::Changed事件。

值得注意的缓存策略(源码依据):

  • 缓存键为"AvailableHarnesses",序列化写入 private user preferences(fn cache/fn get_cached);
  • 服务端尚未响应时使用default_harnesses()兜底:仅含Harness::Oz(enabled=true),保证 UI 在预取阶段即可用;
  • 网络从离线恢复(NetworkStatusEvent::NetworkStatusChanged)、登录完成(AuthManagerEvent::AuthComplete)、团队变更(UserWorkspacesEvent::TeamsChanged)都会触发refresh()重新拉取;
  • refresh()成功后若列表有变化会使缓存失效并发出HarnessAvailabilityEvent::Changed,供 UI 重新填充选择器;
  • normalize_harness_display_names()会强制将 Oz 的 display_name 固定为客户端侧的harness_display::display_name()返回值。

此外,should_show_harness_selector()依据FeatureFlag::AgentHarness且已知 harness 数量 > 1 来决定是否展示 harness 选择器;has_any_enabled_harness()则用于判断是否至少存在一个可用的 harness(影响云 Agent 能否启动,配合cloud_agent_start_blocker的NoEnabledHarnesses判定)。

2.2 展示元数据:图标、品牌色与显示名

app/src/ai/harness_display.rs为每个Harness变体提供统一的展示元数据,确保多个 UI 面(下拉框、会话侧栏)不会漂移:

pub fn display_name(harness: Harness) -> &'static str { match harness { Harness::Oz => "Warp Agent", Harness::Claude => "Claude Code", Harness::OpenCode => "OpenCode", Harness::Gemini => "Gemini CLI", Harness::Codex => "Codex", Harness::Unknown => "Unknown", } }

icon_for()返回对应的品牌图标(Oz→Agent、Claude→ClaudeLogo、Codex→OpenAILogo、Gemini→GeminiLogo),brand_color()返回品牌色调(Claude 橙色、Gemini 蓝色、OpenAI 色),并提供了圆形象底(circle_background)与圆上图标填充色(icon_fill_on_circle)等更精细的渲染辅助。


三、改造方案一:Harness 选择器改为服务端数据驱动

3.1 替换硬编码列表

populate_harness_picker()(orchestration_controls.rs)中原本硬编码的[Harness::Oz, Harness::Claude, Harness::Codex]迭代(L392 附近)将被替换为读取HarnessAvailabilityModel::as_ref(ctx).available_harnesses()。

对每个HarnessAvailability条目:

  • 图标使用harness_display::icon_for()与harness_display::brand_color()(已覆盖包括 Gemini 在内的所有变体);
  • 标签使用harness_display::display_name()(服务端的display_name字段也可用,但客户端名称与服务端返回前保证非空,且二者一致);
  • 若!entry.enabled:渲染为禁用项(不可选中、文字置灰)。MenuItemFieldsAPI 支持.with_disabled(true),并可为禁用原因附加 tooltip;
  • 已启用条目排在禁用条目之前。

从源码看,该改造在populate_harness_picker的 row→menu item 映射中已有雏形:row.disabled_reason为Some(reason)时调用with_disabled(true).with_tooltip(reason),否则注册harness_changed(row.id)选中动作——改造的关键是把 row 的来源从硬编码清单切换为harness_snapshot背后的服务端数据。

3.2 订阅变更事件

两张卡片视图需要同时订阅HarnessAvailabilityEvent::Changed,在服务端列表更新时重新填充 harness 选择器。这一订阅与两张卡片已有的LLMPreferencesEvent::UpdatedAvailableLLMs订阅并存,相互独立。

对应行为清单:PRODUCT.md behaviors 1–5(harness 列表来源、品牌图标、禁用态、排序、选中项被禁用时的保留与提示)。


四、改造方案二:模型选择器切换为 Harness 专属模型 + "Default model" 入口

4.1 按执行模式与 Harness 分支

populate_model_picker_for_harness()需要新增is_local: bool参数(或直接传入当前RunAgentsExecutionMode),使选择器具备执行模式感知能力,替代当前按 Provider 过滤LLMPreferences的逻辑:

let harness = Harness::parse_orchestration_harness(harness_type); match harness { Some(Harness::Oz) | None => { // 维持现状:LLMPreferences 按 Provider 过滤 } Some(Harness::Codex) if is_local => { // 本地 Codex:只有 "Default model" 入口(无法传递模型) } Some(harness) => { // 1. 始终在最前加 "Default model" 入口(值为空字符串) // 2. 读取 HarnessAvailabilityModel::as_ref(ctx).models_for(harness) // 3. Some(models):每个 HarnessModelInfo 追加为菜单项, // display_name 作标签,id 作为 model_changed 动作值 // 4. None:仅展示 "Default model"(加载中/空状态) } }

"Default model" 入口使用标签"Default model"并发出A::model_changed(String::new())(空字符串),与 Web UI 的行为一致(Web 端同样以值""添加该入口)。

4.2 各类 Harness 的模型目录内容

结合 PRODUCT.md 的行为定义:

  • Oz(或未设置):展示 Warp LLM 目录(单 Agent 模式下同一套模型),此行为不得回归;
  • Claude Code:顶部"Default model",其后为服务端提供的 Claude Code 模型目录(如best、opus、sonnet、haiku、opus (1M context)、sonnet (1M context),以及opus 4.7、sonnet 4.6等 pinned 版本);
  • Codex(Cloud 模式):顶部"Default model",其后为服务端提供的 Codex 模型目录(如default、GPT-5.5、GPT-5.4、GPT-5.4 mini)。注意其中default条目与服务端约定为“不写 model 键”,与 "Default model" 的实际效果相同但为 Codex 专属语义;
  • Codex(Local 模式):只展示"Default model"。理由已在 1.3 节说明:Codex CLI 从全局共享的~/.codex/config.toml读取模型,子 Agent 写入会破坏用户已有配置并与并行 Agent 竞争;
  • Gemini(当前对编排禁用):遵循同一模式——顶部"Default model",其后是服务端提供的 Gemini 模型(如有)。

每个模型条目展示服务端目录中的display_name,原始id(如"opus"、"gpt-5.4")则作为选中后的 model_id 存下来并最终传给 harness 进程。harness 模型条目不带 Provider 图标或模型规格侧边信息(这些元数据不存在)。

4.3 同步改造三处辅助函数

  • is_model_in_filtered_choices():非 Oz harness 时,将 model_id 对照HarnessAvailabilityModel::models_for()校验,或接受空字符串(对应 "Default model" 入口);本地 Codex 时仅空字符串有效;
  • first_filtered_model_id():非 Oz harness 时返回Some(String::new())(即 "Default model" 入口)作为默认值;
  • sync_picker_selections():非 Oz harness 时从HarnessAvailabilityModel::models_for()查找 display_name(而非LLMPreferences),并将空 model_id 映射为 "Default model" 标签。

4.4 执行模式切换与 Harness 变更时的重置

当执行模式在 Local 与 Cloud 之间切换时,HarnessChanged/ExecutionModeToggled处理器必须重新填充模型选择器——因为 Codex 的可用模型取决于执行模式。用户切换 harness 时 model_id 会重置(各 harness 模型目录互不相交):非 Oz harness 重置为 "Default model"(空字符串),Oz 重置为第一个可用的 Warp LLM;唯一的例外是空字符串本身("Default model")可以在非 Oz harness 之间保留。

对应行为清单:behaviors 6–10(各 harness 模型目录内容)、11–14(默认值、重置、加载/空状态)、15–16(跨 UI 一致性、sync 逻辑)。


五、改造方案三:双卡片订阅 Harness 模型到达事件

两张卡片视图(orchestration_config_block.rs、run_agents_card_view.rs)已有LLMPreferencesEvent::UpdatedAvailableLLMs订阅,需要新增对HarnessAvailabilityModel的订阅:

  • harness 列表变化时 → 重新填充harness 选择器(behaviors 1–5);
  • harness 模型目录到达时 → 重新填充模型选择器,但仅当当前 harness 为非 Oz 时执行(behavior 15)。

这一机制同时覆盖两种边界情况(PRODUCT.md behaviors 13–14):

  • 加载态:用户切换到非 Oz harness 时模型目录尚未拉取完,选择器只显示 "Default model";目录到达后自动补全完整列表,并保持 "Default model" 处于选中状态;
  • 空态:服务端返回空模型列表时,选择器只显示 "Default model",model_id 保持为空。

六、改造方案四:向本地 Claude Code 子进程传递ANTHROPIC_MODEL

6.1 环境变量注入点

app/src/pane_group/pane/local_harness_launch.rs的prepare_local_harness_child_launch()(L159)新增model_id: Option<String>参数,在基于task_env_vars()构建出env_vars后:

  • Claude:合并harness_model_env_vars(harness, model_id.as_deref())的返回值,即当 model_id 非空时设置ANTHROPIC_MODEL;
  • Codex:本地子进程不注入任何模型覆盖——UI 层保证本地 Codex 的 model_id 为空(behavior 8),因此此处无需动作;现有代码也刻意跳过prepare_codex_environment_config()(L143 附近的注释说明:本地 Codex 必须依赖用户已有的本地认证与会话状态,不能运行共享的 Codex 环境准备流程,否则会向~/.codex/auth.json播种OPENAI_API_KEY并重写全机器的~/.codex/config.toml)。

6.2harness_model_env_vars的底层实现

app/src/ai/agent_sdk/driver/harness/mod.rs的harness_model_env_vars()(L473)实现如下关键语义:

// 文档注释:我们使用 ANTHROPIC_MODEL 环境变量而非 --model CLI 标志, // 因为环境变量是最可靠的机制,且能避免与 Claude Code settings.json 的优先级冲突。 let Some(model_id) = third_party_harness_model_config .map(|config| config.model_id.as_str()) .filter(|id| !id.is_empty()) else { return env_vars; }; match selected_harness { Harness::Claude => { env_vars.insert(OsString::from("ANTHROPIC_MODEL"), OsString::from(model_id)); } Harness::Oz | Harness::OpenCode | Harness::Gemini | Harness::Codex | Harness::Unknown => {} }

即在本地 Claude 子进程启动时(prepare_local_harness_child_launch中env_vars.extend(harness_model_env_vars(harness, harness_model_config.as_ref())),L272–275),模型 ID 会进入隐藏子 pane 的环境;而对 Codex 等其它 harness 返回空 map。

6.3 调用点更新

terminal_pane.rs的launch_local_harness_child()(L1302 附近)中调用prepare_local_harness_child_launch()(L1325 附近)时需传入model_id值。注意terminal_pane.rs中apply_child_model_id_override目前只设置 Oz 的 LLM 偏好——这正是需要被新链路补充/修正的部分。

对应行为清单:behaviors 17(Claude Code 模型送达)、20("Default model" 时不注入任何覆盖)。


七、改造方案五:Cloud/Remote 路径下写入 Codex 的config.toml

7.1 写入规则

app/src/ai/agent_sdk/driver/harness/codex.rs的prepare_codex_config_toml()(L815)新增模型处理:在set_codex_openai_base_url()之后——

  • 若model_id为Some(id)且 id 非空、且不等于"default":写入doc["model"] = toml_edit::value(id);
  • 否则(None、空串或字面量"default"):执行doc.remove("model"),清除任何既有键。

对应常量CODEX_MODEL_KEY: &str = "model"(L580)。set_codex_model()的过滤条件!id.is_empty() && *id != "default"与set_codex_model_reasoning_effort()的reasoning_level处理一同构成完整的模型写入逻辑。

7.2 附带逻辑:模型迁移条目与启动提示抑制

set_codex_model()在写入model键后还会调用set_codex_model_migration()(L924)——Codex 的 TUI 会在会话启动时提示用户升级旧模型,即使model键已被固定。通过在[notice.model_migrations]表中按所选模型 ID 盖章迁移条目(将迁移目标映射到自身,如gpt-5.4 = "gpt-5.4")可抑制该提示。规格注释特别说明:客户端不维护模型版本清单,因此无需随 Anthropic/OpenAI 淘汰模型而发布客户端更新。

7.3 测试证据

codex_tests.rs中已有与新行为完全对应的测试用例:

  • prepare_codex_config_toml_writes_model_when_specified:非 default 模型 ID 写入顶层model键,并断言notice.model_migrations中的自映射条目;
  • prepare_codex_config_toml_writes_model_migration_for_older_model:旧模型同样写入并盖章迁移条目;
  • prepare_codex_config_toml_skips_model_for_default_sentinel:字面量"default"哨兵表示“让 Codex 自选默认模型”,不得写入model键或任何迁移条目;
  • prepare_codex_config_toml_skips_model_when_none:未提供模型 ID 时不写入model键或[notice.model_migrations]条目;
  • prepare_codex_config_toml_writes_model_reasoning_effort_when_specified与..._removes_stale_model_reasoning_effort_when_none:model_reasoning_effort的写入与清理。

规格还要求更新codex_tests.rs:201附近的既有测试——该测试原用于验证预先存在的model键会被保留,需改为验证新的写/删行为;同时所有用例都必须保证既有的非模型键(openai_base_url、projects、mcp_servers)在任何情况下都被保留。

7.4 调用链

codex.rs中build_runner()调用prepare_codex_environment_config()时需透传model_id;local_harness_launch.rs无需改动——本地 Codex 子进程不调用该函数、也不接收模型覆盖。

对应行为清单:behavior 18(Cloud 模式写入 /default不写 / Local 模式不写)。


八、改造方案六:远程启动路径无需改动

远程启动路径已通过StartAgentExecutionMode::Remote { model_id }(run_agents_to_start_agent_mode())将model_id传给服务端。当 UI 开始存储 harness 原生 ID(或 "Default model" 对应的空字符串)后,服务端无需任何翻译即可收到正确值。Claude Code 远程 Agent 的模型由服务端注入(对应ANTHROPIC_MODEL),Codex 远程 Agent 的模型在云端环境内的~/.codex/config.toml写入(云端文件系统隔离,因此写入安全)。

对应行为清单:behaviors 17(remote)、18(remote)。


九、测试与验证策略

9.1 单元测试计划

orchestration_controls 新增测试:

  • harness 选择器从HarnessAvailabilityModel填充;禁用 harness 可见但不可选(behaviors 1–4);
  • populate_model_picker_for_harness(harness="claude"):顶部 "Default model",其后为HarnessAvailabilityModel中的模型(behavior 7);
  • (harness="codex",cloud 模式):顶部 "Default model",其后为 Codex 模型(behavior 8);
  • (harness="codex",local 模式):仅 "Default model"(behavior 8);
  • (harness="oz"):维持 Warp LLM 目录(behavior 6);
  • is_model_in_filtered_choices:非 Oz harness 时 Warp ID 返回 false、空字符串返回 true(behavior 12);
  • first_filtered_model_id:非 Oz harness 返回空字符串(behavior 11);
  • harness 从 Claude(model="opus")切到 Oz:模型重置为第一个 Warp LLM(behavior 12)。

local_harness_launch 新增测试:

  • harness 为 Claude 且提供 model_id 时,ANTHROPIC_MODEL合并进 env_vars(behavior 17);
  • model_id 为 None 或空时,不设置ANTHROPIC_MODEL(behavior 20)。

codex_tests.rs 更新测试(如 7.3 节所列)覆盖写键、"default"删除、None删除与既有键保留(behaviors 18、20)。

orchestration_config_tests:既有测试必须继续通过(behavior 21——matches_active_config对 harness 专属 model_id 与 Warp model_id 一视同仁,按请求 model_id 与配置 model_id 的精确字符串匹配决定是否自动启动)。

9.2 预提交检查

规格要求 PR 前运行cargo fmt、cargo clippy与./script/presubmit。

9.3 手工验证清单

  • 打开 plan card 上的编排配置 → harness 选择器展示 Oz、Claude Code、Codex(服务端若返回 Gemini 也会出现,可能处于禁用态);
  • 选中 Claude Code → 模型选择器顶部为 "Default model",其后为best、opus、sonnet等;
  • 选中 Codex(Cloud 模式)→ 顶部 "Default model",其后为default、GPT-5.5、GPT-5.4等;
  • 选中 Codex(Local 模式)→ 仅 "Default model";
  • 选中 Codex 后将执行模式从 Local 切到 Cloud → 模型选择器补全为完整 Codex 目录;
  • 选中 Oz → 模型选择器回到 Warp LLM 目录;
  • 将 harness 从 Claude(选中 "opus")切到 Oz → 模型重置;
  • 本地启动 Claude Code + "opus" → 子进程环境中存在ANTHROPIC_MODEL=opus;
  • 本地启动 Claude Code + "Default model" → 子进程环境中无ANTHROPIC_MODEL;
  • 云端启动 Codex + "gpt-5.4" → 云环境内~/.codex/config.toml中存在model = "gpt-5.4";
  • 云端启动 Codex + "default" →~/.codex/config.toml中无model键。

十、并行化建议:不推荐并行

规格文档明确建议不进行并行化:各改动间耦合紧密——harness 选择器(改造一)与模型选择器(改造二)在orchestration_controls.rs中共享状态,卡片视图的订阅(改造三)同时依赖两个选择器,启动侧改动(四、五)又依赖从选择器流出的 model_id 格式正确。总范围约 7 个文件、每处中等规模改动,非常适合由单个 Agent 顺序执行。


附:相关源码路径速查

关注点文件
共享选择器逻辑orchestration_controls.rs
plan card 视图orchestration_config_block.rs
run_agents 确认卡run_agents_card_view.rs
Harness 可用性与模型数据harness_availability.rs
Harness 展示元数据harness_display.rs
模型环境变量注入harness/mod.rs
本地 harness 子进程启动local_harness_launch.rs
本地子进程调用点terminal_pane.rs
Codex config.toml 写入codex.rs
Codex 配置测试codex_tests.rs
产品行为规格PRODUCT.md
  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

Warp is an agentic development environment, born out of the terminal.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询