Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/jcode-base/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const CONFIG_ENV_KEYS: &[&str] = &[
"JCODE_DISCORD_CHANNEL_ID",
"JCODE_DISCORD_REPLY_ENABLED",
"JCODE_DISPLAY_CENTERED",
"JCODE_SINGLE_OVERVIEW",
"JCODE_EFFORT_DECREASE_KEY",
"JCODE_EFFORT_INCREASE_KEY",
"JCODE_SPEED_DECREASE_KEY",
Expand Down Expand Up @@ -173,6 +174,7 @@ const CONFIG_ENV_KEYS: &[&str] = &[
"JCODE_MEMORY_EMBEDDING_DIM",
"JCODE_MEMORY_EMBEDDING_MODEL",
"JCODE_MEMORY_ENABLED",
"JCODE_MEMORY_EFFORT",
"JCODE_MEMORY_JEV_PROVIDER",
"JCODE_ENABLE_MERMAID",
"JCODE_MEMORY_MODEL",
Expand Down
12 changes: 12 additions & 0 deletions crates/jcode-base/src/config/config_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,18 @@ impl Config {
Ok(())
}

/// Update the persisted single-overview preference.
pub fn set_single_overview(single: bool) -> anyhow::Result<()> {
let mut cfg = Self::load_for_update()?;
cfg.display.single_overview = single;
cfg.save()?;
crate::logging::info(&format!(
"Saved display.single_overview to config: {}",
single
));
Ok(())
}

/// Update the persisted show-agentgrep-output preference.
pub fn set_show_agentgrep_output(show: bool) -> anyhow::Result<()> {
let mut cfg = Self::load_for_update()?;
Expand Down
16 changes: 15 additions & 1 deletion crates/jcode-base/src/config/default_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ emoji = true
# Usage percentage wording: "left" (default) or "used".
usage_display = "left"

# Show the consolidated single Overview panel in the transcript margins
# instead of the separate per-section info widgets (default: true).
# Set false for the classic layout: Todos, Swarm, Background, Compaction,
# Runtime, Usage, KV cache, Git and Commits each dock on their own.
# Env override: JCODE_SINGLE_OVERVIEW.
single_overview = true

# Show thinking/reasoning content (default: true)
show_thinking = true

Expand Down Expand Up @@ -561,9 +568,16 @@ swarm_stall_after_secs = 600
# Optional text-generating extraction is separate from recall. Disable it to
# learn only through the main agent's explicit memory writes.
# (OpenAI defaults to gpt-5.6-luna with reasoning effort "none").
# Env overrides: JCODE_MEMORY_SIDECAR_ENABLED, JCODE_MEMORY_MODEL
# Env overrides: JCODE_MEMORY_SIDECAR_ENABLED, JCODE_MEMORY_MODEL,
# JCODE_MEMORY_EFFORT
# memory_sidecar_enabled = true
# memory_model = "gpt-5.6-luna"
# Reasoning effort for the memory extraction sidecar on any backend:
# OpenAI effort, Claude thinking budget or output_config effort
# (model-dependent), or the live provider's effort knob
# ("none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | ...).
# Leave unset for the per-model default.
# memory_effort = "none"
# Legacy memory_rerank_* and memory_embedding_* settings are accepted for
# backwards compatibility, but have no effect on Jev recall.

Expand Down
13 changes: 13 additions & 0 deletions crates/jcode-base/src/config/env_overrides.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ impl Config {
self.display.centered = parsed;
}
}
if let Ok(v) = std::env::var("JCODE_SINGLE_OVERVIEW") {
if let Some(parsed) = parse_env_bool(&v) {
self.display.single_overview = parsed;
}
}
if let Ok(v) = std::env::var("JCODE_QUEUE_MODE") {
if let Some(parsed) = parse_env_bool(&v) {
self.display.queue_mode = parsed;
Expand Down Expand Up @@ -464,6 +469,14 @@ impl Config {
Some(trimmed.to_string())
};
}
if let Ok(v) = std::env::var("JCODE_MEMORY_EFFORT") {
let trimmed = v.trim();
self.agents.memory_effort = if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
};
}
if let Ok(v) = std::env::var("JCODE_MEMORY_SIDECAR_ENABLED") {
if let Some(parsed) = parse_env_bool(&v) {
self.agents.memory_sidecar_enabled = parsed;
Expand Down
83 changes: 83 additions & 0 deletions crates/jcode-base/src/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,39 @@ fn swarm_effort_parses_from_toml_and_env_override() {
restore_env_var("JCODE_SWARM_EFFORT", prev);
}

#[test]
fn memory_effort_parses_from_toml_and_env_override() {
let _guard = crate::storage::lock_test_env();
let prev = std::env::var_os("JCODE_MEMORY_EFFORT");
restore_env_var("JCODE_MEMORY_EFFORT", None);

// Public config-file interface, mirroring swarm_effort.
let cfg: Config =
toml::from_str("[agents]\nmemory_model = \"gpt-5.6-luna\"\nmemory_effort = \"none\"\n")
.expect("config with memory_effort parses");
assert_eq!(cfg.agents.memory_effort.as_deref(), Some("none"));
assert_eq!(Config::default().agents.memory_effort, None);

// Older config files without the key still parse (backward compat).
let cfg: Config = toml::from_str("[agents]\nmemory_model = \"gpt-5.6-luna\"\n")
.expect("config without memory_effort parses");
assert_eq!(cfg.agents.memory_effort, None);

crate::env::set_var("JCODE_MEMORY_EFFORT", "low");
let mut cfg = Config::default();
cfg.apply_env_overrides();
assert_eq!(cfg.agents.memory_effort.as_deref(), Some("low"));

// A blank env value clears a preset (unset semantics, like swarm_effort).
crate::env::set_var("JCODE_MEMORY_EFFORT", " ");
let mut cfg = Config::default();
cfg.agents.memory_effort = Some("preset".to_string());
cfg.apply_env_overrides();
assert_eq!(cfg.agents.memory_effort, None);

restore_env_var("JCODE_MEMORY_EFFORT", prev);
}

#[test]
fn wake_mode_defaults_parses_and_env_overrides() {
let _guard = crate::storage::lock_test_env();
Expand Down Expand Up @@ -564,17 +597,21 @@ fn test_memory_sidecar_enabled_defaults_true() {
fn test_env_override_memory_sidecar() {
let _guard = crate::storage::lock_test_env();
let prev_model = std::env::var_os("JCODE_MEMORY_MODEL");
let prev_effort = std::env::var_os("JCODE_MEMORY_EFFORT");
let prev_enabled = std::env::var_os("JCODE_MEMORY_SIDECAR_ENABLED");
crate::env::set_var("JCODE_MEMORY_MODEL", "claude-haiku-4");
crate::env::set_var("JCODE_MEMORY_EFFORT", "low");
crate::env::set_var("JCODE_MEMORY_SIDECAR_ENABLED", "true");

let mut cfg = Config::default();
cfg.apply_env_overrides();

assert_eq!(cfg.agents.memory_model.as_deref(), Some("claude-haiku-4"));
assert_eq!(cfg.agents.memory_effort.as_deref(), Some("low"));
assert!(cfg.agents.memory_sidecar_enabled);

restore_env_var("JCODE_MEMORY_MODEL", prev_model);
restore_env_var("JCODE_MEMORY_EFFORT", prev_effort);
restore_env_var("JCODE_MEMORY_SIDECAR_ENABLED", prev_enabled);
}

Expand Down Expand Up @@ -820,6 +857,10 @@ fn test_generated_default_config_has_expected_user_defaults() {
jcode_config_types::ReasoningDisplayMode::Full,
"freshly created user config should show the full reasoning trace"
);
assert!(
parsed.display.single_overview,
"freshly created user config should use the consolidated overview"
);

if let Some(prev) = prev_home {
crate::env::set_var("JCODE_HOME", prev);
Expand Down Expand Up @@ -1750,3 +1791,45 @@ fn removed_overscroll_status_key_still_loads_config() {
assert!(config.display.centered);
assert_eq!(config.display.usage_display, "used");
}

/// `display.single_overview = false` must reach the live config cache, and
/// `JCODE_SINGLE_OVERVIEW=0` must override the file value. The TUI's Overview
/// placement gates on this flag, so a stale value would resurrect the
/// consolidated panel after the user opted out.
#[test]
fn single_overview_setting_and_env_override_load() {
let _guard = crate::storage::lock_test_env();
let prev_home = std::env::var_os("JCODE_HOME");
let prev_env = std::env::var_os("JCODE_SINGLE_OVERVIEW");
let dir = tempfile::TempDir::new().expect("tempdir");
crate::env::set_var("JCODE_HOME", dir.path());
crate::env::remove_var("JCODE_SINGLE_OVERVIEW");
Config::invalidate_cache();

let path = Config::path().expect("config path");
std::fs::create_dir_all(path.parent().expect("config parent")).expect("create config parent");
std::fs::write(&path, "[display]\nsingle_overview = false\n").expect("write config");

assert!(
!crate::config::config().display.single_overview,
"config file value false must disable the consolidated overview"
);

crate::env::set_var("JCODE_SINGLE_OVERVIEW", "1");
Config::invalidate_cache();
assert!(
crate::config::config().display.single_overview,
"JCODE_SINGLE_OVERVIEW=1 must override the file value"
);

crate::env::set_var("JCODE_SINGLE_OVERVIEW", "0");
Config::invalidate_cache();
assert!(
!crate::config::config().display.single_overview,
"JCODE_SINGLE_OVERVIEW=0 must override the file value"
);

restore_env_var("JCODE_SINGLE_OVERVIEW", prev_env);
restore_env_var("JCODE_HOME", prev_home);
Config::invalidate_cache();
}
9 changes: 8 additions & 1 deletion crates/jcode-base/src/mcp/remote_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -761,8 +761,15 @@ async fn remote_handshake_deadline_is_independent_of_long_tool_timeout() {
open_stream(&mut socket, "text/event-stream").await.unwrap();
started_tx.send(()).unwrap();
// Never send a response. The client must close the socket on deadline.
// The client can close with unread SSE bytes still in flight, so the
// peer may send RST instead of FIN; both orderly EOF and reset are a
// valid close from the client's side.
let mut bytes = Vec::new();
socket.read_to_end(&mut bytes).await.unwrap();
match socket.read_to_end(&mut bytes).await {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::ConnectionReset => {}
Err(e) => panic!("server read failed: {e}"),
}
});
let config: McpServerConfig = serde_json::from_value(json!({
"type": "http", "url": url, "timeout_secs": 3600
Expand Down
Loading
Loading