mirror of
https://github.com/block/buzz.git
synced 2026-10-31 22:41:12 +00:00
fix: effort startup default via ACP + honor CLAUDE_CONFIG_DIR for .claude.json
Finding 1 — effort startup-default glue: - Add BUZZ_ACP_EFFORT_LEVEL CLI arg + env var to buzz-acp config - Add startup_effort to PoolStartup and OwnedAgent structs - Extract OwnedAgent::resolve_startup_effort() method that arms desired_effort from startup_effort + capabilities-derived configId - Call resolve_startup_effort() at first session creation after capabilities are populated (replaces inline block) - Inject BUZZ_ACP_EFFORT_LEVEL at spawn in runtime.rs from record.effort_level - Fix stale doc comment in types.rs (was settings.json seeding language) Finding 2 — .claude.json path honors CLAUDE_CONFIG_DIR: - Fix claude.rs read_config_file: resolve .claude.json relative to config_dir when set, same as settings.json (binary does the same) - Fix reader.rs mcp_config_file_path_for_runtime to accept and use claude_config_dir for the claude case - Fix agent_config.rs CLAUDE_CONFIG_DIR lookup to use resolve_effective_agent_env instead of hand-rolled record chain that skipped definition-env tier and baked floor Tests added: - resolve_startup_effort arms desired_effort from startup_effort + configId - resolve_startup_effort does not override live pick - resolve_startup_effort is no-op when startup_effort absent - resolve_startup_effort is no-op when model lacks thought_level Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
parent
c1397bf7dd
commit
51918328cb
@@ -429,6 +429,14 @@ pub struct CliArgs {
|
||||
#[arg(long, env = "BUZZ_ACP_MODEL")]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Persisted effort level value (e.g. "high", "medium", "low") to apply via
|
||||
/// `session/set_config_option` at the first session creation. The configId is
|
||||
/// resolved from the adapter's advertised `thought_level` capability — not
|
||||
/// hardcoded. Non-fatal: if the adapter does not advertise `thought_level`,
|
||||
/// the value is silently ignored and the persisted effort is not overwritten.
|
||||
#[arg(long, env = "BUZZ_ACP_EFFORT_LEVEL")]
|
||||
pub effort_level: Option<String>,
|
||||
|
||||
/// Title for the agent's ACP sessions, passed out-of-band in `session/new`
|
||||
/// `_meta`. Adapters that recognize it name the session after this value;
|
||||
/// others ignore it. Never enters the prompt.
|
||||
@@ -539,6 +547,11 @@ pub struct Config {
|
||||
pub memory_enabled: bool,
|
||||
/// Desired LLM model ID. Applied after every `session_new_full()`.
|
||||
pub model: Option<String>,
|
||||
/// Persisted effort level value (e.g. "high", "medium", "low"). Resolved into a
|
||||
/// real `desired_effort` at the first session creation by pairing with the
|
||||
/// adapter's advertised `thought_level` configId. Non-fatal when absent or
|
||||
/// when the adapter does not advertise `thought_level`.
|
||||
pub effort_level: Option<String>,
|
||||
/// Sanitized session title, sent as `_meta.sessionTitle` on `session/new`.
|
||||
/// `None` when unset or when the configured value sanitized to empty.
|
||||
pub session_title: Option<String>,
|
||||
@@ -1100,6 +1113,7 @@ impl Config {
|
||||
typing_enabled: !args.no_typing,
|
||||
memory_enabled: args.memory && !args.no_memory,
|
||||
model,
|
||||
effort_level: args.effort_level,
|
||||
session_title: args
|
||||
.session_title
|
||||
.as_deref()
|
||||
|
||||
@@ -1908,6 +1908,7 @@ async fn tokio_main() -> Result<()> {
|
||||
desired_model: config.model.clone(),
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: config.effort_level.clone(),
|
||||
agent_name,
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version,
|
||||
@@ -3905,6 +3906,12 @@ struct PoolStartup {
|
||||
extra_env: Vec<(String, String)>,
|
||||
has_generated_codex_config: bool,
|
||||
model: Option<String>,
|
||||
/// Persisted effort value to apply at the first session creation by pairing
|
||||
/// with the adapter's advertised 'thought_level' configId. Carried from
|
||||
/// (the env var). The configId
|
||||
/// is unknown until capabilities arrive at session creation; it is never
|
||||
/// hardcoded in the harness.
|
||||
startup_effort: Option<String>,
|
||||
observer: Option<observer::ObserverHandle>,
|
||||
}
|
||||
|
||||
@@ -3917,6 +3924,7 @@ impl PoolStartup {
|
||||
extra_env: config.persona_env_vars.clone(),
|
||||
has_generated_codex_config: config.has_generated_codex_config,
|
||||
model: config.model.clone(),
|
||||
startup_effort: config.effort_level.clone(),
|
||||
observer,
|
||||
}
|
||||
}
|
||||
@@ -3985,6 +3993,7 @@ async fn initialize_agent_pool(
|
||||
desired_model: startup.model.clone(),
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: startup.startup_effort.clone(),
|
||||
agent_name,
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version,
|
||||
@@ -5439,6 +5448,7 @@ mod error_outcome_emission_tests {
|
||||
desired_model: None,
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: None,
|
||||
agent_name: "unknown".into(),
|
||||
goose_system_prompt_supported: None,
|
||||
// Error branches under test never read this; 1 is the legacy
|
||||
@@ -6878,6 +6888,7 @@ mod control_result_tests {
|
||||
desired_model: None,
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: None,
|
||||
agent_name: "test".into(),
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version: 2,
|
||||
@@ -6996,6 +7007,7 @@ mod control_result_tests {
|
||||
desired_model: None,
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: None,
|
||||
agent_name: "test".into(),
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version: 2,
|
||||
|
||||
@@ -171,6 +171,12 @@ pub struct OwnedAgent {
|
||||
/// `config_id` is the adapter's actual id from `AgentModelCapabilities::thought_level_config_id`;
|
||||
/// it is set here by `set_idle_agent_effort` and never hardcoded in the harness.
|
||||
pub desired_effort: Option<(String, String)>,
|
||||
/// Startup effort value from `BUZZ_ACP_EFFORT_LEVEL` (carried from the Desktop
|
||||
/// record). At the first session creation, if `desired_effort` is None, this
|
||||
/// value is paired with the capabilities-derived `thought_level` configId to
|
||||
/// form `desired_effort`. Non-fatal when absent or when the adapter does not
|
||||
/// advertise `thought_level`.
|
||||
pub startup_effort: Option<String>,
|
||||
/// Normalized agent name from initialize (`agentInfo.name`/`serverInfo.name`).
|
||||
pub agent_name: String,
|
||||
/// Whether Goose accepted its custom system-prompt method. `None` probes on
|
||||
@@ -225,6 +231,26 @@ impl OwnedAgent {
|
||||
self.goose_system_prompt_supported,
|
||||
)
|
||||
}
|
||||
|
||||
/// B5 startup-default: arms `desired_effort` from `startup_effort` + capabilities.
|
||||
///
|
||||
/// Called once at first session creation after capabilities are populated.
|
||||
/// No-op when `desired_effort` is already set (live pick takes precedence),
|
||||
/// when `startup_effort` is absent, or when the adapter does not advertise
|
||||
/// a `thought_level` configId for the current model.
|
||||
pub(crate) fn resolve_startup_effort(&mut self) {
|
||||
if self.desired_effort.is_none() {
|
||||
if let Some(ref value) = self.startup_effort.clone() {
|
||||
if let Some(config_id) = self
|
||||
.model_capabilities
|
||||
.as_ref()
|
||||
.and_then(|c| c.thought_level_config_id.clone())
|
||||
{
|
||||
self.desired_effort = Some((config_id, value.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pool of agents with take-and-return ownership semantics.
|
||||
@@ -1017,6 +1043,11 @@ async fn create_session_and_apply_model(
|
||||
});
|
||||
}
|
||||
|
||||
// B5 startup-default: arm desired_effort from startup_effort + capabilities.
|
||||
// No-op when desired_effort already set (live pick takes precedence) or when
|
||||
// the adapter does not advertise thought_level for this model.
|
||||
agent.resolve_startup_effort();
|
||||
|
||||
// Apply desired_model if set, matching against the fresh session/new response.
|
||||
// Track whether the switch succeeded so session_config_captured reflects
|
||||
// the post-switch state (not the pre-switch desired state).
|
||||
@@ -6115,6 +6146,7 @@ mod tests {
|
||||
desired_model: None,
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: None,
|
||||
agent_name: "unknown".into(),
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version: 2,
|
||||
@@ -6174,6 +6206,7 @@ mod tests {
|
||||
desired_model: None,
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: None,
|
||||
agent_name: "unknown".into(),
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version: 2,
|
||||
@@ -7220,6 +7253,7 @@ mod effort_tests {
|
||||
desired_model: None,
|
||||
model_overridden: false,
|
||||
desired_effort: None,
|
||||
startup_effort: None,
|
||||
agent_name: "test".into(),
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version: 2,
|
||||
@@ -7243,4 +7277,107 @@ mod effort_tests {
|
||||
"desired_effort must be queued"
|
||||
);
|
||||
}
|
||||
|
||||
// ── resolve_startup_effort ────────────────────────────────────────────────
|
||||
|
||||
/// Helper to build a minimal `OwnedAgent` for `resolve_startup_effort` tests.
|
||||
/// The bash subprocess is needed for `AcpClient` construction; none of the
|
||||
/// tests below send any ACP messages.
|
||||
async fn make_agent_for_startup_effort(
|
||||
startup_effort: Option<&str>,
|
||||
thought_level_config_id: Option<&str>,
|
||||
desired_effort: Option<(&str, &str)>,
|
||||
) -> OwnedAgent {
|
||||
let acp = AcpClient::spawn(
|
||||
"bash",
|
||||
&["-c".to_string(), "sleep 10".to_string()],
|
||||
&[],
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.expect("failed to spawn test agent");
|
||||
OwnedAgent {
|
||||
index: 0,
|
||||
acp,
|
||||
state: SessionState::default(),
|
||||
model_capabilities: thought_level_config_id.map(|id| AgentModelCapabilities {
|
||||
config_options_raw: vec![],
|
||||
available_models_raw: None,
|
||||
thought_level_config_id: Some(id.to_string()),
|
||||
}),
|
||||
desired_model: None,
|
||||
model_overridden: false,
|
||||
desired_effort: desired_effort.map(|(id, v)| (id.to_string(), v.to_string())),
|
||||
startup_effort: startup_effort.map(str::to_string),
|
||||
agent_name: "test".into(),
|
||||
goose_system_prompt_supported: None,
|
||||
protocol_version: 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// `resolve_startup_effort` arms `desired_effort` from `startup_effort` and the
|
||||
/// capabilities-derived `thought_level` configId on the first session creation.
|
||||
#[tokio::test]
|
||||
async fn test_resolve_startup_effort_arms_desired_effort_from_startup_value() {
|
||||
let mut agent = make_agent_for_startup_effort(Some("high"), Some("tlevel-id"), None).await;
|
||||
agent.resolve_startup_effort();
|
||||
assert_eq!(
|
||||
agent
|
||||
.desired_effort
|
||||
.as_ref()
|
||||
.map(|(id, v)| (id.as_str(), v.as_str())),
|
||||
Some(("tlevel-id", "high")),
|
||||
"desired_effort must be armed from startup_effort + thought_level configId"
|
||||
);
|
||||
}
|
||||
|
||||
/// `resolve_startup_effort` is a no-op when `desired_effort` is already set
|
||||
/// (a live user pick must not be overridden by the startup default).
|
||||
#[tokio::test]
|
||||
async fn test_resolve_startup_effort_does_not_override_live_pick() {
|
||||
let mut agent = make_agent_for_startup_effort(
|
||||
Some("high"),
|
||||
Some("tlevel-id"),
|
||||
Some(("tlevel-id", "low")), // live pick already present
|
||||
)
|
||||
.await;
|
||||
agent.resolve_startup_effort();
|
||||
assert_eq!(
|
||||
agent
|
||||
.desired_effort
|
||||
.as_ref()
|
||||
.map(|(id, v)| (id.as_str(), v.as_str())),
|
||||
Some(("tlevel-id", "low")),
|
||||
"live desired_effort must not be overridden by startup default"
|
||||
);
|
||||
}
|
||||
|
||||
/// `resolve_startup_effort` is a no-op when no `startup_effort` was provided
|
||||
/// (agent record had no persisted effort level).
|
||||
#[tokio::test]
|
||||
async fn test_resolve_startup_effort_noop_when_startup_effort_absent() {
|
||||
let mut agent = make_agent_for_startup_effort(None, Some("tlevel-id"), None).await;
|
||||
agent.resolve_startup_effort();
|
||||
assert!(
|
||||
agent.desired_effort.is_none(),
|
||||
"desired_effort must stay None when no startup_effort set"
|
||||
);
|
||||
}
|
||||
|
||||
/// `resolve_startup_effort` is a no-op when the adapter has no `thought_level`
|
||||
/// configId (model does not support thinking effort).
|
||||
#[tokio::test]
|
||||
async fn test_resolve_startup_effort_noop_when_model_lacks_thought_level() {
|
||||
let mut agent = make_agent_for_startup_effort(
|
||||
Some("high"),
|
||||
None, // no thought_level_config_id → not supported
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
agent.resolve_startup_effort();
|
||||
assert!(
|
||||
agent.desired_effort.is_none(),
|
||||
"desired_effort must stay None when adapter does not advertise thought_level"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ use crate::{
|
||||
},
|
||||
},
|
||||
current_instance_id, is_reserved_env_key, is_safe_to_reveal, is_well_formed_env_key,
|
||||
known_acp_runtime, load_managed_agents, load_personas, save_managed_agents,
|
||||
sync_managed_agent_processes, AgentDefinition, GlobalAgentConfig, KnownAcpRuntime,
|
||||
ManagedAgentRecord, ManagedAgentRuntimeKey, MAX_ENV_VALUE_BYTES,
|
||||
known_acp_runtime, load_managed_agents, load_personas, resolve_effective_agent_env,
|
||||
save_managed_agents, sync_managed_agent_processes, AgentDefinition, GlobalAgentConfig,
|
||||
KnownAcpRuntime, ManagedAgentRecord, ManagedAgentRuntimeKey, MAX_ENV_VALUE_BYTES,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -295,30 +295,29 @@ pub async fn get_agent_config_surface(
|
||||
let session_cache = state.get_session_cache(&runtime_key);
|
||||
let global = crate::managed_agents::load_global_agent_config(&app).unwrap_or_default();
|
||||
|
||||
// #3493: for claude agents, resolve the settings.json path from the agent's
|
||||
// effective CLAUDE_CONFIG_DIR env var (if set), falling back to ~/.claude/.
|
||||
// We never provision this dir ourselves — we only respect what the user configured.
|
||||
let claude_config_dir: Option<std::path::PathBuf> = if runtime_meta
|
||||
.is_some_and(|m| m.id == "claude")
|
||||
{
|
||||
// Look up CLAUDE_CONFIG_DIR from the effective agent env (record overrides
|
||||
// persona overrides global) — matching the precedence order at spawn.
|
||||
let personas_ref = &personas;
|
||||
let persona = record
|
||||
.persona_id
|
||||
.as_deref()
|
||||
.and_then(|pid| personas_ref.iter().find(|p| p.id == pid));
|
||||
let global_ref = crate::managed_agents::load_global_agent_config(&app).unwrap_or_default();
|
||||
|
||||
record
|
||||
.env_vars
|
||||
.get("CLAUDE_CONFIG_DIR")
|
||||
.or_else(|| persona.and_then(|p| p.env_vars.get("CLAUDE_CONFIG_DIR")))
|
||||
.or_else(|| global_ref.env_vars.get("CLAUDE_CONFIG_DIR"))
|
||||
.map(std::path::PathBuf::from)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// #3493: for claude agents, resolve the settings.json and .claude.json paths
|
||||
// from the agent's effective CLAUDE_CONFIG_DIR env var (if set), falling
|
||||
// back to ~/.claude/ and ~/.claude.json. We never provision this dir
|
||||
// ourselves — we only respect what the user configured.
|
||||
//
|
||||
// Use resolve_effective_agent_env so the lookup covers all tiers (baked
|
||||
// floor → definition → global → persona → record) and cannot diverge from
|
||||
// what the spawned process actually sees.
|
||||
let claude_config_dir: Option<std::path::PathBuf> =
|
||||
if runtime_meta.is_some_and(|m| m.id == "claude") {
|
||||
let effective_env = resolve_effective_agent_env(
|
||||
&record,
|
||||
&personas,
|
||||
runtime_meta,
|
||||
&global,
|
||||
);
|
||||
effective_env
|
||||
.env
|
||||
.get("CLAUDE_CONFIG_DIR")
|
||||
.map(std::path::PathBuf::from)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(resolve_config_surface(
|
||||
record,
|
||||
@@ -540,8 +539,10 @@ fn parse_models(raw: Option<&serde_json::Value>) -> (Vec<AcpModelEntry>, Option<
|
||||
///
|
||||
/// B5: called by the TypeScript observer when `session/set_config_option` for
|
||||
/// the "effort" config option receives a positive acknowledgement. The record
|
||||
/// is updated in-place and persisted; the next spawn will apply this value via
|
||||
/// `session/set_config_option` at session creation (in `create_session_and_apply_model`).
|
||||
/// is updated in-place and persisted. At the next spawn the Desktop injects
|
||||
/// `BUZZ_ACP_EFFORT_LEVEL` so the harness applies this value via
|
||||
/// `session/set_config_option` at session creation, pairing with the
|
||||
/// adapter-advertised `thought_level` configId discovered from capabilities.
|
||||
///
|
||||
/// `effort_level` is the acknowledged value. Pass `None` to clear the
|
||||
/// canonical effort (reverts to adapter default on next spawn).
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
use super::types::{ExtensionEntry, RuntimeFileConfig};
|
||||
|
||||
/// Read Claude Code config from `settings.json` and `~/.claude.json`.
|
||||
/// Read Claude Code config from `settings.json` and `.claude.json`.
|
||||
///
|
||||
/// `config_dir` — when `Some`, reads `settings.json` from that directory
|
||||
/// (the agent's effective `CLAUDE_CONFIG_DIR`) instead of `~/.claude/`.
|
||||
/// MCP servers are always read from `~/.claude.json` regardless of
|
||||
/// `CLAUDE_CONFIG_DIR` — Claude Code does not remap the global MCP config
|
||||
/// file via that variable.
|
||||
/// `config_dir` — when `Some`, reads both `settings.json` and `.claude.json`
|
||||
/// from that directory (the agent's effective `CLAUDE_CONFIG_DIR`).
|
||||
/// Defaults to `~/.claude/settings.json` and `~/.claude.json` when `None`.
|
||||
///
|
||||
/// Both files are resolved from the same directory: the claude 2.1.x binary
|
||||
/// resolves `.claude.json` as `join(process.env.CLAUDE_CONFIG_DIR || homedir(),
|
||||
/// ".claude.json")`, mirroring the `settings.json` resolver. A user-set
|
||||
/// `CLAUDE_CONFIG_DIR` therefore remaps both files — honoring only
|
||||
/// `settings.json` would misrepresent the agent's actual MCP config.
|
||||
pub(super) fn read_config_file(config_dir: Option<&std::path::Path>) -> Option<RuntimeFileConfig> {
|
||||
let home = dirs::home_dir()?;
|
||||
|
||||
// #3493: honor user-set CLAUDE_CONFIG_DIR for settings.json path.
|
||||
// #3493: honor user-set CLAUDE_CONFIG_DIR for both settings.json and
|
||||
// .claude.json — the binary resolves both relative to CLAUDE_CONFIG_DIR.
|
||||
// Panel reflects the actual config the agent reads.
|
||||
let settings_path = config_dir
|
||||
.map(|d| d.join("settings.json"))
|
||||
.unwrap_or_else(|| home.join(".claude").join("settings.json"));
|
||||
|
||||
// MCP config is always ~/ relative — CLAUDE_CONFIG_DIR does not affect it.
|
||||
let mcp_path = home.join(".claude.json");
|
||||
let mcp_path = config_dir
|
||||
.map(|d| d.join(".claude.json"))
|
||||
.unwrap_or_else(|| home.join(".claude.json"));
|
||||
|
||||
let settings = read_json_file(&settings_path);
|
||||
let mcp_config = read_json_file(&mcp_path);
|
||||
|
||||
@@ -154,7 +154,8 @@ pub(crate) fn read_config_surface(
|
||||
let config_file_path = runtime_meta
|
||||
.and_then(|m| m.config_file_path)
|
||||
.map(resolve_tilde);
|
||||
let mcp_config_file_path = runtime_meta.and_then(mcp_config_file_path_for_runtime);
|
||||
let mcp_config_file_path =
|
||||
runtime_meta.and_then(|m| mcp_config_file_path_for_runtime(m, claude_config_dir));
|
||||
let extensions = file_config.extensions.clone();
|
||||
|
||||
let sources = ConfigSourceReport {
|
||||
@@ -211,12 +212,28 @@ pub(crate) fn read_config_surface(
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_config_file_path_for_runtime(runtime: &KnownAcpRuntime) -> Option<String> {
|
||||
fn mcp_config_file_path_for_runtime(
|
||||
runtime: &KnownAcpRuntime,
|
||||
claude_config_dir: Option<&std::path::Path>,
|
||||
) -> Option<String> {
|
||||
match runtime.id {
|
||||
"goose" => {
|
||||
super::goose::goose_config_path().map(|path| path.to_string_lossy().into_owned())
|
||||
}
|
||||
"claude" => dirs::home_dir().map(|h| h.join(".claude.json").to_string_lossy().into_owned()),
|
||||
// #3493: the claude 2.1.x binary resolves .claude.json as
|
||||
// join(CLAUDE_CONFIG_DIR || homedir(), ".claude.json"), so the MCP
|
||||
// config file moves with a user-set CLAUDE_CONFIG_DIR.
|
||||
"claude" => Some(
|
||||
claude_config_dir
|
||||
.map(|d| d.join(".claude.json"))
|
||||
.unwrap_or_else(|| {
|
||||
dirs::home_dir()
|
||||
.map(|h| h.join(".claude.json"))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
),
|
||||
"codex" => {
|
||||
super::codex::codex_config_path().map(|path| path.to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
@@ -760,6 +760,12 @@ pub fn spawn_agent_child(
|
||||
} else {
|
||||
command.env_remove("BUZZ_ACP_MODEL");
|
||||
}
|
||||
// B5: carry persisted effort; harness resolves thought_level configId at first session.
|
||||
if let Some(ref effort) = record.effort_level {
|
||||
command.env("BUZZ_ACP_EFFORT_LEVEL", effort);
|
||||
} else {
|
||||
command.env_remove("BUZZ_ACP_EFFORT_LEVEL");
|
||||
}
|
||||
// Session title for the harness to pass out-of-band on `session/new`. The
|
||||
// adapter names the session after it; it never reaches the prompt, so this
|
||||
// is display metadata only. The spawn-config snapshot records the same
|
||||
@@ -837,10 +843,9 @@ pub fn spawn_agent_child(
|
||||
command.env(key, value);
|
||||
}
|
||||
|
||||
// A1: for local claude agents, ANTHROPIC_MODEL is the single startup model
|
||||
// authority. BUZZ_ACP_MODEL is removed so the harness never sees two
|
||||
// simultaneous model authorities (BUZZ_ACP_MODEL is for live ACP switches
|
||||
// only; ANTHROPIC_MODEL locks the session model in the adapter env).
|
||||
// A1: for local claude agents, ANTHROPIC_MODEL is the single startup model authority.
|
||||
// BUZZ_ACP_MODEL is removed (live ACP switches only; two authorities in the same env
|
||||
// would be ambiguous).
|
||||
if record.backend == super::BackendKind::Local && runtime_meta.is_some_and(|r| r.id == "claude")
|
||||
{
|
||||
apply_claude_model_env(&mut command, effective_model.as_deref());
|
||||
|
||||
@@ -439,7 +439,8 @@ pub struct ManagedAgentRecord {
|
||||
/// deserialize as `None`.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub relay_mesh: Option<RelayMeshConfig>,
|
||||
/// Canonical Claude Code effort level; seeded into the per-agent `settings.json` at spawn.
|
||||
/// Canonical Claude Code effort level. Injected as `BUZZ_ACP_EFFORT_LEVEL` at spawn
|
||||
/// so the harness applies it via `session/set_config_option` at session creation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub effort_level: Option<String>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user