Skip to content
Merged
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
7 changes: 7 additions & 0 deletions crates/jcode-config-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,12 @@ pub struct ProviderConfig {
/// Copilot premium request mode: "normal", "one", or "zero"
/// "zero" means all requests are free (no premium requests consumed)
pub copilot_premium: Option<String>,
/// Custom system prompt to use instead of the default. When set, every
/// new agent session is initialized with this string as
/// `system_prompt_override`. Useful for forking jcode against a hosted
/// model that ships a different default persona, or for project-pinned
/// behavior baselines.
pub system_prompt: Option<String>,
}

impl Default for ProviderConfig {
Expand All @@ -708,6 +714,7 @@ impl Default for ProviderConfig {
cross_provider_failover: CrossProviderFailoverMode::Countdown,
same_provider_account_failover: true,
copilot_premium: None,
system_prompt: None,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ impl Agent {
cache_tracker: CacheTracker::new(),
last_usage: TokenUsage::default(),
locked_tools: None,
system_prompt_override: None,
system_prompt_override: crate::config::config().provider.system_prompt.clone(),
memory_enabled: crate::config::config().features.memory,
rewind_undo_snapshot: None,
stdin_request_tx: None,
Expand Down
45 changes: 45 additions & 0 deletions src/perf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ impl SystemProfile {
pub fn is_wsl_windows_terminal(&self) -> bool {
self.is_wsl && self.is_windows_terminal()
}

pub fn is_vscode_terminal(&self) -> bool {
self.terminal == "vscode"
}
}

static PROFILE: OnceLock<SystemProfile> = OnceLock::new();
Expand Down Expand Up @@ -207,6 +211,17 @@ pub fn tui_policy_for(
linked_side_panel_refresh_interval = std::time::Duration::from_millis(1000);
}

if profile.is_vscode_terminal() {
// VSCode / VSCodium / Cursor / Windsurf integrated terminal (xterm.js)
// translates keys via the OS keyboard layout *before* sending escape
// sequences. Enabling crossterm keyboard enhancement causes crossterm
// to reconstruct shifted characters using a hardcoded US layout, which
// breaks international keyboards (e.g. Finnish Shift+7 = '/' becomes
// '&'). Disable enhancement here so the layout-translated characters
// pass through untouched. See issue #55 / upstream PR #55.
enable_keyboard_enhancement = false;
}

match profile.tier {
PerformanceTier::Full => {}
PerformanceTier::Reduced => {
Expand Down Expand Up @@ -772,6 +787,36 @@ mod tests {
assert!(!policy.enable_decorative_animations);
}

#[test]
fn test_tui_policy_disables_keyboard_enhancement_on_vscode_terminal() {
// Regression for issue #55: VSCode's xterm.js layer translates keys
// via the OS keyboard layout before sending escape sequences, and
// crossterm's keyboard enhancement reconstructs shifted characters
// assuming a US layout, breaking non-US keyboards.
let profile = SystemProfile {
load_avg_1m: Some(0.2),
cpu_count: Some(8),
available_memory_mb: Some(8192),
total_memory_mb: Some(16384),
is_ssh: false,
is_wsl: false,
terminal: "vscode".to_string(),
tier: PerformanceTier::Full,
};
let mut display = crate::config::DisplayConfig::default();
display.redraw_fps = 60;
display.animation_fps = 60;
let policy = tui_policy_for(&profile, &display);
assert!(
!policy.enable_keyboard_enhancement,
"keyboard enhancement must be off in VSCode terminal so non-US \
layouts are not US-mapped"
);
// Other capabilities should not be globally crippled — only the
// keyboard enhancement is the issue.
assert!(policy.enable_focus_change);
}

#[test]
fn test_detect_runs() {
let p = detect();
Expand Down