Compare commits

..

1 Commits

Author SHA1 Message Date
minori e35063ee78 deps: update @codemirror/language to 6.12.3
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m19s
CI / Lint & Test (pull_request) Successful in 16m42s
CI / Build Linux (pull_request) Successful in 20m3s
CI / Build Windows (cross-compile) (pull_request) Successful in 30m59s
2026-04-04 07:02:56 -07:00
26 changed files with 71 additions and 1451 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "hikari-desktop",
"version": "1.15.0",
"version": "1.13.0",
"description": "",
"type": "module",
"scripts": {
@@ -47,8 +47,8 @@
"@codemirror/lang-wast": "6.0.2",
"@codemirror/lang-xml": "6.1.0",
"@codemirror/lang-yaml": "6.1.2",
"@codemirror/language": "6.12.2",
"@codemirror/legacy-modes": "6.5.3",
"@codemirror/language": "6.12.3",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/state": "6.5.4",
"@codemirror/theme-one-dark": "6.1.3",
"@codemirror/view": "6.39.15",
+34 -34
View File
@@ -69,11 +69,11 @@ importers:
specifier: 6.1.2
version: 6.1.2
'@codemirror/language':
specifier: 6.12.2
version: 6.12.2
specifier: 6.12.3
version: 6.12.3
'@codemirror/legacy-modes':
specifier: 6.5.3
version: 6.5.3
specifier: 6.5.2
version: 6.5.2
'@codemirror/state':
specifier: 6.5.4
version: 6.5.4
@@ -310,11 +310,11 @@ packages:
'@codemirror/lang-yaml@6.1.2':
resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==}
'@codemirror/language@6.12.2':
resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==}
'@codemirror/language@6.12.3':
resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==}
'@codemirror/legacy-modes@6.5.3':
resolution: {integrity: sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg==}
'@codemirror/legacy-modes@6.5.2':
resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==}
'@codemirror/lint@6.9.3':
resolution: {integrity: sha512-y3YkYhdnhjDBAe0VIA0c4wVoFOvnp8CnAvfLqi0TqotIv92wIlAAP7HELOpLBsKwjAX6W92rSflA6an/2zBvXw==}
@@ -2200,14 +2200,14 @@ snapshots:
'@codemirror/autocomplete@6.20.0':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
'@lezer/common': 1.5.0
'@codemirror/commands@6.10.2':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
'@lezer/common': 1.5.0
@@ -2216,20 +2216,20 @@ snapshots:
dependencies:
'@codemirror/lang-html': 6.4.11
'@codemirror/lang-javascript': 6.2.4
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/common': 1.5.0
'@lezer/highlight': 1.2.3
'@lezer/lr': 1.4.8
'@codemirror/lang-cpp@6.0.3':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/cpp': 1.1.5
'@codemirror/lang-css@6.3.1':
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@lezer/common': 1.5.0
'@lezer/css': 1.3.0
@@ -2237,7 +2237,7 @@ snapshots:
'@codemirror/lang-go@6.0.1':
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@lezer/common': 1.5.0
'@lezer/go': 1.0.1
@@ -2247,7 +2247,7 @@ snapshots:
'@codemirror/autocomplete': 6.20.0
'@codemirror/lang-css': 6.3.1
'@codemirror/lang-javascript': 6.2.4
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
'@lezer/common': 1.5.0
@@ -2256,13 +2256,13 @@ snapshots:
'@codemirror/lang-java@6.0.2':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/java': 1.1.3
'@codemirror/lang-javascript@6.2.4':
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/lint': 6.9.3
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
@@ -2271,13 +2271,13 @@ snapshots:
'@codemirror/lang-json@6.0.2':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/json': 1.0.3
'@codemirror/lang-less@6.0.2':
dependencies:
'@codemirror/lang-css': 6.3.1
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/common': 1.5.0
'@lezer/highlight': 1.2.3
'@lezer/lr': 1.4.8
@@ -2286,7 +2286,7 @@ snapshots:
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/lang-html': 6.4.11
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
'@lezer/common': 1.5.0
@@ -2295,7 +2295,7 @@ snapshots:
'@codemirror/lang-php@6.0.2':
dependencies:
'@codemirror/lang-html': 6.4.11
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@lezer/common': 1.5.0
'@lezer/php': 1.0.5
@@ -2303,20 +2303,20 @@ snapshots:
'@codemirror/lang-python@6.2.1':
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@lezer/common': 1.5.0
'@lezer/python': 1.1.18
'@codemirror/lang-rust@6.0.2':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/rust': 1.0.2
'@codemirror/lang-sass@6.0.2':
dependencies:
'@codemirror/lang-css': 6.3.1
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@lezer/common': 1.5.0
'@lezer/sass': 1.1.0
@@ -2324,7 +2324,7 @@ snapshots:
'@codemirror/lang-sql@6.10.0':
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@lezer/common': 1.5.0
'@lezer/highlight': 1.2.3
@@ -2334,14 +2334,14 @@ snapshots:
dependencies:
'@codemirror/lang-html': 6.4.11
'@codemirror/lang-javascript': 6.2.4
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/common': 1.5.0
'@lezer/highlight': 1.2.3
'@lezer/lr': 1.4.8
'@codemirror/lang-wast@6.0.2':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@lezer/common': 1.5.0
'@lezer/highlight': 1.2.3
'@lezer/lr': 1.4.8
@@ -2349,7 +2349,7 @@ snapshots:
'@codemirror/lang-xml@6.1.0':
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
'@lezer/common': 1.5.0
@@ -2358,14 +2358,14 @@ snapshots:
'@codemirror/lang-yaml@6.1.2':
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@lezer/common': 1.5.0
'@lezer/highlight': 1.2.3
'@lezer/lr': 1.4.8
'@lezer/yaml': 1.0.3
'@codemirror/language@6.12.2':
'@codemirror/language@6.12.3':
dependencies:
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
@@ -2374,9 +2374,9 @@ snapshots:
'@lezer/lr': 1.4.8
style-mod: 4.1.3
'@codemirror/legacy-modes@6.5.3':
'@codemirror/legacy-modes@6.5.2':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/lint@6.9.3':
dependencies:
@@ -2396,7 +2396,7 @@ snapshots:
'@codemirror/theme-one-dark@6.1.3':
dependencies:
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.15
'@lezer/highlight': 1.2.3
@@ -3232,7 +3232,7 @@ snapshots:
dependencies:
'@codemirror/autocomplete': 6.20.0
'@codemirror/commands': 6.10.2
'@codemirror/language': 6.12.2
'@codemirror/language': 6.12.3
'@codemirror/lint': 6.9.3
'@codemirror/search': 6.6.0
'@codemirror/state': 6.5.4
+1 -1
View File
@@ -1648,7 +1648,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hikari-desktop"
version = "1.15.0"
version = "1.13.0"
dependencies = [
"chrono",
"dirs 5.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "hikari-desktop"
version = "1.15.0"
version = "1.13.0"
description = "Hikari - Claude Code Visual Assistant"
authors = ["Naomi Carrigan"]
edition = "2021"
-115
View File
@@ -2618,103 +2618,6 @@ pub async fn open_binary_file(app: AppHandle, path: String) -> Result<(), String
}
}
/// Read `~/.claude/CLAUDE.md` via WSL (for Windows).
/// Returns an empty string if the file does not exist.
#[cfg(target_os = "windows")]
async fn get_global_claude_md_via_wsl() -> Result<String, String> {
use std::process::Command;
let output = Command::new("wsl")
.hide_window()
.args(["-e", "bash", "-l", "-c", "cat ~/.claude/CLAUDE.md 2>/dev/null || true"])
.output()
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
/// Write content to `~/.claude/CLAUDE.md` via WSL (for Windows).
/// Creates the file (and `~/.claude/` directory) if they do not exist.
#[cfg(target_os = "windows")]
async fn save_global_claude_md_via_wsl(content: String) -> Result<(), String> {
use std::io::Write;
use std::process::{Command, Stdio};
let mut child = Command::new("wsl")
.hide_window()
.args([
"-e",
"bash",
"-l",
"-c",
"mkdir -p ~/.claude && cat > ~/.claude/CLAUDE.md",
])
.stdin(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
if let Some(stdin) = child.stdin.as_mut() {
stdin
.write_all(content.as_bytes())
.map_err(|e| format!("Failed to write content to WSL stdin: {}", e))?;
}
let status = child
.wait()
.map_err(|e| format!("Failed to wait for WSL command: {}", e))?;
if !status.success() {
return Err("Failed to save CLAUDE.md via WSL".to_string());
}
Ok(())
}
/// Read the contents of `~/.claude/CLAUDE.md`.
/// Returns an empty string if the file does not exist.
#[tauri::command]
pub async fn get_global_claude_md() -> Result<String, String> {
#[cfg(target_os = "windows")]
return get_global_claude_md_via_wsl().await;
#[cfg(not(target_os = "windows"))]
{
let path = dirs::home_dir()
.ok_or_else(|| "Could not determine home directory".to_string())?
.join(".claude")
.join("CLAUDE.md");
if !path.exists() {
return Ok(String::new());
}
std::fs::read_to_string(&path).map_err(|e| format!("Failed to read CLAUDE.md: {}", e))
}
}
/// Write content to `~/.claude/CLAUDE.md`.
/// Creates the file (and `~/.claude/` directory) if they do not exist.
#[tauri::command]
pub async fn save_global_claude_md(content: String) -> Result<(), String> {
#[cfg(target_os = "windows")]
return save_global_claude_md_via_wsl(content).await;
#[cfg(not(target_os = "windows"))]
{
let claude_dir = dirs::home_dir()
.ok_or_else(|| "Could not determine home directory".to_string())?
.join(".claude");
if !claude_dir.exists() {
std::fs::create_dir_all(&claude_dir)
.map_err(|e| format!("Failed to create ~/.claude directory: {}", e))?;
}
let path = claude_dir.join("CLAUDE.md");
std::fs::write(&path, content).map_err(|e| format!("Failed to write CLAUDE.md: {}", e))
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -3464,22 +3367,4 @@ gitea: gitea-mcp -t stdio (STDIO) - ✓ Connected"#;
let (_, args) = build_wslpath_command(path);
assert_eq!(args[2], path);
}
#[test]
fn test_get_global_claude_md_path_construction() {
// Verify that home_dir() resolves successfully on the test platform
let home = dirs::home_dir();
assert!(home.is_some(), "home_dir() should be available in test environment");
let expected = home.unwrap().join(".claude").join("CLAUDE.md");
assert!(expected.to_string_lossy().contains(".claude"));
assert!(expected.to_string_lossy().ends_with("CLAUDE.md"));
}
#[test]
fn test_save_global_claude_md_dir_path_construction() {
let home = dirs::home_dir();
assert!(home.is_some());
let dir = home.unwrap().join(".claude");
assert!(dir.to_string_lossy().contains(".claude"));
}
}
-73
View File
@@ -54,32 +54,6 @@ pub struct ClaudeStartOptions {
#[serde(default)]
pub session_name: Option<String>,
#[serde(default)]
pub disable_skill_shell_execution: bool,
/// Pass `--bare` flag to suppress UI chrome, useful for scripted headless `-p` calls (v2.1.81+).
#[serde(default)]
pub bare_mode: bool,
/// Controls `showClearContextOnPlanAccept` in `--settings` (v2.1.81+).
/// Defaults to true (matching CLI default). Set to false to suppress the dialog.
#[serde(default = "default_show_clear_context")]
pub show_clear_context_on_plan_accept: bool,
/// Sets `ANTHROPIC_CUSTOM_MODEL_OPTION` env var for custom model providers (v2.1.81+).
#[serde(default)]
pub custom_model_option: Option<String>,
/// Passes `--effort <level>` to set the effort level (v2.1.111+).
/// Valid values: "low", "medium", "high", "xhigh" (Opus 4.7 only), "max". None uses CLI default.
#[serde(default)]
pub effort_level: Option<String>,
/// Sets `ENABLE_PROMPT_CACHING_1H=1` or `FORCE_PROMPT_CACHING_5M=1` env var (v2.1.108+).
/// Values: "1h" → 1-hour TTL, "5m" → force 5-minute TTL. None uses CLI default.
#[serde(default)]
pub prompt_caching_ttl: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -229,33 +203,6 @@ pub struct HikariConfig {
#[serde(default)]
pub model_overrides: Option<HashMap<String, String>>,
/// Prevents skill scripts from executing shell commands (Claude Code v2.1.91+).
/// Passes `"disableSkillShellExecution": true` via the `--settings` flag.
#[serde(default)]
pub disable_skill_shell_execution: bool,
/// Pass `--bare` flag to suppress UI chrome, useful for scripted headless `-p` calls (v2.1.81+).
#[serde(default)]
pub bare_mode: bool,
/// Controls `showClearContextOnPlanAccept` in `--settings` (v2.1.81+).
#[serde(default = "default_show_clear_context")]
pub show_clear_context_on_plan_accept: bool,
/// Sets `ANTHROPIC_CUSTOM_MODEL_OPTION` env var for custom model providers (v2.1.81+).
#[serde(default)]
pub custom_model_option: Option<String>,
/// Passes `--effort <level>` to set the effort level (v2.1.111+).
/// Valid values: "low", "medium", "high", "xhigh" (Opus 4.7 only), "max". None uses CLI default.
#[serde(default)]
pub effort_level: Option<String>,
/// Sets `ENABLE_PROMPT_CACHING_1H=1` or `FORCE_PROMPT_CACHING_5M=1` env var (v2.1.108+).
/// Values: "1h" → 1-hour TTL, "5m" → force 5-minute TTL. None uses CLI default.
#[serde(default)]
pub prompt_caching_ttl: Option<String>,
}
impl Default for HikariConfig {
@@ -307,12 +254,6 @@ impl Default for HikariConfig {
enable_claudeai_mcp_servers: true,
auto_memory_directory: None,
model_overrides: None,
disable_skill_shell_execution: false,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: None,
effort_level: None,
prompt_caching_ttl: None,
}
}
}
@@ -365,10 +306,6 @@ fn default_enable_claudeai_mcp_servers() -> bool {
true
}
fn default_show_clear_context() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BudgetAction {
@@ -468,10 +405,6 @@ mod tests {
assert!(config.enable_claudeai_mcp_servers);
assert!(config.auto_memory_directory.is_none());
assert!(config.model_overrides.is_none());
assert!(!config.disable_skill_shell_execution);
assert!(!config.bare_mode);
assert!(config.show_clear_context_on_plan_accept);
assert!(config.custom_model_option.is_none());
}
#[test]
@@ -526,12 +459,6 @@ mod tests {
"claude-opus-4-6".to_string(),
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1".to_string(),
)])),
disable_skill_shell_execution: true,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: None,
effort_level: None,
prompt_caching_ttl: None,
};
let json = serde_json::to_string(&config).unwrap();
-2
View File
@@ -224,8 +224,6 @@ pub fn run() {
delete_all_drafts,
scan_project,
open_binary_file,
get_global_claude_md,
save_global_claude_md,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
+3 -3
View File
@@ -81,7 +81,7 @@ pub async fn list_sessions(app: AppHandle) -> Result<Vec<SessionListItem>, Strin
let mut items: Vec<SessionListItem> = sessions.iter().map(SessionListItem::from).collect();
// Sort by last activity, most recent first
items.sort_by_key(|b| std::cmp::Reverse(b.last_activity_at));
items.sort_by(|a, b| b.last_activity_at.cmp(&a.last_activity_at));
Ok(items)
}
@@ -132,7 +132,7 @@ pub async fn search_sessions(app: AppHandle, query: String) -> Result<Vec<Sessio
.collect();
// Sort by last activity, most recent first
matching.sort_by_key(|b| std::cmp::Reverse(b.last_activity_at));
matching.sort_by(|a, b| b.last_activity_at.cmp(&a.last_activity_at));
Ok(matching)
}
@@ -348,7 +348,7 @@ mod tests {
];
// Sort by last activity, most recent first (mimics list_sessions behavior)
sessions.sort_by_key(|b| std::cmp::Reverse(b.last_activity_at));
sessions.sort_by(|a, b| b.last_activity_at.cmp(&a.last_activity_at));
assert_eq!(sessions[0].id, "new");
assert_eq!(sessions[1].id, "old");
+7 -30
View File
@@ -86,10 +86,9 @@ impl ContextWarning {
/// Get the context window limit (in tokens) for a given model
fn get_context_window_limit(model: &str) -> u64 {
match model {
// Claude 4.7 - 1M token context window
"claude-opus-4-7" => 1_000_000,
// Claude 4.6 family - 1M token context window
"claude-opus-4-6" | "claude-sonnet-4-6" => 1_000_000,
// Claude 4.6 family
"claude-opus-4-6" => 200_000,
"claude-sonnet-4-6" => 1_000_000, // 1M token context window
// Claude 4.5 family - 200K standard context
"claude-opus-4-5-20251101"
| "claude-sonnet-4-5-20250929"
@@ -491,7 +490,7 @@ fn is_consecutive_day(prev_date: &str, current_date: &str) -> bool {
}
}
// Pricing as of May 2026
// Pricing as of February 2026
// https://platform.claude.com/docs/en/about-claude/models/overview
// Cache pricing: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
pub fn calculate_cost(
@@ -502,17 +501,14 @@ pub fn calculate_cost(
cache_read_tokens: Option<u64>,
) -> f64 {
let (input_price_per_million, output_price_per_million) = match model {
// Current generation (Claude 4.7/4.6/4.5)
"claude-opus-4-7" => (5.0, 25.0),
"claude-sonnet-4-6" => (3.0, 15.0),
"claude-haiku-4-5-20251001" => (1.0, 5.0),
// Previous generation (Claude 4.6)
// Current generation (Claude 4.6)
"claude-opus-4-6" => (5.0, 25.0),
"claude-sonnet-4-6" => (3.0, 15.0),
// Previous generation (Claude 4.5)
"claude-opus-4-5-20251101" => (5.0, 25.0),
"claude-sonnet-4-5-20250929" => (3.0, 15.0),
"claude-haiku-4-5-20251001" => (1.0, 5.0),
// Previous generation (Claude 4.x)
"claude-opus-4-1-20250805" => (15.0, 75.0),
@@ -685,15 +681,6 @@ mod tests {
assert!((cost - 0.165).abs() < 0.0001);
}
#[test]
fn test_cost_calculation_opus_47() {
let cost = calculate_cost(1000, 2000, "claude-opus-4-7", None, None);
// Opus 4.7 pricing: $5/MTok input, $25/MTok output
// 1000 input tokens = $0.005, 2000 output tokens = $0.05
// Total = $0.055
assert!((cost - 0.055).abs() < 0.0001);
}
#[test]
fn test_cost_calculation_opus_45() {
let cost = calculate_cost(1000, 2000, "claude-opus-4-5-20251101", None, None);
@@ -1032,16 +1019,6 @@ mod tests {
// Context Window Tracking tests
// =====================
#[test]
fn test_context_window_limit_opus_47() {
assert_eq!(get_context_window_limit("claude-opus-4-7"), 1_000_000);
}
#[test]
fn test_context_window_limit_opus_46() {
assert_eq!(get_context_window_limit("claude-opus-4-6"), 1_000_000);
}
#[test]
fn test_context_window_limit_claude_4() {
assert_eq!(get_context_window_limit("claude-opus-4-5-20251101"), 200_000);
-318
View File
@@ -95,13 +95,6 @@ pub enum ClaudeMessage {
cwd: Option<String>,
#[serde(default)]
tools: Option<Vec<String>>,
/// Output style hint from Claude Code (v2.1.81+). Informational only.
#[serde(default)]
output_style: Option<String>,
/// Plugin errors from Claude Code (v2.1.111+). Populated when plugins are demoted
/// due to unsatisfied dependencies.
#[serde(default)]
plugin_errors: Option<serde_json::Value>,
},
#[serde(rename = "assistant")]
Assistant {
@@ -126,15 +119,6 @@ pub enum ClaudeMessage {
permission_denials: Option<Vec<PermissionDenial>>,
#[serde(default)]
usage: Option<UsageInfo>,
/// Fast mode state from Claude Code v2.1.81+. Values: "default" | "enabled" | "disabled".
#[serde(default)]
fast_mode_state: Option<String>,
/// Per-model usage breakdown from Claude Code v2.1.81+.
#[serde(default)]
model_usage: Option<serde_json::Value>,
/// Authoritative total cost in USD reported by Claude Code v2.1.81+.
#[serde(default)]
total_cost_usd: Option<f64>,
},
#[serde(rename = "rate_limit_event")]
RateLimitEvent {
@@ -334,50 +318,6 @@ pub struct PostCompactEvent {
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreCompactEvent {
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CwdChangedEvent {
pub cwd: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileChangedEvent {
pub file: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskCreatedEvent {
#[serde(skip_serializing_if = "Option::is_none")]
pub task_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_tool_use_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionDeniedEvent {
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentStartEvent {
pub tool_use_id: String,
@@ -801,262 +741,4 @@ mod tests {
assert!(serialized.contains("\"session_id\":\"sess-xyz\""));
assert!(!serialized.contains("conversation_id"));
}
#[test]
fn test_pre_compact_event_serialization() {
let event = PreCompactEvent {
session_id: Some("sess-abc".to_string()),
conversation_id: Some("conv-123".to_string()),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"session_id\":\"sess-abc\""));
assert!(serialized.contains("\"conversation_id\":\"conv-123\""));
}
#[test]
fn test_pre_compact_event_omits_none_fields() {
let event = PreCompactEvent {
session_id: None,
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(!serialized.contains("session_id"));
assert!(!serialized.contains("conversation_id"));
}
#[test]
fn test_cwd_changed_event_serialization() {
let event = CwdChangedEvent {
cwd: "/home/naomi/code/my-project".to_string(),
conversation_id: Some("conv-abc".to_string()),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"cwd\":\"/home/naomi/code/my-project\""));
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
}
#[test]
fn test_cwd_changed_event_omits_none_fields() {
let event = CwdChangedEvent {
cwd: "/tmp/workspace".to_string(),
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"cwd\":\"/tmp/workspace\""));
assert!(!serialized.contains("conversation_id"));
}
#[test]
fn test_file_changed_event_serialization() {
let event = FileChangedEvent {
file: "/home/naomi/code/my-project/src/main.rs".to_string(),
conversation_id: Some("conv-abc".to_string()),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"file\":\"/home/naomi/code/my-project/src/main.rs\""));
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
}
#[test]
fn test_file_changed_event_omits_none_fields() {
let event = FileChangedEvent {
file: "/tmp/test.txt".to_string(),
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"file\":\"/tmp/test.txt\""));
assert!(!serialized.contains("conversation_id"));
}
#[test]
fn test_task_created_event_serialization() {
let event = TaskCreatedEvent {
task_id: Some("task-abc123".to_string()),
description: Some("Explore the codebase".to_string()),
parent_tool_use_id: Some("toolu_xyz".to_string()),
conversation_id: Some("conv-abc".to_string()),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"task_id\":\"task-abc123\""));
assert!(serialized.contains("\"description\":\"Explore the codebase\""));
assert!(serialized.contains("\"parent_tool_use_id\":\"toolu_xyz\""));
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
}
#[test]
fn test_task_created_event_omits_none_fields() {
let event = TaskCreatedEvent {
task_id: None,
description: None,
parent_tool_use_id: None,
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert_eq!(serialized, "{}");
}
#[test]
fn test_task_created_event_partial_fields() {
let event = TaskCreatedEvent {
task_id: Some("task-001".to_string()),
description: None,
parent_tool_use_id: None,
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"task_id\":\"task-001\""));
assert!(!serialized.contains("description"));
assert!(!serialized.contains("parent_tool_use_id"));
assert!(!serialized.contains("conversation_id"));
}
#[test]
fn test_permission_denied_event_serialization() {
let event = PermissionDeniedEvent {
tool_name: Some("Bash".to_string()),
reason: Some("Tool not in allow list".to_string()),
conversation_id: Some("conv-abc".to_string()),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"tool_name\":\"Bash\""));
assert!(serialized.contains("\"reason\":\"Tool not in allow list\""));
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
}
#[test]
fn test_permission_denied_event_omits_none_fields() {
let event = PermissionDeniedEvent {
tool_name: None,
reason: None,
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert_eq!(serialized, "{}");
}
#[test]
fn test_permission_denied_event_partial_fields() {
let event = PermissionDeniedEvent {
tool_name: Some("Edit".to_string()),
reason: None,
conversation_id: None,
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(serialized.contains("\"tool_name\":\"Edit\""));
assert!(!serialized.contains("reason"));
assert!(!serialized.contains("conversation_id"));
}
#[test]
fn test_system_init_with_output_style() {
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1","output_style":"auto"}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::System { output_style, .. } = msg {
assert_eq!(output_style, Some("auto".to_string()));
} else {
panic!("Expected System variant");
}
}
#[test]
fn test_system_init_without_output_style() {
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1"}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::System { output_style, .. } = msg {
assert!(output_style.is_none());
} else {
panic!("Expected System variant");
}
}
#[test]
fn test_system_init_with_plugin_errors() {
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1","plugin_errors":["Plugin 'foo' requires 'bar' which is not installed","Plugin 'baz' failed to load"]}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::System { plugin_errors, .. } = msg {
let errors = plugin_errors.expect("plugin_errors should be present");
let arr = errors.as_array().expect("plugin_errors should be an array");
assert_eq!(arr.len(), 2);
assert_eq!(arr[0].as_str(), Some("Plugin 'foo' requires 'bar' which is not installed"));
} else {
panic!("Expected System variant");
}
}
#[test]
fn test_system_init_without_plugin_errors() {
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1"}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::System { plugin_errors, .. } = msg {
assert!(plugin_errors.is_none());
} else {
panic!("Expected System variant");
}
}
#[test]
fn test_system_init_with_empty_plugin_errors() {
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1","plugin_errors":[]}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::System { plugin_errors, .. } = msg {
let errors = plugin_errors.expect("plugin_errors should be present");
let arr = errors.as_array().expect("plugin_errors should be an array");
assert!(arr.is_empty());
} else {
panic!("Expected System variant");
}
}
#[test]
fn test_result_message_with_fast_mode_state() {
let json = r#"{"type":"result","subtype":"success","fast_mode_state":"enabled"}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::Result { fast_mode_state, .. } = msg {
assert_eq!(fast_mode_state, Some("enabled".to_string()));
} else {
panic!("Expected Result variant");
}
}
#[test]
fn test_result_message_with_total_cost_usd() {
let json = r#"{"type":"result","subtype":"success","total_cost_usd":0.05}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::Result { total_cost_usd, .. } = msg {
assert!((total_cost_usd.unwrap() - 0.05).abs() < f64::EPSILON);
} else {
panic!("Expected Result variant");
}
}
#[test]
fn test_result_message_without_new_fields() {
let json = r#"{"type":"result","subtype":"success"}"#;
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
if let ClaudeMessage::Result {
fast_mode_state,
model_usage,
total_cost_usd,
..
} = msg
{
assert!(fast_mode_state.is_none());
assert!(model_usage.is_none());
assert!(total_cost_usd.is_none());
} else {
panic!("Expected Result variant");
}
}
}
+12 -548
View File
@@ -15,11 +15,10 @@ use crate::process_ext::HideWindow;
use crate::stats::{calculate_cost, StatsUpdateEvent, UsageStats};
use crate::types::{
AgentEndEvent, AgentStartEvent, CharacterState, ClaudeMessage, ConnectionEvent,
ConnectionStatus, ContentBlock, CwdChangedEvent, ElicitationEvent, ElicitationResultEvent,
FileChangedEvent, MessageCost, OutputEvent, PermissionDeniedEvent, PermissionPromptEvent,
PermissionPromptEventItem, PostCompactEvent, PreCompactEvent, QuestionOption, SessionEvent, StateChangeEvent,
StopFailureEvent, TaskCreatedEvent, TodoItem, TodoUpdateEvent, UserQuestionEvent,
WorkingDirectoryEvent, WorktreeEvent, WorktreeInfo,
ConnectionStatus, ContentBlock, ElicitationEvent, ElicitationResultEvent, MessageCost,
OutputEvent, PermissionPromptEvent, PermissionPromptEventItem, QuestionOption, SessionEvent,
PostCompactEvent, StateChangeEvent, StopFailureEvent, TodoItem, TodoUpdateEvent,
UserQuestionEvent, WorkingDirectoryEvent, WorktreeEvent, WorktreeInfo,
};
use parking_lot::RwLock;
use std::cell::RefCell;
@@ -37,10 +36,7 @@ struct PendingToolUse {
tool_input: serde_json::Value,
}
// "Monitor" added in Claude Code v2.1.98 — it streams events and is observational in nature,
// so it maps to the Searching character state. It may appear as name "Monitor" in tool_use
// blocks (confirmed by CLI source inspection; it is a local_bash with kind="monitor" internally).
const SEARCH_TOOLS: [&str; 6] = ["Read", "Glob", "Grep", "WebSearch", "WebFetch", "Monitor"];
const SEARCH_TOOLS: [&str; 5] = ["Read", "Glob", "Grep", "WebSearch", "WebFetch"];
const CODING_TOOLS: [&str; 3] = ["Edit", "Write", "NotebookEdit"];
fn detect_wsl() -> bool {
@@ -303,19 +299,6 @@ impl WslBridge {
cmd.arg("--worktree");
}
// Add bare flag if requested (v2.1.81+)
if options.bare_mode {
cmd.arg("--bare");
}
// Add effort level if specified (v2.1.111+)
if let Some(ref level) = options.effort_level {
if !level.is_empty() {
cmd.arg("--effort");
cmd.arg(level);
}
}
// Pass combined settings via --settings flag if any settings are specified
{
let has_memory_dir = options
@@ -328,13 +311,8 @@ impl WslBridge {
.as_ref()
.map(|m| !m.is_empty())
.unwrap_or(false);
let suppress_clear_context = !options.show_clear_context_on_plan_accept;
if has_memory_dir
|| has_overrides
|| options.disable_skill_shell_execution
|| suppress_clear_context
{
if has_memory_dir || has_overrides {
let mut settings = serde_json::Map::new();
if let Some(ref dir) = options.auto_memory_directory {
if !dir.is_empty() {
@@ -351,18 +329,6 @@ impl WslBridge {
}
}
}
if options.disable_skill_shell_execution {
settings.insert(
"disableSkillShellExecution".to_string(),
serde_json::Value::Bool(true),
);
}
if suppress_clear_context {
settings.insert(
"showClearContextOnPlanAccept".to_string(),
serde_json::Value::Bool(false),
);
}
if let Ok(settings_json) = serde_json::to_string(&settings) {
cmd.args(["--settings", &settings_json]);
}
@@ -403,22 +369,6 @@ impl WslBridge {
cmd.env("ENABLE_CLAUDEAI_MCP_SERVERS", "false");
}
// Set custom model option if specified (v2.1.81+)
if let Some(ref custom_opt) = options.custom_model_option {
if !custom_opt.is_empty() {
cmd.env("ANTHROPIC_CUSTOM_MODEL_OPTION", custom_opt);
}
}
// Set prompt caching TTL env var if specified (v2.1.108+)
if let Some(ref ttl) = options.prompt_caching_ttl {
match ttl.as_str() {
"1h" => { cmd.env("ENABLE_PROMPT_CACHING_1H", "1"); }
"5m" => { cmd.env("FORCE_PROMPT_CACHING_5M", "1"); }
_ => {}
}
}
cmd
} else {
// Running on Windows - use wsl with bash login shell to ensure PATH is loaded
@@ -486,23 +436,6 @@ impl WslBridge {
claude_cmd.push_str("ENABLE_CLAUDEAI_MCP_SERVERS=false ");
}
// Set custom model option if specified (v2.1.81+)
if let Some(ref custom_opt) = options.custom_model_option {
if !custom_opt.is_empty() {
let escaped = custom_opt.replace('\'', "'\\''");
claude_cmd.push_str(&format!("ANTHROPIC_CUSTOM_MODEL_OPTION='{}' ", escaped));
}
}
// Set prompt caching TTL env var if specified (v2.1.108+)
if let Some(ref ttl) = options.prompt_caching_ttl {
match ttl.as_str() {
"1h" => { claude_cmd.push_str("ENABLE_PROMPT_CACHING_1H=1 "); }
"5m" => { claude_cmd.push_str("FORCE_PROMPT_CACHING_5M=1 "); }
_ => {}
}
}
claude_cmd.push_str(
"claude --output-format stream-json --input-format stream-json --verbose",
);
@@ -553,18 +486,6 @@ impl WslBridge {
claude_cmd.push_str(" --worktree");
}
// Add bare flag if requested (v2.1.81+)
if options.bare_mode {
claude_cmd.push_str(" --bare");
}
// Add effort level if specified (v2.1.111+)
if let Some(ref level) = options.effort_level {
if !level.is_empty() {
claude_cmd.push_str(&format!(" --effort {}", level));
}
}
// Pass combined settings via --settings flag if any settings are specified
{
let has_memory_dir = options
@@ -577,13 +498,8 @@ impl WslBridge {
.as_ref()
.map(|m| !m.is_empty())
.unwrap_or(false);
let suppress_clear_context = !options.show_clear_context_on_plan_accept;
if has_memory_dir
|| has_overrides
|| options.disable_skill_shell_execution
|| suppress_clear_context
{
if has_memory_dir || has_overrides {
let mut settings = serde_json::Map::new();
if let Some(ref dir) = options.auto_memory_directory {
if !dir.is_empty() {
@@ -600,18 +516,6 @@ impl WslBridge {
}
}
}
if options.disable_skill_shell_execution {
settings.insert(
"disableSkillShellExecution".to_string(),
serde_json::Value::Bool(true),
);
}
if suppress_clear_context {
settings.insert(
"showClearContextOnPlanAccept".to_string(),
serde_json::Value::Bool(false),
);
}
if let Ok(settings_json) = serde_json::to_string(&settings) {
let escaped = settings_json.replace('\'', "'\\''");
claude_cmd.push_str(&format!(" --settings '{}'", escaped));
@@ -920,9 +824,10 @@ impl WslBridge {
let stats = self.stats.read();
for tool_name in &tools {
if let Some(tool_stats) = stats.session_tools_usage.get(tool_name) {
if let Some(avg_tokens) = (tool_stats.estimated_input_tokens + tool_stats.estimated_output_tokens)
.checked_div(tool_stats.call_count)
{
if tool_stats.call_count > 0 {
// Use session average tokens per call for this tool
let avg_tokens = (tool_stats.estimated_input_tokens + tool_stats.estimated_output_tokens)
/ tool_stats.call_count;
tool_overhead_tokens += avg_tokens;
tracing::info!("[COST ESTIMATION] Tool {} average: {} tokens", tool_name, avg_tokens);
}
@@ -1172,12 +1077,7 @@ fn handle_stderr(
let is_elicitation = line.contains("[Elicitation Hook]");
let is_elicitation_result = line.contains("[ElicitationResult Hook]");
let is_stop_failure = line.contains("[StopFailure Hook]");
let is_pre_compact = line.contains("[PreCompact Hook]");
let is_post_compact = line.contains("[PostCompact Hook]");
let is_cwd_changed = line.contains("[CwdChanged Hook]");
let is_file_changed = line.contains("[FileChanged Hook]");
let is_task_created = line.contains("[TaskCreated Hook]");
let is_permission_denied = line.contains("[PermissionDenied Hook]");
let line_type = if is_worktree_create || is_worktree_remove {
"worktree"
@@ -1187,16 +1087,8 @@ fn handle_stderr(
"elicitation"
} else if is_stop_failure {
"error"
} else if is_pre_compact || is_post_compact {
} else if is_post_compact {
"compact-prompt"
} else if is_cwd_changed {
"cwd-changed"
} else if is_file_changed {
"file-changed"
} else if is_task_created {
"task-created"
} else if is_permission_denied {
"permission-denied"
} else {
"error"
};
@@ -1313,28 +1205,6 @@ fn handle_stderr(
parent_tool_use_id: None,
},
);
} else if is_pre_compact {
let data = parse_pre_compact_hook(&line);
let _ = app.emit(
"claude:pre-compact",
PreCompactEvent {
session_id: data.session_id,
conversation_id: conversation_id.clone(),
},
);
let _ = app.emit(
"claude:output",
OutputEvent {
line_type: "compact-prompt".to_string(),
content: "Compacting context — conversation history is being summarised to free up space.".to_string(),
tool_name: None,
conversation_id: conversation_id.clone(),
cost: None,
parent_tool_use_id: None,
},
);
} else if is_post_compact {
let data = parse_post_compact_hook(&line);
@@ -1357,124 +1227,6 @@ fn handle_stderr(
parent_tool_use_id: None,
},
);
} else if is_cwd_changed {
let data = parse_cwd_changed_hook(&line);
let friendly_content =
format!("Working directory changed to: {}", data.cwd);
let _ = app.emit(
"claude:cwd-changed",
CwdChangedEvent {
cwd: data.cwd,
conversation_id: conversation_id.clone(),
},
);
let _ = app.emit(
"claude:output",
OutputEvent {
line_type: "cwd-changed".to_string(),
content: friendly_content,
tool_name: None,
conversation_id: conversation_id.clone(),
cost: None,
parent_tool_use_id: None,
},
);
} else if is_file_changed {
let data = parse_file_changed_hook(&line);
let friendly_content = if data.file.is_empty() {
"File changed".to_string()
} else {
format!("File changed: {}", data.file)
};
let _ = app.emit(
"claude:file-changed",
FileChangedEvent {
file: data.file,
conversation_id: conversation_id.clone(),
},
);
let _ = app.emit(
"claude:output",
OutputEvent {
line_type: "file-changed".to_string(),
content: friendly_content,
tool_name: None,
conversation_id: conversation_id.clone(),
cost: None,
parent_tool_use_id: None,
},
);
} else if is_task_created {
let data = parse_task_created_hook(&line);
let friendly_content = match data.description.as_deref() {
Some(desc) if !desc.is_empty() => format!("Task created: {}", desc),
_ => "Task created".to_string(),
};
let _ = app.emit(
"claude:task-created",
TaskCreatedEvent {
task_id: data.task_id,
description: data.description,
parent_tool_use_id: data.parent_tool_use_id,
conversation_id: conversation_id.clone(),
},
);
let _ = app.emit(
"claude:output",
OutputEvent {
line_type: "task-created".to_string(),
content: friendly_content,
tool_name: None,
conversation_id: conversation_id.clone(),
cost: None,
parent_tool_use_id: None,
},
);
} else if is_permission_denied {
let data = parse_permission_denied_hook(&line);
let friendly_content = match (data.tool_name.as_deref(), data.reason.as_deref()) {
(Some(tool), Some(reason)) => {
format!("Permission denied for {}: {}", tool, reason)
}
(Some(tool), None) => format!("Permission denied for {}", tool),
_ => "Permission denied".to_string(),
};
let _ = app.emit(
"claude:state-change",
StateChangeEvent {
state: CharacterState::Permission,
tool_name: data.tool_name.clone(),
conversation_id: conversation_id.clone(),
},
);
let _ = app.emit(
"claude:permission-denied",
PermissionDeniedEvent {
tool_name: data.tool_name,
reason: data.reason,
conversation_id: conversation_id.clone(),
},
);
let _ = app.emit(
"claude:output",
OutputEvent {
line_type: "permission-denied".to_string(),
content: friendly_content,
tool_name: None,
conversation_id: conversation_id.clone(),
cost: None,
parent_tool_use_id: None,
},
);
} else {
let _ = app.emit(
"claude:output",
@@ -1678,16 +1430,6 @@ fn build_stop_failure_message(data: &StopFailureData) -> String {
}
}
#[derive(Debug)]
struct PreCompactData {
session_id: Option<String>,
}
fn parse_pre_compact_hook(line: &str) -> PreCompactData {
let session_id = extract_debug_string_value(line, "session_id");
PreCompactData { session_id }
}
#[derive(Debug)]
struct PostCompactData {
session_id: Option<String>,
@@ -1698,85 +1440,6 @@ fn parse_post_compact_hook(line: &str) -> PostCompactData {
PostCompactData { session_id }
}
#[derive(Debug)]
struct CwdChangedData {
cwd: String,
}
fn parse_cwd_changed_hook(line: &str) -> CwdChangedData {
let cwd = extract_quoted_value(line, "cwd")
.or_else(|| extract_quoted_value(line, "path"))
.or_else(|| {
line.split("[CwdChanged Hook]")
.nth(1)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
})
.unwrap_or_default();
CwdChangedData { cwd }
}
#[derive(Debug)]
struct FileChangedData {
file: String,
}
fn parse_file_changed_hook(line: &str) -> FileChangedData {
let file = extract_quoted_value(line, "file")
.or_else(|| extract_quoted_value(line, "path"))
.or_else(|| {
line.split("[FileChanged Hook]")
.nth(1)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
})
.unwrap_or_default();
FileChangedData { file }
}
#[derive(Debug)]
struct TaskCreatedData {
task_id: Option<String>,
description: Option<String>,
parent_tool_use_id: Option<String>,
}
fn parse_task_created_hook(line: &str) -> TaskCreatedData {
let task_id = extract_debug_string_value(line, "task_id")
.or_else(|| extract_quoted_value(line, "task_id"));
let description = extract_quoted_value(line, "description")
.or_else(|| extract_quoted_value(line, "prompt"));
let parent_tool_use_id = if line.contains("parent_tool_use_id=Some") {
line.split("parent_tool_use_id=Some(\"")
.nth(1)
.and_then(|s| s.split('"').next())
.map(|s| s.to_string())
} else {
None
};
TaskCreatedData { task_id, description, parent_tool_use_id }
}
#[derive(Debug)]
struct PermissionDeniedData {
tool_name: Option<String>,
reason: Option<String>,
}
fn parse_permission_denied_hook(line: &str) -> PermissionDeniedData {
let tool_name = extract_quoted_value(line, "tool_name")
.or_else(|| extract_quoted_value(line, "tool"))
.or_else(|| extract_debug_string_value(line, "tool_name"));
let reason = extract_quoted_value(line, "reason")
.or_else(|| extract_quoted_value(line, "message"));
PermissionDeniedData { tool_name, reason }
}
/// Extracts a double-quoted string value from a `key="value"` pair in a hook line.
/// Handles escape sequences within the quoted value.
fn extract_quoted_value(line: &str, key: &str) -> Option<String> {
@@ -1849,7 +1512,6 @@ fn process_json_line(
subtype,
session_id,
cwd,
plugin_errors,
..
} => {
if subtype == "init" {
@@ -1874,31 +1536,6 @@ fn process_json_line(
},
);
}
// Warn about any plugins that failed to load (v2.1.111+)
if let Some(errors) = plugin_errors {
if let Some(arr) = errors.as_array() {
for error in arr {
let msg = if let Some(s) = error.as_str() {
s.to_string()
} else {
error.to_string()
};
let _ = app.emit(
"claude:output",
OutputEvent {
line_type: "error".to_string(),
content: format!("Plugin error: {}", msg),
tool_name: None,
conversation_id: conversation_id.clone(),
cost: None,
parent_tool_use_id: None,
},
);
}
}
}
emit_state_change(app, CharacterState::Idle, None, conversation_id.clone());
}
}
@@ -2260,9 +1897,6 @@ fn process_json_line(
usage,
duration_ms,
num_turns,
fast_mode_state,
model_usage,
total_cost_usd,
} => {
tracing::info!(
"Received Result message: subtype={}, has_denials={}, denial_count={:?}",
@@ -2347,25 +1981,6 @@ fn process_json_line(
});
}
// Log fast mode state if present (v2.1.81+)
if let Some(ref state) = fast_mode_state {
tracing::debug!("Fast mode state: {}", state);
}
// Log per-model usage if available (v2.1.81+)
if let Some(ref model_usage_val) = model_usage {
if let Some(map) = model_usage_val.as_object() {
for (model_name, _usage_val) in map {
tracing::debug!("Per-model usage logged for: {}", model_name);
}
}
}
// Log authoritative cost from Claude Code if available (v2.1.81+)
if let Some(auth_cost) = total_cost_usd {
tracing::debug!("Authoritative total cost from Claude Code: ${:.6}", auth_cost);
}
// Clear tracking fields since request completed successfully
{
let mut stats_guard = stats.write();
@@ -2931,11 +2546,6 @@ mod tests {
get_tool_state("WebFetch"),
CharacterState::Searching
));
// Monitor tool added in v2.1.98 — observational/streaming, maps to Searching
assert!(matches!(
get_tool_state("Monitor"),
CharacterState::Searching
));
}
#[test]
@@ -3978,152 +3588,6 @@ mod tests {
assert_eq!(data.session_id, None);
}
#[test]
fn test_parse_pre_compact_hook_with_session_id() {
let line =
r#"[PreCompact Hook] session_id=Some("sess-abc123"), conversation_id=Some("conv-xyz")"#;
let data = parse_pre_compact_hook(line);
assert_eq!(data.session_id, Some("sess-abc123".to_string()));
}
#[test]
fn test_parse_pre_compact_hook_without_session_id() {
let line = "[PreCompact Hook] session_id=None";
let data = parse_pre_compact_hook(line);
assert_eq!(data.session_id, None);
}
#[test]
fn test_parse_pre_compact_hook_empty_line() {
let line = "[PreCompact Hook]";
let data = parse_pre_compact_hook(line);
assert_eq!(data.session_id, None);
}
#[test]
fn test_parse_cwd_changed_hook_with_cwd_key() {
let line = r#"[CwdChanged Hook] cwd="/home/naomi/code/my-project""#;
let data = parse_cwd_changed_hook(line);
assert_eq!(data.cwd, "/home/naomi/code/my-project");
}
#[test]
fn test_parse_cwd_changed_hook_with_path_key() {
let line = r#"[CwdChanged Hook] path="/tmp/workspace""#;
let data = parse_cwd_changed_hook(line);
assert_eq!(data.cwd, "/tmp/workspace");
}
#[test]
fn test_parse_cwd_changed_hook_bare_path_fallback() {
let line = "[CwdChanged Hook] /home/naomi/code/project";
let data = parse_cwd_changed_hook(line);
assert_eq!(data.cwd, "/home/naomi/code/project");
}
#[test]
fn test_parse_cwd_changed_hook_empty_line() {
let line = "[CwdChanged Hook]";
let data = parse_cwd_changed_hook(line);
assert_eq!(data.cwd, "");
}
#[test]
fn test_parse_file_changed_hook_with_file_key() {
let line = r#"[FileChanged Hook] file="/home/naomi/code/project/src/main.rs""#;
let data = parse_file_changed_hook(line);
assert_eq!(data.file, "/home/naomi/code/project/src/main.rs");
}
#[test]
fn test_parse_file_changed_hook_with_path_key() {
let line = r#"[FileChanged Hook] path="/tmp/test.txt""#;
let data = parse_file_changed_hook(line);
assert_eq!(data.file, "/tmp/test.txt");
}
#[test]
fn test_parse_file_changed_hook_bare_path_fallback() {
let line = "[FileChanged Hook] /home/naomi/code/project/README.md";
let data = parse_file_changed_hook(line);
assert_eq!(data.file, "/home/naomi/code/project/README.md");
}
#[test]
fn test_parse_file_changed_hook_empty_line() {
let line = "[FileChanged Hook]";
let data = parse_file_changed_hook(line);
assert_eq!(data.file, "");
}
#[test]
fn test_parse_task_created_hook_with_all_fields() {
let line = r#"[TaskCreated Hook] task_id=Some("task-abc123"), description="Explore the codebase", parent_tool_use_id=Some("toolu_xyz")"#;
let data = parse_task_created_hook(line);
assert_eq!(data.task_id, Some("task-abc123".to_string()));
assert_eq!(data.description, Some("Explore the codebase".to_string()));
assert_eq!(data.parent_tool_use_id, Some("toolu_xyz".to_string()));
}
#[test]
fn test_parse_task_created_hook_with_description_only() {
let line = r#"[TaskCreated Hook] description="Search for relevant files""#;
let data = parse_task_created_hook(line);
assert_eq!(data.description, Some("Search for relevant files".to_string()));
assert_eq!(data.task_id, None);
assert_eq!(data.parent_tool_use_id, None);
}
#[test]
fn test_parse_task_created_hook_no_parent() {
let line = r#"[TaskCreated Hook] task_id=Some("task-001"), description="Run tests""#;
let data = parse_task_created_hook(line);
assert_eq!(data.task_id, Some("task-001".to_string()));
assert_eq!(data.description, Some("Run tests".to_string()));
assert_eq!(data.parent_tool_use_id, None);
}
#[test]
fn test_parse_task_created_hook_empty_line() {
let line = "[TaskCreated Hook]";
let data = parse_task_created_hook(line);
assert_eq!(data.task_id, None);
assert_eq!(data.description, None);
assert_eq!(data.parent_tool_use_id, None);
}
#[test]
fn test_parse_permission_denied_hook_with_all_fields() {
let line = r#"[PermissionDenied Hook] tool_name="Bash", reason="Tool not in allow list""#;
let data = parse_permission_denied_hook(line);
assert_eq!(data.tool_name, Some("Bash".to_string()));
assert_eq!(data.reason, Some("Tool not in allow list".to_string()));
}
#[test]
fn test_parse_permission_denied_hook_tool_only() {
let line = r#"[PermissionDenied Hook] tool_name="Edit""#;
let data = parse_permission_denied_hook(line);
assert_eq!(data.tool_name, Some("Edit".to_string()));
assert_eq!(data.reason, None);
}
#[test]
fn test_parse_permission_denied_hook_tool_key_alias() {
let line = r#"[PermissionDenied Hook] tool="Write", reason="Workspace not trusted""#;
let data = parse_permission_denied_hook(line);
assert_eq!(data.tool_name, Some("Write".to_string()));
assert_eq!(data.reason, Some("Workspace not trusted".to_string()));
}
#[test]
fn test_parse_permission_denied_hook_empty_line() {
let line = "[PermissionDenied Hook]";
let data = parse_permission_denied_hook(line);
assert_eq!(data.tool_name, None);
assert_eq!(data.reason, None);
}
#[test]
fn test_build_stop_failure_message_no_fields() {
let data = StopFailureData {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "hikari-desktop",
"version": "1.15.0",
"version": "1.13.0",
"identifier": "com.naomi.hikari-desktop",
"build": {
"beforeDevCommand": "pnpm dev",
-14
View File
@@ -67,18 +67,11 @@ async function changeDirectory(path: string): Promise<void> {
use_worktree: config.use_worktree ?? false,
disable_1m_context: config.disable_1m_context ?? false,
max_output_tokens: config.max_output_tokens ?? null,
disable_cron: config.disable_cron ?? false,
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
bare_mode: config.bare_mode ?? false,
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
custom_model_option: config.custom_model_option || null,
effort_level: config.effort_level || null,
prompt_caching_ttl: config.prompt_caching_ttl || null,
},
});
@@ -156,18 +149,11 @@ async function startNewConversation(): Promise<void> {
use_worktree: config.use_worktree ?? false,
disable_1m_context: config.disable_1m_context ?? false,
max_output_tokens: config.max_output_tokens ?? null,
disable_cron: config.disable_cron ?? false,
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
bare_mode: config.bare_mode ?? false,
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
custom_model_option: config.custom_model_option || null,
effort_level: config.effort_level || null,
prompt_caching_ttl: config.prompt_caching_ttl || null,
},
});
+1 -1
View File
@@ -2,7 +2,7 @@
import { invoke } from "@tauri-apps/api/core";
import { onMount } from "svelte";
const SUPPORTED_CLI_VERSION = "2.1.131";
const SUPPORTED_CLI_VERSION = "2.1.80";
let installedVersion = $state("Loading...");
let latestNpmVersion = $state<string | null>(null);
+5 -197
View File
@@ -63,10 +63,6 @@
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
disable_skill_shell_execution: false,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: null,
max_output_tokens: null,
trusted_workspaces: [],
background_image_path: null,
@@ -78,8 +74,6 @@
task_loop_auto_commit: false,
task_loop_commit_prefix: "feat",
task_loop_include_summary: false,
effort_level: null,
prompt_caching_ttl: null,
});
let showCustomThemeEditor = $state(false);
@@ -91,9 +85,6 @@
let customUiFontStatus: string | null = $state(null);
let modelOverridesJson = $state("");
let modelOverridesError: string | null = $state(null);
let globalClaudeMd = $state("");
let globalClaudeMdSaving = $state(false);
let globalClaudeMdSaveStatus: string | null = $state(null);
interface AuthStatus {
is_logged_in: boolean;
@@ -131,9 +122,6 @@
if (open && authStatus === null) {
void refreshAuthStatus();
}
if (open) {
void loadGlobalClaudeMd();
}
});
configStore.saveError.subscribe((error) => {
@@ -146,20 +134,17 @@
const availableModels = [
{ value: "", label: "Default (from ~/.claude)" },
// Current generation (Claude 4.7/4.6/4.5)
{ value: "claude-opus-4-7", label: "Claude Opus 4.7 (Most Capable)" },
// Current generation (Claude 4.6)
{ value: "claude-opus-4-6", label: "Claude Opus 4.6 (Most Capable)" },
{ value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6 (Recommended)" },
{ value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5 (Fast & Cheap)" },
// Previous generation (Claude 4.6)
{ value: "claude-opus-4-6", label: "Claude Opus 4.6" },
// Previous generation (Claude 4.5)
{ value: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5" },
{ value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5 (Fast & Cheap)" },
{ value: "claude-opus-4-5-20251101", label: "Claude Opus 4.5" },
// Previous generation (Claude 4.x)
{ value: "claude-opus-4-1-20250805", label: "Claude Opus 4.1" },
// Deprecated — retire June 15, 2026
{ value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4 (Deprecated)" },
{ value: "claude-opus-4-20250514", label: "Claude Opus 4 (Deprecated)" },
{ value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4" },
{ value: "claude-opus-4-20250514", label: "Claude Opus 4" },
];
const commonTools = [
@@ -212,30 +197,6 @@
}
}
async function loadGlobalClaudeMd() {
try {
globalClaudeMd = await invoke<string>("get_global_claude_md");
} catch (error) {
console.error("Failed to load global CLAUDE.md:", error);
}
}
async function saveGlobalClaudeMd() {
globalClaudeMdSaving = true;
globalClaudeMdSaveStatus = null;
try {
await invoke("save_global_claude_md", { content: globalClaudeMd });
globalClaudeMdSaveStatus = "Saved!";
setTimeout(() => {
globalClaudeMdSaveStatus = null;
}, 2000);
} catch (error) {
globalClaudeMdSaveStatus = `Error: ${error}`;
} finally {
globalClaudeMdSaving = false;
}
}
async function handleSave() {
isSaving = true;
saveError = null;
@@ -624,22 +585,6 @@
</p>
</div>
<!-- Disable Skill Shell Execution -->
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
bind:checked={config.disable_skill_shell_execution}
class="w-4 h-4 rounded border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--accent-primary)] focus:ring-[var(--accent-primary)]"
/>
<span class="text-sm text-[var(--text-primary)]">Disable skill shell execution</span>
</label>
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
Passes <code class="font-mono">disableSkillShellExecution: true</code> to prevent skill scripts
from executing shell commands (requires Claude Code v2.1.91+)
</p>
</div>
<!-- Include Git Instructions -->
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
@@ -717,102 +662,6 @@
defaults.
</p>
</div>
<!-- Effort Level -->
<div class="mb-4">
<label class="block text-sm text-[var(--text-primary)] mb-1" for="effort-level"
>Effort level</label
>
<select
id="effort-level"
bind:value={config.effort_level}
class="w-full px-3 py-2 rounded border border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm focus:outline-none focus:ring-1 focus:ring-[var(--accent-primary)]"
>
<option value={null}>Default (CLI decides)</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="xhigh">Extra High (Opus 4.7 only)</option>
<option value="max">Max</option>
</select>
<p class="text-xs text-[var(--text-tertiary)] mt-1">
Passes <code class="font-mono">--effort</code> to tune speed vs. intelligence. Requires Claude
Code v2.1.111+.
</p>
</div>
<!-- Prompt Caching TTL -->
<div class="mb-4">
<label class="block text-sm text-[var(--text-primary)] mb-1" for="prompt-caching-ttl"
>Prompt caching TTL</label
>
<select
id="prompt-caching-ttl"
bind:value={config.prompt_caching_ttl}
class="w-full px-3 py-2 rounded border border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm focus:outline-none focus:ring-1 focus:ring-[var(--accent-primary)]"
>
<option value={null}>Default (CLI decides)</option>
<option value="1h">1-hour TTL (reduces cost for long sessions)</option>
<option value="5m">Force 5-minute TTL</option>
</select>
<p class="text-xs text-[var(--text-tertiary)] mt-1">
Sets <code class="font-mono">ENABLE_PROMPT_CACHING_1H</code> or
<code class="font-mono">FORCE_PROMPT_CACHING_5M</code>. Requires Claude Code v2.1.108+.
</p>
</div>
<!-- Bare Mode -->
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
bind:checked={config.bare_mode}
class="w-4 h-4 rounded border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--accent-primary)] focus:ring-[var(--accent-primary)]"
/>
<span class="text-sm text-[var(--text-primary)]">Bare mode</span>
</label>
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
Passes <code class="font-mono">--bare</code> to suppress UI chrome — useful for scripted
headless <code class="font-mono">-p</code> calls (requires Claude Code v2.1.81+)
</p>
</div>
<!-- Show Clear Context on Plan Accept -->
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
bind:checked={config.show_clear_context_on_plan_accept}
class="w-4 h-4 rounded border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--accent-primary)] focus:ring-[var(--accent-primary)]"
/>
<span class="text-sm text-[var(--text-primary)]"
>Show clear context prompt on plan accept</span
>
</label>
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
When enabled, prompts to clear context when accepting a plan. Passes
<code class="font-mono">showClearContextOnPlanAccept: false</code> in
<code class="font-mono">--settings</code> when disabled (requires Claude Code v2.1.81+)
</p>
</div>
<!-- Custom Model Option -->
<div class="mb-4">
<label for="custom-model-option" class="block text-sm text-[var(--text-primary)] mb-1">
Custom model option <span class="text-[var(--text-tertiary)]">(optional)</span>
</label>
<input
id="custom-model-option"
type="text"
placeholder="Leave blank to use default"
bind:value={config.custom_model_option}
class="w-full px-3 py-2 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:outline-none focus:border-[var(--accent-primary)]"
/>
<p class="text-xs text-[var(--text-tertiary)] mt-1">
Sets <code class="font-mono">ANTHROPIC_CUSTOM_MODEL_OPTION</code> environment variable for custom
model providers (requires Claude Code v2.1.81+)
</p>
</div>
</section>
<!-- Greeting Section -->
@@ -853,47 +702,6 @@
{/if}
</section>
<!-- Global Instructions Section -->
<section class="mb-6">
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
Global Instructions
</h3>
<div class="mb-2">
<label for="global-claude-md" class="block text-sm text-[var(--text-secondary)] mb-1">
~/.claude/CLAUDE.md
</label>
<textarea
id="global-claude-md"
bind:value={globalClaudeMd}
rows="12"
placeholder="Add persistent instructions for all Claude Code sessions..."
class="w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] font-mono text-sm focus:outline-none focus:border-[var(--accent-primary)] resize-y"
></textarea>
</div>
<div class="flex items-center gap-2">
<button
onclick={saveGlobalClaudeMd}
disabled={globalClaudeMdSaving}
class="px-3 py-1.5 text-sm bg-[var(--accent-primary)] text-white rounded hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed"
>
{globalClaudeMdSaving ? "Saving..." : "Save"}
</button>
{#if globalClaudeMdSaveStatus}
<span
class="text-xs {globalClaudeMdSaveStatus.startsWith('Error')
? 'text-red-500'
: 'text-green-500'}"
>
{globalClaudeMdSaveStatus}
</span>
{/if}
</div>
<p class="text-xs text-[var(--text-tertiary)] mt-2">
Persistent instructions applied to all Claude Code sessions. Changes take effect on the next
session start.
</p>
</section>
<!-- MCP Servers Section -->
<section class="mb-6">
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
@@ -68,18 +68,11 @@
allowed_tools: grantedToolsList,
use_worktree: config.use_worktree ?? false,
disable_1m_context: config.disable_1m_context ?? false,
disable_cron: config.disable_cron ?? false,
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
bare_mode: config.bare_mode ?? false,
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
custom_model_option: config.custom_model_option || null,
effort_level: config.effort_level || null,
prompt_caching_ttl: config.prompt_caching_ttl || null,
},
});
-7
View File
@@ -402,18 +402,11 @@ User: ${formattedMessage}`;
allowed_tools: allAllowedTools,
use_worktree: config.use_worktree ?? false,
disable_1m_context: config.disable_1m_context ?? false,
disable_cron: config.disable_cron ?? false,
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
bare_mode: config.bare_mode ?? false,
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
custom_model_option: config.custom_model_option || null,
effort_level: config.effort_level || null,
prompt_caching_ttl: config.prompt_caching_ttl || null,
},
});
@@ -89,18 +89,11 @@
allowed_tools: [...new Set([...newGrantedTools, ...config.auto_granted_tools])],
use_worktree: config.use_worktree ?? false,
disable_1m_context: config.disable_1m_context ?? false,
disable_cron: config.disable_cron ?? false,
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
bare_mode: config.bare_mode ?? false,
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
custom_model_option: config.custom_model_option || null,
effort_level: config.effort_level || null,
prompt_caching_ttl: config.prompt_caching_ttl || null,
},
});
-22
View File
@@ -93,12 +93,6 @@
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
disable_skill_shell_execution: false,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: null,
effort_level: null,
prompt_caching_ttl: null,
});
let streamerModeActive = $state(false);
@@ -180,19 +174,11 @@
use_worktree: currentConfig.use_worktree ?? false,
disable_1m_context: currentConfig.disable_1m_context ?? false,
max_output_tokens: currentConfig.max_output_tokens ?? null,
disable_cron: currentConfig.disable_cron ?? false,
disable_skill_shell_execution: currentConfig.disable_skill_shell_execution ?? false,
include_git_instructions: currentConfig.include_git_instructions ?? true,
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: currentConfig.auto_memory_directory || null,
model_overrides: currentConfig.model_overrides || null,
session_name: activeConversationForName?.name || null,
bare_mode: currentConfig.bare_mode ?? false,
show_clear_context_on_plan_accept:
currentConfig.show_clear_context_on_plan_accept ?? true,
custom_model_option: currentConfig.custom_model_option || null,
effort_level: currentConfig.effort_level || null,
prompt_caching_ttl: currentConfig.prompt_caching_ttl || null,
},
});
@@ -350,19 +336,11 @@
use_worktree: currentConfig.use_worktree ?? false,
disable_1m_context: currentConfig.disable_1m_context ?? false,
max_output_tokens: currentConfig.max_output_tokens ?? null,
disable_cron: currentConfig.disable_cron ?? false,
disable_skill_shell_execution: currentConfig.disable_skill_shell_execution ?? false,
include_git_instructions: currentConfig.include_git_instructions ?? true,
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: currentConfig.auto_memory_directory || null,
model_overrides: currentConfig.model_overrides || null,
session_name: null,
bare_mode: currentConfig.bare_mode ?? false,
show_clear_context_on_plan_accept:
currentConfig.show_clear_context_on_plan_accept ?? true,
custom_model_option: currentConfig.custom_model_option || null,
effort_level: currentConfig.effort_level || null,
prompt_caching_ttl: currentConfig.prompt_caching_ttl || null,
},
});
-7
View File
@@ -218,18 +218,11 @@
use_worktree: cfg.use_worktree ?? false,
disable_1m_context: cfg.disable_1m_context ?? false,
max_output_tokens: cfg.max_output_tokens ?? null,
disable_cron: cfg.disable_cron ?? false,
disable_skill_shell_execution: cfg.disable_skill_shell_execution ?? false,
include_git_instructions: cfg.include_git_instructions ?? true,
enable_claudeai_mcp_servers: cfg.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: cfg.auto_memory_directory || null,
model_overrides: cfg.model_overrides || null,
session_name: null,
bare_mode: cfg.bare_mode ?? false,
show_clear_context_on_plan_accept: cfg.show_clear_context_on_plan_accept ?? true,
custom_model_option: cfg.custom_model_option || null,
effort_level: cfg.effort_level || null,
prompt_caching_ttl: cfg.prompt_caching_ttl || null,
},
});
} catch (error) {
@@ -108,18 +108,11 @@
allowed_tools: grantedToolsList,
use_worktree: config.use_worktree ?? false,
disable_1m_context: config.disable_1m_context ?? false,
disable_cron: config.disable_cron ?? false,
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
bare_mode: config.bare_mode ?? false,
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
custom_model_option: config.custom_model_option || null,
effort_level: config.effort_level || null,
prompt_caching_ttl: config.prompt_caching_ttl || null,
},
});
-18
View File
@@ -225,12 +225,6 @@ describe("config store", () => {
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
disable_skill_shell_execution: false,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: null,
effort_level: null,
prompt_caching_ttl: null,
};
expect(config.model).toBe("claude-sonnet-4");
@@ -295,12 +289,6 @@ describe("config store", () => {
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
disable_skill_shell_execution: false,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: null,
effort_level: null,
prompt_caching_ttl: null,
};
expect(config.model).toBeNull();
@@ -920,12 +908,6 @@ describe("config store", () => {
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
disable_skill_shell_execution: false,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: null,
effort_level: null,
prompt_caching_ttl: null,
};
const mockInvokeImpl = vi.mocked(invoke);
-18
View File
@@ -91,18 +91,6 @@ export interface HikariConfig {
auto_memory_directory: string | null;
// Model overrides for provider-specific model IDs (AWS Bedrock, Google Vertex, etc.)
model_overrides: Record<string, string> | null;
// Prevents skill scripts from executing shell commands (Claude Code v2.1.91+)
disable_skill_shell_execution: boolean;
// Bare mode — suppress UI chrome for scripted headless -p calls (v2.1.81+)
bare_mode: boolean;
// Show clear context dialog when accepting a plan (v2.1.81+)
show_clear_context_on_plan_accept: boolean;
// Custom model option env var (v2.1.81+)
custom_model_option: string | null;
// Effort level for Claude Code (v2.1.111+) — null means use CLI default
effort_level: string | null;
// Prompt caching TTL override (v2.1.108+) — null means use CLI default
prompt_caching_ttl: string | null;
}
const defaultConfig: HikariConfig = {
@@ -161,12 +149,6 @@ const defaultConfig: HikariConfig = {
enable_claudeai_mcp_servers: true,
auto_memory_directory: null,
model_overrides: null,
disable_skill_shell_execution: false,
bare_mode: false,
show_clear_context_on_plan_accept: true,
custom_model_option: null,
effort_level: null,
prompt_caching_ttl: null,
};
function createConfigStore() {
+3 -5
View File
@@ -10,15 +10,13 @@ export type BudgetType = "token" | "cost";
// Model pricing (per million tokens) - keep in sync with stats.rs
// Source: https://platform.claude.com/docs/en/about-claude/models/overview
export const MODEL_PRICING: Record<string, { input: number; output: number }> = {
// Current generation (Claude 4.7/4.6/4.5)
"claude-opus-4-7": { input: 5.0, output: 25.0 },
"claude-sonnet-4-6": { input: 3.0, output: 15.0 },
"claude-haiku-4-5-20251001": { input: 1.0, output: 5.0 },
// Previous generation (Claude 4.6)
// Current generation (Claude 4.6)
"claude-opus-4-6": { input: 5.0, output: 25.0 },
"claude-sonnet-4-6": { input: 3.0, output: 15.0 },
// Previous generation (Claude 4.5)
"claude-opus-4-5-20251101": { input: 5.0, output: 25.0 },
"claude-sonnet-4-5-20250929": { input: 3.0, output: 15.0 },
"claude-haiku-4-5-20251001": { input: 1.0, output: 5.0 },
// Previous generation (Claude 4.x)
"claude-opus-4-1-20250805": { input: 15.0, output: 75.0 },
"claude-opus-4-20250514": { input: 15.0, output: 75.0 },
-7
View File
@@ -11,7 +11,6 @@ import type {
ElicitationEvent,
PermissionPromptEvent,
PostCompactEvent,
PreCompactEvent,
StopFailureEvent,
UserQuestionEvent,
} from "$lib/types/messages";
@@ -662,12 +661,6 @@ export async function initializeTauriListeners() {
});
unlisteners.push(stopFailureUnlisten);
const preCompactUnlisten = await listen<PreCompactEvent>("claude:pre-compact", () => {
toastStore.addInfo("Compacting context...", "🗜️");
characterState.setTemporaryState("thinking", 3000);
});
unlisteners.push(preCompactUnlisten);
const postCompactUnlisten = await listen<PostCompactEvent>("claude:post-compact", () => {
toastStore.addInfo("Context compacted", "🗜️");
characterState.setTemporaryState("success", 2000);
-5
View File
@@ -187,11 +187,6 @@ export interface PostCompactEvent {
conversation_id?: string;
}
export interface PreCompactEvent {
session_id?: string;
conversation_id?: string;
}
export type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error";
export interface Attachment {