fix: prevent panic-prone edge cases

Summary:
- Use Reverse for job timestamp sorting to avoid negation overflow edge cases.
- Make secret redaction UTF-8 safe while preserving the previous short-secret threshold.
- Update remaining setup and doctor guidance to use the supported deepseek dispatcher name.

Test plan:
- cargo test -p deepseek-config list_values_redacts --locked
- cargo test -p deepseek-core --locked
- cargo test -p deepseek-tui doctor_endpoint_tests --locked
- cargo fmt --all -- --check
- git diff --check

Supersedes #957.
This commit is contained in:
Hunter Bown
2026-05-07 03:37:50 -05:00
committed by GitHub
parent 593ed2f900
commit 063d5d7d99
3 changed files with 33 additions and 8 deletions
+27 -2
View File
@@ -1247,10 +1247,20 @@ fn serialize_http_headers(headers: &BTreeMap<String, String>) -> Option<String>
}
fn redact_secret(secret: &str) -> String {
if secret.len() <= 16 {
let chars: Vec<char> = secret.chars().collect();
if chars.len() <= 16 {
return "********".to_string();
}
format!("{}***{}", &secret[..4], &secret[secret.len() - 4..])
let prefix: String = chars.iter().take(4).collect();
let suffix: String = chars
.iter()
.rev()
.take(4)
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect();
format!("{prefix}***{suffix}")
}
#[derive(Debug, Clone, Default)]
@@ -1729,6 +1739,21 @@ mod tests {
assert_eq!(values.get("api_key").map(String::as_str), Some("********"));
}
#[test]
fn list_values_redacts_unicode_api_key_without_byte_slicing() {
let config = ConfigToml {
api_key: Some("密钥密钥密钥密钥123456789".to_string()),
..ConfigToml::default()
};
let values = config.list_values();
assert_eq!(
values.get("api_key").map(String::as_str),
Some("密钥密钥***6789")
);
}
#[cfg(unix)]
#[test]
fn save_clamps_existing_config_permissions() {
+2 -2
View File
@@ -292,7 +292,7 @@ impl JobManager {
pub fn list(&self) -> Vec<JobRecord> {
let mut out = self.jobs.values().cloned().collect::<Vec<_>>();
out.sort_by_key(|job| -job.updated_at);
out.sort_by_key(|job| std::cmp::Reverse(job.updated_at));
out
}
@@ -319,7 +319,7 @@ impl JobManager {
pub fn load_from_store(&mut self, store: &StateStore) -> Result<()> {
let persisted = store.list_jobs(Some(500))?;
for job in persisted {
let fallback_status = job_state_status_to_runtime(job.status.clone());
let fallback_status = job_state_status_to_runtime(job.status);
let parsed = Self::parse_persisted_detail(job.detail.as_deref());
let (status, detail, retry, history) = if let Some(detail_state) = parsed {
(
+4 -4
View File
@@ -1451,7 +1451,7 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
println!(" {} {}", "·".dimmed(), dotenv_status_line(workspace));
println!();
println!("Run `deepseek-tui doctor --json` for a machine-readable check.");
println!("Run `deepseek doctor --json` for a machine-readable check.");
Ok(())
}
@@ -1979,7 +1979,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
"·".dimmed(),
crate::utils::display_path(&tools_dir)
);
println!(" Run `deepseek-tui setup --tools` to scaffold a starter dir.");
println!(" Run `deepseek setup --tools` to scaffold a starter dir.");
}
// Plugins directory
@@ -2000,7 +2000,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
"·".dimmed(),
crate::utils::display_path(&plugins_dir)
);
println!(" Run `deepseek-tui setup --plugins` to scaffold a starter dir.");
println!(" Run `deepseek setup --plugins` to scaffold a starter dir.");
}
// Storage surfaces (#422 / #440 / #500)
@@ -2291,7 +2291,7 @@ fn run_doctor_json(
},
"api_connectivity": {
"checked": false,
"note": "Skipped in --json mode; run `deepseek-tui doctor` for a live check.",
"note": "Skipped in --json mode; run `deepseek doctor` for a live check.",
},
"capability": provider_capability_report(config),
});