fix: resolve all post-merge compile errors

- Fix unresolved imports (auto_compaction, AutoCompactionEvent)
- Add Thinking/RedactedThinking match arms
- Fix workspace.dependencies serde_json
- Fix enum exhaustiveness in OutputContentBlock matches
- cargo check --workspace passes
This commit is contained in:
YeonGyu-Kim
2026-04-01 18:57:50 +09:00
parent 5b997e2de2
commit 8ca53dec0d
13 changed files with 99 additions and 52 deletions

View File

@@ -9,7 +9,7 @@ publish.workspace = true
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
runtime = { path = "../runtime" } runtime = { path = "../runtime" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json.workspace = true
tokio = { version = "1", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } tokio = { version = "1", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] }
[lints] [lints]

View File

@@ -9,7 +9,7 @@ pub use client::{
resolve_startup_auth_source, MessageStream, OAuthTokenSet, ProviderClient, resolve_startup_auth_source, MessageStream, OAuthTokenSet, ProviderClient,
}; };
pub use error::ApiError; pub use error::ApiError;
pub use providers::anthropic::{AnthropicClient, AuthSource}; pub use providers::anthropic::{AnthropicClient, AnthropicClient as ApiClient, AuthSource};
pub use providers::openai_compat::{OpenAiCompatClient, OpenAiCompatConfig}; pub use providers::openai_compat::{OpenAiCompatClient, OpenAiCompatConfig};
pub use providers::{ pub use providers::{
detect_provider_kind, max_tokens_for_model, resolve_model_alias, ProviderKind, detect_provider_kind, max_tokens_for_model, resolve_model_alias, ProviderKind,

View File

@@ -660,7 +660,7 @@ mod tests {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new(); static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(())) LOCK.get_or_init(|| Mutex::new(()))
.lock() .lock()
.expect("env lock") .unwrap_or_else(std::sync::PoisonError::into_inner)
} }
fn temp_config_home() -> std::path::PathBuf { fn temp_config_home() -> std::path::PathBuf {
@@ -674,6 +674,14 @@ mod tests {
)) ))
} }
fn cleanup_temp_config_home(config_home: &std::path::Path) {
match std::fs::remove_dir_all(config_home) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => panic!("cleanup temp dir: {error}"),
}
}
fn sample_oauth_config(token_url: String) -> OAuthConfig { fn sample_oauth_config(token_url: String) -> OAuthConfig {
OAuthConfig { OAuthConfig {
client_id: "runtime-client".to_string(), client_id: "runtime-client".to_string(),
@@ -709,7 +717,7 @@ mod tests {
let _guard = env_lock(); let _guard = env_lock();
std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
let error = super::read_api_key().expect_err("missing key should error"); let error = super::read_api_key().expect_err("missing key should error");
assert!(matches!( assert!(matches!(
error, error,
@@ -779,7 +787,7 @@ mod tests {
fn auth_source_from_saved_oauth_when_env_absent() { fn auth_source_from_saved_oauth_when_env_absent() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); let config_home = temp_config_home();
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); std::env::set_var("CLAW_CONFIG_HOME", &config_home);
std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -794,8 +802,8 @@ mod tests {
assert_eq!(auth.bearer_token(), Some("saved-access-token")); assert_eq!(auth.bearer_token(), Some("saved-access-token"));
clear_oauth_credentials().expect("clear credentials"); clear_oauth_credentials().expect("clear credentials");
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); cleanup_temp_config_home(&config_home);
} }
#[test] #[test]
@@ -818,7 +826,7 @@ mod tests {
fn resolve_saved_oauth_token_refreshes_expired_credentials() { fn resolve_saved_oauth_token_refreshes_expired_credentials() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); let config_home = temp_config_home();
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); std::env::set_var("CLAW_CONFIG_HOME", &config_home);
std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -842,15 +850,15 @@ mod tests {
assert_eq!(stored.access_token, "refreshed-token"); assert_eq!(stored.access_token, "refreshed-token");
clear_oauth_credentials().expect("clear credentials"); clear_oauth_credentials().expect("clear credentials");
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); cleanup_temp_config_home(&config_home);
} }
#[test] #[test]
fn resolve_startup_auth_source_uses_saved_oauth_without_loading_config() { fn resolve_startup_auth_source_uses_saved_oauth_without_loading_config() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); let config_home = temp_config_home();
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); std::env::set_var("CLAW_CONFIG_HOME", &config_home);
std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -866,15 +874,15 @@ mod tests {
assert_eq!(auth.bearer_token(), Some("saved-access-token")); assert_eq!(auth.bearer_token(), Some("saved-access-token"));
clear_oauth_credentials().expect("clear credentials"); clear_oauth_credentials().expect("clear credentials");
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); cleanup_temp_config_home(&config_home);
} }
#[test] #[test]
fn resolve_startup_auth_source_errors_when_refreshable_token_lacks_config() { fn resolve_startup_auth_source_errors_when_refreshable_token_lacks_config() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); let config_home = temp_config_home();
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); std::env::set_var("CLAW_CONFIG_HOME", &config_home);
std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -898,15 +906,15 @@ mod tests {
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token")); assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
clear_oauth_credentials().expect("clear credentials"); clear_oauth_credentials().expect("clear credentials");
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); cleanup_temp_config_home(&config_home);
} }
#[test] #[test]
fn resolve_saved_oauth_token_preserves_refresh_token_when_refresh_response_omits_it() { fn resolve_saved_oauth_token_preserves_refresh_token_when_refresh_response_omits_it() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); let config_home = temp_config_home();
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); std::env::set_var("CLAW_CONFIG_HOME", &config_home);
std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -931,8 +939,8 @@ mod tests {
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token")); assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
clear_oauth_credentials().expect("clear credentials"); clear_oauth_credentials().expect("clear credentials");
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); cleanup_temp_config_home(&config_home);
} }
#[test] #[test]

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use api::{ use api::{
AnthropicClient, ApiError, AuthSource, ContentBlockDelta, ContentBlockDeltaEvent, ApiClient, ApiError, AuthSource, ContentBlockDelta, ContentBlockDeltaEvent,
ContentBlockStartEvent, InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest, ContentBlockStartEvent, InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest,
OutputContentBlock, ProviderClient, StreamEvent, ToolChoice, ToolDefinition, OutputContentBlock, ProviderClient, StreamEvent, ToolChoice, ToolDefinition,
}; };
@@ -34,7 +34,7 @@ async fn send_message_posts_json_and_parses_response() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_auth_token(Some("proxy-token".to_string())) .with_auth_token(Some("proxy-token".to_string()))
.with_base_url(server.base_url()); .with_base_url(server.base_url());
let response = client let response = client
@@ -104,7 +104,7 @@ async fn stream_message_parses_sse_events_with_tool_use() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_auth_token(Some("proxy-token".to_string())) .with_auth_token(Some("proxy-token".to_string()))
.with_base_url(server.base_url()); .with_base_url(server.base_url());
let mut stream = client let mut stream = client
@@ -182,7 +182,7 @@ async fn retries_retryable_failures_before_succeeding() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_base_url(server.base_url()) .with_base_url(server.base_url())
.with_retry_policy(2, Duration::from_millis(1), Duration::from_millis(2)); .with_retry_policy(2, Duration::from_millis(1), Duration::from_millis(2));
@@ -256,7 +256,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_base_url(server.base_url()) .with_base_url(server.base_url())
.with_retry_policy(1, Duration::from_millis(1), Duration::from_millis(2)); .with_retry_policy(1, Duration::from_millis(1), Duration::from_millis(2));
@@ -287,7 +287,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() {
#[tokio::test] #[tokio::test]
#[ignore = "requires ANTHROPIC_API_KEY and network access"] #[ignore = "requires ANTHROPIC_API_KEY and network access"]
async fn live_stream_smoke_test() { async fn live_stream_smoke_test() {
let client = AnthropicClient::from_env().expect("ANTHROPIC_API_KEY must be set"); let client = ApiClient::from_env().expect("ANTHROPIC_API_KEY must be set");
let mut stream = client let mut stream = client
.stream_message(&MessageRequest { .stream_message(&MessageRequest {
model: std::env::var("ANTHROPIC_MODEL") model: std::env::var("ANTHROPIC_MODEL")

View File

@@ -11,4 +11,4 @@ workspace = true
[dependencies] [dependencies]
plugins = { path = "../plugins" } plugins = { path = "../plugins" }
runtime = { path = "../runtime" } runtime = { path = "../runtime" }
serde_json = "1" serde_json.workspace = true

View File

@@ -7,7 +7,7 @@ publish.workspace = true
[dependencies] [dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json.workspace = true
[lints] [lints]
workspace = true workspace = true

View File

@@ -1208,6 +1208,8 @@ impl PluginManager {
let install_path = install_root.join(sanitize_plugin_id(&plugin_id)); let install_path = install_root.join(sanitize_plugin_id(&plugin_id));
let now = unix_time_ms(); let now = unix_time_ms();
let existing_record = registry.plugins.get(&plugin_id); let existing_record = registry.plugins.get(&plugin_id);
let installed_copy_is_valid =
install_path.exists() && load_plugin_from_directory(&install_path).is_ok();
let needs_sync = existing_record.is_none_or(|record| { let needs_sync = existing_record.is_none_or(|record| {
record.kind != PluginKind::Bundled record.kind != PluginKind::Bundled
|| record.version != manifest.version || record.version != manifest.version
@@ -1215,6 +1217,7 @@ impl PluginManager {
|| record.description != manifest.description || record.description != manifest.description
|| record.install_path != install_path || record.install_path != install_path
|| !record.install_path.exists() || !record.install_path.exists()
|| !installed_copy_is_valid
}); });
if !needs_sync { if !needs_sync {
@@ -1294,6 +1297,7 @@ impl PluginManager {
fn load_registry(&self) -> Result<InstalledPluginRegistry, PluginError> { fn load_registry(&self) -> Result<InstalledPluginRegistry, PluginError> {
let path = self.registry_path(); let path = self.registry_path();
match fs::read_to_string(&path) { match fs::read_to_string(&path) {
Ok(contents) if contents.trim().is_empty() => Ok(InstalledPluginRegistry::default()),
Ok(contents) => Ok(serde_json::from_str(&contents)?), Ok(contents) => Ok(serde_json::from_str(&contents)?),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => { Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
Ok(InstalledPluginRegistry::default()) Ok(InstalledPluginRegistry::default())
@@ -2003,7 +2007,11 @@ mod tests {
use super::*; use super::*;
fn temp_dir(label: &str) -> PathBuf { fn temp_dir(label: &str) -> PathBuf {
std::env::temp_dir().join(format!("plugins-{label}-{}", unix_time_ms())) let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("time should be after epoch")
.as_nanos();
std::env::temp_dir().join(format!("plugins-{label}-{nanos}"))
} }
fn write_file(path: &Path, contents: &str) { fn write_file(path: &Path, contents: &str) {

View File

@@ -11,7 +11,7 @@ glob = "0.3"
plugins = { path = "../plugins" } plugins = { path = "../plugins" }
regex = "1" regex = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json.workspace = true
tokio = { version = "1", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] } tokio = { version = "1", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] }
walkdir = "2" walkdir = "2"

View File

@@ -31,8 +31,8 @@ pub use config::{
RuntimePluginConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME, RuntimePluginConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME,
}; };
pub use conversation::{ pub use conversation::{
auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent, AutoCompactionEvent, ApiClient, ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError, StaticToolExecutor,
ConversationRuntime, RuntimeError, StaticToolExecutor, ToolError, ToolExecutor, TurnSummary, ToolError, ToolExecutor, TurnSummary,
}; };
pub use file_ops::{ pub use file_ops::{
edit_file, glob_search, grep_search, read_file, write_file, EditFileOutput, GlobSearchOutput, edit_file, glob_search, grep_search, read_file, write_file, EditFileOutput, GlobSearchOutput,

View File

@@ -18,7 +18,7 @@ pulldown-cmark = "0.13"
rustyline = "15" rustyline = "15"
runtime = { path = "../runtime" } runtime = { path = "../runtime" }
plugins = { path = "../plugins" } plugins = { path = "../plugins" }
serde_json = "1" serde_json.workspace = true
syntect = "5" syntect = "5"
tokio = { version = "1", features = ["rt-multi-thread", "time"] } tokio = { version = "1", features = ["rt-multi-thread", "time"] }
tools = { path = "../tools" } tools = { path = "../tools" }

View File

@@ -13,7 +13,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use api::{ use api::{
detect_provider_kind, max_tokens_for_model, resolve_model_alias, resolve_startup_auth_source, detect_provider_kind, max_tokens_for_model, resolve_model_alias, resolve_startup_auth_source,
AnthropicClient, AuthSource, ContentBlockDelta, InputContentBlock, InputMessage, ApiClient as ApiHttpClient, AuthSource, ContentBlockDelta, InputContentBlock, InputMessage,
MessageRequest, MessageResponse, OutputContentBlock, ProviderClient, ProviderKind, MessageRequest, MessageResponse, OutputContentBlock, ProviderClient, ProviderKind,
StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock, StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock,
}; };
@@ -191,7 +191,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
index += 1; index += 1;
} }
"-p" => { "-p" => {
// Claude Code compat: -p "prompt" = one-shot prompt // Claw Code compat: -p "prompt" = one-shot prompt
let prompt = args[index + 1..].join(" "); let prompt = args[index + 1..].join(" ");
if prompt.trim().is_empty() { if prompt.trim().is_empty() {
return Err("-p requires a prompt string".to_string()); return Err("-p requires a prompt string".to_string());
@@ -205,7 +205,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
}); });
} }
"--print" => { "--print" => {
// Claude Code compat: --print makes output non-interactive // Claw Code compat: --print makes output non-interactive
output_format = CliOutputFormat::Text; output_format = CliOutputFormat::Text;
index += 1; index += 1;
} }
@@ -484,7 +484,7 @@ fn run_login() -> Result<(), Box<dyn std::error::Error>> {
return Err(io::Error::new(io::ErrorKind::InvalidData, "oauth state mismatch").into()); return Err(io::Error::new(io::ErrorKind::InvalidData, "oauth state mismatch").into());
} }
let client = AnthropicClient::from_auth(AuthSource::None).with_base_url(api::read_base_url()); let client = ApiHttpClient::from_auth(AuthSource::None).with_base_url(api::read_base_url());
let exchange_request = let exchange_request =
OAuthTokenExchangeRequest::from_config(oauth, code, state, pkce.verifier, redirect_uri); OAuthTokenExchangeRequest::from_config(oauth, code, state, pkce.verifier, redirect_uri);
let runtime = tokio::runtime::Runtime::new()?; let runtime = tokio::runtime::Runtime::new()?;
@@ -899,6 +899,14 @@ fn run_resume_command(
}) })
} }
SlashCommand::Resume { .. } SlashCommand::Resume { .. }
| SlashCommand::Bughunter { .. }
| SlashCommand::Commit
| SlashCommand::Pr { .. }
| SlashCommand::Issue { .. }
| SlashCommand::Ultraplan { .. }
| SlashCommand::Teleport { .. }
| SlashCommand::DebugToolCall
| SlashCommand::Plugins { .. }
| SlashCommand::Model { .. } | SlashCommand::Model { .. }
| SlashCommand::Permissions { .. } | SlashCommand::Permissions { .. }
| SlashCommand::Session { .. } | SlashCommand::Session { .. }
@@ -1153,6 +1161,17 @@ impl LiveCli {
SlashCommand::Session { action, target } => { SlashCommand::Session { action, target } => {
self.handle_session_command(action.as_deref(), target.as_deref())? self.handle_session_command(action.as_deref(), target.as_deref())?
} }
SlashCommand::Bughunter { .. }
| SlashCommand::Commit
| SlashCommand::Pr { .. }
| SlashCommand::Issue { .. }
| SlashCommand::Ultraplan { .. }
| SlashCommand::Teleport { .. }
| SlashCommand::DebugToolCall
| SlashCommand::Plugins { .. } => {
eprintln!("slash command not supported in this REPL yet");
false
}
SlashCommand::Unknown(name) => { SlashCommand::Unknown(name) => {
eprintln!("unknown slash command: /{name}"); eprintln!("unknown slash command: /{name}");
false false
@@ -1437,7 +1456,7 @@ impl LiveCli {
fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> { fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
let cwd = env::current_dir()?; let cwd = env::current_dir()?;
let path = cwd.join(".claude").join("sessions"); let path = cwd.join(".claw").join("sessions");
fs::create_dir_all(&path)?; fs::create_dir_all(&path)?;
Ok(path) Ok(path)
} }
@@ -2094,6 +2113,8 @@ impl ApiClient for ProviderRuntimeClient {
input.push_str(&partial_json); input.push_str(&partial_json);
} }
} }
ContentBlockDelta::ThinkingDelta { .. }
| ContentBlockDelta::SignatureDelta { .. } => {}
}, },
ApiStreamEvent::ContentBlockStop(stop) => { ApiStreamEvent::ContentBlockStop(stop) => {
if let Some(rendered) = markdown_stream.flush(&renderer) { if let Some(rendered) = markdown_stream.flush(&renderer) {
@@ -2595,6 +2616,7 @@ fn push_output_block(
}; };
pending_tools.insert(block_index, (id, name, initial_input)); pending_tools.insert(block_index, (id, name, initial_input));
} }
OutputContentBlock::Thinking { .. } | OutputContentBlock::RedactedThinking { .. } => {}
} }
Ok(()) Ok(())
} }
@@ -3080,7 +3102,7 @@ mod tests {
assert!(help.contains("/clear [--confirm]")); assert!(help.contains("/clear [--confirm]"));
assert!(help.contains("/cost")); assert!(help.contains("/cost"));
assert!(help.contains("/resume <session-path>")); assert!(help.contains("/resume <session-path>"));
assert!(help.contains("/config [env|hooks|model]")); assert!(help.contains("/config [env|hooks|model|plugins]"));
assert!(help.contains("/memory")); assert!(help.contains("/memory"));
assert!(help.contains("/init")); assert!(help.contains("/init"));
assert!(help.contains("/diff")); assert!(help.contains("/diff"));

View File

@@ -11,7 +11,7 @@ plugins = { path = "../plugins" }
runtime = { path = "../runtime" } runtime = { path = "../runtime" }
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json.workspace = true
tokio = { version = "1", features = ["rt-multi-thread"] } tokio = { version = "1", features = ["rt-multi-thread"] }
[lints] [lints]

View File

@@ -1308,6 +1308,12 @@ fn resolve_skill_path(skill: &str) -> Result<std::path::PathBuf, String> {
if let Ok(codex_home) = std::env::var("CODEX_HOME") { if let Ok(codex_home) = std::env::var("CODEX_HOME") {
candidates.push(std::path::PathBuf::from(codex_home).join("skills")); candidates.push(std::path::PathBuf::from(codex_home).join("skills"));
} }
if let Ok(home) = std::env::var("HOME") {
let home = std::path::PathBuf::from(home);
candidates.push(home.join(".agents").join("skills"));
candidates.push(home.join(".config").join("opencode").join("skills"));
candidates.push(home.join(".codex").join("skills"));
}
candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills")); candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills"));
for root in candidates { for root in candidates {
@@ -1537,7 +1543,7 @@ fn allowed_tools_for_subagent(subagent_type: &str) -> BTreeSet<String> {
"SendUserMessage", "SendUserMessage",
"PowerShell", "PowerShell",
], ],
"claude-code-guide" => vec![ "claw-guide" => vec![
"read_file", "read_file",
"glob_search", "glob_search",
"grep_search", "grep_search",
@@ -1716,6 +1722,8 @@ impl ApiClient for ProviderRuntimeClient {
input.push_str(&partial_json); input.push_str(&partial_json);
} }
} }
ContentBlockDelta::ThinkingDelta { .. }
| ContentBlockDelta::SignatureDelta { .. } => {}
}, },
ApiStreamEvent::ContentBlockStop(stop) => { ApiStreamEvent::ContentBlockStop(stop) => {
if let Some((id, name, input)) = pending_tools.remove(&stop.index) { if let Some((id, name, input)) = pending_tools.remove(&stop.index) {
@@ -1861,6 +1869,7 @@ fn push_output_block(
}; };
pending_tools.insert(block_index, (id, name, initial_input)); pending_tools.insert(block_index, (id, name, initial_input));
} }
OutputContentBlock::Thinking { .. } | OutputContentBlock::RedactedThinking { .. } => {}
} }
} }
@@ -2089,7 +2098,7 @@ fn normalize_subagent_type(subagent_type: Option<&str>) -> String {
"verification" | "verificationagent" | "verify" | "verifier" => { "verification" | "verificationagent" | "verify" | "verifier" => {
String::from("Verification") String::from("Verification")
} }
"claudecodeguide" | "claudecodeguideagent" | "guide" => String::from("claude-code-guide"), "clawguide" | "clawguideagent" | "guide" => String::from("claw-guide"),
"statusline" | "statuslinesetup" => String::from("statusline-setup"), "statusline" | "statuslinesetup" => String::from("statusline-setup"),
_ => trimmed.to_string(), _ => trimmed.to_string(),
} }
@@ -2589,16 +2598,16 @@ fn config_file_for_scope(scope: ConfigScope) -> Result<PathBuf, String> {
let cwd = std::env::current_dir().map_err(|error| error.to_string())?; let cwd = std::env::current_dir().map_err(|error| error.to_string())?;
Ok(match scope { Ok(match scope {
ConfigScope::Global => config_home_dir()?.join("settings.json"), ConfigScope::Global => config_home_dir()?.join("settings.json"),
ConfigScope::Settings => cwd.join(".claude").join("settings.local.json"), ConfigScope::Settings => cwd.join(".claw").join("settings.local.json"),
}) })
} }
fn config_home_dir() -> Result<PathBuf, String> { fn config_home_dir() -> Result<PathBuf, String> {
if let Ok(path) = std::env::var("CLAUDE_CONFIG_HOME") { if let Ok(path) = std::env::var("CLAW_CONFIG_HOME") {
return Ok(PathBuf::from(path)); return Ok(PathBuf::from(path));
} }
let home = std::env::var("HOME").map_err(|_| String::from("HOME is not set"))?; let home = std::env::var("HOME").map_err(|_| String::from("HOME is not set"))?;
Ok(PathBuf::from(home).join(".claude")) Ok(PathBuf::from(home).join(".claw"))
} }
fn read_json_object(path: &Path) -> Result<serde_json::Map<String, Value>, String> { fn read_json_object(path: &Path) -> Result<serde_json::Map<String, Value>, String> {
@@ -4043,19 +4052,19 @@ mod tests {
)); ));
let home = root.join("home"); let home = root.join("home");
let cwd = root.join("cwd"); let cwd = root.join("cwd");
std::fs::create_dir_all(home.join(".claude")).expect("home dir"); std::fs::create_dir_all(home.join(".claw")).expect("home dir");
std::fs::create_dir_all(cwd.join(".claude")).expect("cwd dir"); std::fs::create_dir_all(cwd.join(".claw")).expect("cwd dir");
std::fs::write( std::fs::write(
home.join(".claude").join("settings.json"), home.join(".claw").join("settings.json"),
r#"{"verbose":false}"#, r#"{"verbose":false}"#,
) )
.expect("write global settings"); .expect("write global settings");
let original_home = std::env::var("HOME").ok(); let original_home = std::env::var("HOME").ok();
let original_claude_home = std::env::var("CLAUDE_CONFIG_HOME").ok(); let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
let original_dir = std::env::current_dir().expect("cwd"); let original_dir = std::env::current_dir().expect("cwd");
std::env::set_var("HOME", &home); std::env::set_var("HOME", &home);
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
std::env::set_current_dir(&cwd).expect("set cwd"); std::env::set_current_dir(&cwd).expect("set cwd");
let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config"); let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config");
@@ -4088,9 +4097,9 @@ mod tests {
Some(value) => std::env::set_var("HOME", value), Some(value) => std::env::set_var("HOME", value),
None => std::env::remove_var("HOME"), None => std::env::remove_var("HOME"),
} }
match original_claude_home { match original_config_home {
Some(value) => std::env::set_var("CLAUDE_CONFIG_HOME", value), Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
None => std::env::remove_var("CLAUDE_CONFIG_HOME"), None => std::env::remove_var("CLAW_CONFIG_HOME"),
} }
let _ = std::fs::remove_dir_all(root); let _ = std::fs::remove_dir_all(root);
} }