From f2856874c7a2046da17ad70f10209e5e7b52aa5c Mon Sep 17 00:00:00 2001 From: CCChisato <1278019419@qq.com> Date: Fri, 17 Jul 2026 14:38:04 +0800 Subject: [PATCH] WIP: add skill readiness probes (readiness trait, ProbeRegistry, MockProbe, doctor --json) [skip ci] Refs #4407 --- .../tui/src/commands/groups/skills/skills.rs | 96 +++ crates/tui/src/main.rs | 39 +- crates/tui/src/runtime_api.rs | 5 + crates/tui/src/skills/probe.rs | 146 ++++ crates/tui/src/skills/system.rs | 88 ++- crates/tui/src/tui/slash_menu.rs | 11 + docs/ISSUE_4407_ANALYSIS.md | 106 +++ issues.json | 1 + issues2.json | 1 + ...66\346\236\204\346\246\202\350\247\210.md" | 174 +++++ "my-docs/02_Crate\350\257\246\350\247\243.md" | 734 ++++++++++++++++++ ...53\351\200\237\344\270\212\346\211\213.md" | 254 ++++++ my-docs/README.md | 44 ++ my-docs/call-stack/01-call-stack-analysis.md | 129 +++ 14 files changed, 1814 insertions(+), 14 deletions(-) create mode 100644 crates/tui/src/skills/probe.rs create mode 100644 docs/ISSUE_4407_ANALYSIS.md create mode 100644 issues.json create mode 100644 issues2.json create mode 100644 "my-docs/01_\346\236\266\346\236\204\346\246\202\350\247\210.md" create mode 100644 "my-docs/02_Crate\350\257\246\350\247\243.md" create mode 100644 "my-docs/03_\345\277\253\351\200\237\344\270\212\346\211\213.md" create mode 100644 my-docs/README.md create mode 100644 my-docs/call-stack/01-call-stack-analysis.md diff --git a/crates/tui/src/commands/groups/skills/skills.rs b/crates/tui/src/commands/groups/skills/skills.rs index 5646e8d15d..f786b905e8 100644 --- a/crates/tui/src/commands/groups/skills/skills.rs +++ b/crates/tui/src/commands/groups/skills/skills.rs @@ -8,11 +8,25 @@ use crate::skills::install::{ self, DEFAULT_MAX_SIZE_BYTES, DEFAULT_REGISTRY_URL, InstallOutcome, InstallSource, RegistryFetchResult, SkillSyncOutcome, SyncResult, UpdateResult, }; +use crate::skills::probe::ProbeRegistry; use crate::tui::app::{App, AppAction}; use crate::tui::history::HistoryCell; use crate::commands::CommandResult; +<<<<<<< Updated upstream +======= +fn skill_readiness_badge(probes: &ProbeRegistry, name: &str) -> String { + let result = match probes.probe(name) { + Some(crate::skills::SkillReadiness::NeedsSetup) => " [needs setup]".to_string(), + Some(crate::skills::SkillReadiness::Partial) => " [partial]".to_string(), + _ => String::new(), + }; + eprintln!("skill_readiness_badge({name}) = {result:?}"); + result +} + +>>>>>>> Stashed changes #[cfg(test)] thread_local! { static TEST_HOME_DIR: std::cell::RefCell> = @@ -83,6 +97,7 @@ fn visible_skill_directories(app: &App) -> Vec { } fn inspect_skills(app: &mut App) -> CommandResult { + let probes = ProbeRegistry::new(); let mode = skill_discovery_mode(app); let dirs = visible_skill_directories(app); let registry = discover_visible_skills(app); @@ -117,9 +132,26 @@ fn inspect_skills(app: &mut App) -> CommandResult { } else { for skill in registry.list() { if skill.description.trim().is_empty() { +<<<<<<< Updated upstream let _ = writeln!(output, " - {}", skill.name); } else { let _ = writeln!(output, " - {} — {}", skill.name, skill.description); +======= + let _ = writeln!( + output, + " - {}{}", + skill.name, + skill_readiness_badge(&probes, &skill.name) + ); + } else { + let _ = writeln!( + output, + " - {} — {}{}", + skill.name, + skill.description, + skill_readiness_badge(&probes, &skill.name) + ); +>>>>>>> Stashed changes } let _ = writeln!(output, " path: {}", skill.path.display()); } @@ -135,7 +167,15 @@ fn inspect_skills(app: &mut App) -> CommandResult { /// local cache (`~/.codewhale/cache/skills/`). Pass `inspect` to show local /// discovery mode, searched directories, and skill source paths. fn list_skills(app: &mut App, arg: Option<&str>) -> CommandResult { + let probes = ProbeRegistry::new(); let mut prefix: Option = None; +<<<<<<< Updated upstream +======= + let _ = std::fs::write( + "D:\\codewhale_debug.txt", + format!("list_skills called, arg={arg:?}\n"), + ); +>>>>>>> Stashed changes if let Some(arg) = arg { let trimmed = arg.trim(); if trimmed == "--remote" || trimmed == "remote" { @@ -221,7 +261,17 @@ fn list_skills(app: &mut App, arg: Option<&str>) -> CommandResult { if idx > 0 { output.push('\n'); } +<<<<<<< Updated upstream let _ = writeln!(output, " /{} - {}", skill.name, skill.description); +======= + let _ = writeln!( + output, + " /{} - {}{}", + skill.name, + skill.description, + skill_readiness_badge(&probes, &skill.name) + ); +>>>>>>> Stashed changes } } else { // Unfiltered view: partition into user-created and built-in so a @@ -239,7 +289,17 @@ fn list_skills(app: &mut App, arg: Option<&str>) -> CommandResult { if !user_skills.is_empty() { let _ = writeln!(output, "Your skills ({}):", user_skills.len()); for skill in &user_skills { +<<<<<<< Updated upstream let _ = writeln!(output, " /{} - {}", skill.name, skill.description); +======= + let _ = writeln!( + output, + " /{} - {}{}", + skill.name, + skill.description, + skill_readiness_badge(&probes, &skill.name) + ); +>>>>>>> Stashed changes } if !bundled_skills.is_empty() { output.push('\n'); @@ -255,7 +315,17 @@ fn list_skills(app: &mut App, arg: Option<&str>) -> CommandResult { // likely getting their first look at the catalog. if user_skills.is_empty() { for skill in &bundled_skills { +<<<<<<< Updated upstream let _ = writeln!(output, " /{} - {}", skill.name, skill.description); +======= + let _ = writeln!( + output, + " /{} - {}{}", + skill.name, + skill.description, + skill_readiness_badge(&probes, &skill.name) + ); +>>>>>>> Stashed changes } } else { let names: Vec = bundled_skills @@ -342,6 +412,7 @@ fn activate_skill(app: &mut App, name: &str) -> CommandResult { // `/skill new` is a friendly alias for `/skill skill-creator`. let name = if name == "new" { "skill-creator" } else { name }; + let probes = ProbeRegistry::new(); let registry = discover_visible_skills(app); if let Some(skill) = registry.get(name) { @@ -350,6 +421,31 @@ fn activate_skill(app: &mut App, name: &str) -> CommandResult { skill.name, skill.body ); +<<<<<<< Updated upstream +======= + eprintln!( + "activate skill: {name}, readiness probe={:?}", + probes.probe(name) + ); + // If skill reports NeedsSetup, append readiness report instruction + if let Some(readiness) = probes.probe(name) { + use crate::skills::SkillReadiness; + if readiness == SkillReadiness::NeedsSetup || readiness == SkillReadiness::Partial { + let status = if readiness == SkillReadiness::NeedsSetup { + "needs setup" + } else { + "partial" + }; + let report = format!( + "\n\n## ⚠ Readiness Notice\n\nThis skill reports as **{status}**. Required tools may be missing.\n\ + After attempting the task, report whether the skill worked, what dependencies are \ + missing, and whether installation was successful.\n\n---\n" + ); + instruction.push_str(&report); + } + } + +>>>>>>> Stashed changes app.add_message(HistoryCell::System { content: format!("Activated skill: {}\n\n{}", skill.name, skill.description), }); diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 3991fea9d9..d56ecfabf3 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -6,6 +6,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; use std::collections::HashMap; +use crate::skills::probe::ProbeRegistry; use std::io::{self, IsTerminal, Read, Write}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -3408,6 +3409,12 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt println!(); println!("{}", "Skills:".bold()); let global_skills_dir = config.skills_dir(); +<<<<<<< Updated upstream +======= + // Ensure bundled skill probes are registered for doctor --json + let mut probes = crate::skills::probe::ProbeRegistry::new(); + let _ = crate::skills::install_system_skills(&global_skills_dir, &mut probes); +>>>>>>> Stashed changes let agents_skills_dir = workspace.join(".agents").join("skills"); let local_skills_dir = workspace.join("skills"); let agents_global_skills_dir = crate::skills::agents_global_skills_dir(); @@ -4707,6 +4714,12 @@ fn run_doctor_json( }; let global_skills_dir = config.skills_dir(); +<<<<<<< Updated upstream +======= + // Ensure bundled skill probes are registered for doctor --json + let mut probes = crate::skills::probe::ProbeRegistry::new(); + let _ = crate::skills::install_system_skills(&global_skills_dir, &mut probes); +>>>>>>> Stashed changes let agents_skills_dir = workspace.join(".agents").join("skills"); let local_skills_dir = workspace.join("skills"); let agents_global_skills_dir = crate::skills::agents_global_skills_dir(); @@ -4779,6 +4792,29 @@ fn run_doctor_json( let (code_home, legacy_home) = doctor_state_roots(); let legacy_state_report = doctor_legacy_state_report(&code_home, &legacy_home); +<<<<<<< Updated upstream +======= + let skill_items: Vec = { + let registry = crate::skills::discover_for_workspace_and_dir_with_mode( + workspace, + &selected_skills_dir, + crate::skills::SkillDiscoveryMode::Compatible, + ); + registry + .list() + .iter() + .map(|skill| { + serde_json::json!({ + "name": skill.name, + "readiness": probes.probe(&skill.name) + .unwrap_or(crate::skills::SkillReadiness::Unknown), + "required_tools": probes.query_tools(&skill.name), + }) + }) + .collect() + }; + +>>>>>>> Stashed changes let report = json!({ "version": env!("CARGO_PKG_VERSION"), "config_path": config_path.display().to_string(), @@ -7479,7 +7515,8 @@ async fn run_interactive( // Auto-install bundled system skills (e.g. skill-creator) on first launch. // Errors are non-fatal: log a warning and continue. let skills_dir = config.skills_dir(); - if let Err(e) = crate::skills::install_system_skills(&skills_dir) { + let mut probes = ProbeRegistry::new(); + if let Err(e) = crate::skills::install_system_skills(&skills_dir, &mut probes) { logging::warn(format!("Failed to install system skills: {e}")); } diff --git a/crates/tui/src/runtime_api.rs b/crates/tui/src/runtime_api.rs index b0023fe5ff..9dc7e90d98 100644 --- a/crates/tui/src/runtime_api.rs +++ b/crates/tui/src/runtime_api.rs @@ -60,6 +60,7 @@ use crate::session_manager::default_sessions_dir; #[cfg(test)] pub(super) use crate::session_manager::{SavedSession, SessionMetadata}; use crate::skill_state::SkillStateStore; +use crate::skills::probe::ProbeRegistry; use crate::task_manager::{ NewTaskRequest, SharedTaskManager, TaskManager, TaskManagerConfig, TaskRecord, TaskSummary, }; @@ -1382,6 +1383,10 @@ async fn list_skills( path: skill.path.clone(), enabled: skill_state.is_enabled(&skill.name), is_bundled: skill_entry_is_bundled(skill, &skills_dir), +<<<<<<< Updated upstream +======= + readiness: crate::skills::SkillReadiness::Unknown, +>>>>>>> Stashed changes }) .collect(); Ok(Json(SkillsResponse { diff --git a/crates/tui/src/skills/probe.rs b/crates/tui/src/skills/probe.rs new file mode 100644 index 0000000000..ed2bab021a --- /dev/null +++ b/crates/tui/src/skills/probe.rs @@ -0,0 +1,146 @@ +pub use super::SkillReadiness; +use std::collections::HashMap; + +/// A registry of named readiness probes. Owned by the skill system; +/// tests can create a fresh instance for isolation. +pub struct ProbeRegistry { + probes: HashMap>, +} + +impl ProbeRegistry { + pub fn new() -> Self { + Self { + probes: HashMap::new(), + } + } + pub fn register(&mut self, name: &str, probe: Box) { + self.probes.insert(name.to_string(), probe); + } + pub fn probe(&self, name: &str) -> Option { + self.probes.get(name).map(|p| p.probe()) + } + pub fn query_tools(&self, name: &str) -> Vec { + self.probes + .get(name) + .map(|p| p.required_tools()) + .unwrap_or_default() + } +} + +pub trait ReadinessProbe: Send + Sync { + fn probe(&self) -> SkillReadiness; + fn required_tools(&self) -> Vec { + vec![] + } +} + +// ── Built-in probe helpers ───────────────────────────────────── + +pub fn has_python() -> bool { + std::process::Command::new("python3") + .arg("--version") + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +pub fn has_python_pptx() -> bool { + std::process::Command::new("python3") + .args(["-c", "import pptx"]) + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +pub fn has_openpyxl() -> bool { + std::process::Command::new("python3") + .args(["-c", "import openpyxl"]) + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +// ── Test-only probe: always fails ───────────────────────────── + +/// A `ReadinessProbe` that always reports `NeedsSetup`. +/// Used to verify that the TUI and `doctor --json` surface +/// unavailability correctly. +pub struct NeverReadyProbe; +impl ReadinessProbe for NeverReadyProbe { + fn probe(&self) -> SkillReadiness { + SkillReadiness::NeedsSetup + } + fn required_tools(&self) -> Vec { + vec!["python3".into(), "node".into(), "libreoffice".into()] + } +} + +/// A `ReadinessProbe` that returns a canned result. +/// Use in tests to avoid executing real OS commands. +pub struct MockProbe { + result: SkillReadiness, + tools: Vec, +} + +impl MockProbe { + pub fn new(result: SkillReadiness) -> Self { + Self { + result, + tools: vec![], + } + } + pub fn with_tools(mut self, tools: Vec) -> Self { + self.tools = tools; + self + } +} + +impl ReadinessProbe for MockProbe { + fn probe(&self) -> SkillReadiness { + self.result + } + fn required_tools(&self) -> Vec { + self.tools.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn never_ready_probe_returns_needs_setup() { + let probe = NeverReadyProbe; + assert_eq!(probe.probe(), SkillReadiness::NeedsSetup); + } + + #[test] + fn never_ready_probe_reports_three_missing_tools() { + let probe = NeverReadyProbe; + assert_eq!(probe.required_tools().len(), 3); + } + + #[test] + fn registered_probe_can_be_queried() { + let mut registry = ProbeRegistry::new(); + registry.register("test-query", Box::new(NeverReadyProbe)); + assert_eq!( + registry.probe("test-query"), + Some(SkillReadiness::NeedsSetup) + ); + } + + #[test] + fn mock_probe_returns_canned_result() { + let probe = MockProbe::new(SkillReadiness::Ready); + assert_eq!(probe.probe(), SkillReadiness::Ready); + } + + #[test] + fn probe_registry_isolation() { + let mut r1 = ProbeRegistry::new(); + r1.register("a", Box::new(MockProbe::new(SkillReadiness::Ready))); + let r2 = ProbeRegistry::new(); + assert_eq!(r2.probe("a"), None); + } +} diff --git a/crates/tui/src/skills/system.rs b/crates/tui/src/skills/system.rs index a1ef12e063..820e87cccc 100644 --- a/crates/tui/src/skills/system.rs +++ b/crates/tui/src/skills/system.rs @@ -87,6 +87,52 @@ const BUNDLED_SKILLS: &[BundledSkill] = &[ }, ]; +<<<<<<< Updated upstream +======= +use crate::skills::SkillReadiness; +use crate::skills::probe::{ + ProbeRegistry, ReadinessProbe, has_openpyxl, has_python, has_python_pptx, +}; +struct PresentationsProbe; +impl ReadinessProbe for PresentationsProbe { + fn probe(&self) -> SkillReadiness { + if has_python() && has_python_pptx() { + SkillReadiness::Ready + } else if has_python() { + SkillReadiness::Partial + } else { + SkillReadiness::NeedsSetup + } + } + fn required_tools(&self) -> Vec { + vec!["python3".into(), "python-pptx".into()] + } +} + +struct SpreadsheetsProbe; +impl ReadinessProbe for SpreadsheetsProbe { + fn probe(&self) -> SkillReadiness { + if has_python() && has_openpyxl() { + SkillReadiness::Ready + } else if has_python() { + SkillReadiness::Partial + } else { + SkillReadiness::NeedsSetup + } + } + fn required_tools(&self) -> Vec { + vec!["python3".into(), "openpyxl".into()] + } +} + +fn make_presentations_probe() -> Box { + Box::new(PresentationsProbe) +} +fn make_spreadsheets_probe() -> Box { + Box::new(SpreadsheetsProbe) +} + +>>>>>>> Stashed changes /// Whether a skill name matches one of the bundled first-party skills. /// /// Used by `/skills` to distinguish user-created skills (which should be @@ -143,7 +189,7 @@ fn install_one( /// /// Errors are I/O errors from the filesystem; the caller should log them but not /// abort startup. -pub fn install_system_skills(skills_dir: &Path) -> std::io::Result<()> { +pub fn install_system_skills(skills_dir: &Path, probes: &mut ProbeRegistry) -> std::io::Result<()> { let marker = skills_dir.join(".system-installed-version"); let installed_version = fs::read_to_string(&marker) @@ -151,6 +197,22 @@ pub fn install_system_skills(skills_dir: &Path) -> std::io::Result<()> { .map(|s| s.trim().to_string()); let mut changed = false; +<<<<<<< Updated upstream +======= + // ── Phase 1: always register probes (guaranteed, no early return) ── + for skill in BUNDLED_SKILLS { + if let Some(factory) = skill.probe { + probes.register(skill.name, factory()); + } + } + // ── Phase 2: install skill files (may fail — let errors propagate) ── + // Register test probe in debug builds only (never in release) + #[cfg(debug_assertions)] + probes.register( + "skill-never-ready", + Box::new(crate::skills::probe::NeverReadyProbe), + ); +>>>>>>> Stashed changes for skill in BUNDLED_SKILLS { changed |= install_one(skills_dir, skill, installed_version.as_deref())?; } @@ -203,7 +265,7 @@ mod tests { #[test] fn fresh_install_creates_bundled_skills_and_marker() { let tmp = TempDir::new().unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); for skill in BUNDLED_SKILLS { assert!( @@ -221,7 +283,7 @@ mod tests { #[test] fn fresh_install_skills_parse_for_discovery() { let tmp = TempDir::new().unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); let registry = crate::skills::SkillRegistry::discover(tmp.path()); assert!( @@ -247,7 +309,7 @@ mod tests { #[test] fn calling_twice_is_idempotent() { let tmp = TempDir::new().unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); for skill in BUNDLED_SKILLS { fs::write( @@ -257,7 +319,7 @@ mod tests { .unwrap(); } - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); for skill in BUNDLED_SKILLS { let body = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap(); @@ -275,13 +337,13 @@ mod tests { #[test] fn user_deleted_dir_is_not_recreated() { let tmp = TempDir::new().unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); // Simulate user deliberately removing one skill directory. fs::remove_dir_all(skill_dir(&tmp, "delegate")).unwrap(); // Re-launch must NOT recreate the deleted directory. - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); assert!( !skill_file(&tmp, "delegate").exists(), @@ -296,13 +358,13 @@ mod tests { #[test] fn user_deleted_all_dirs_are_not_recreated() { let tmp = TempDir::new().unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); for skill in BUNDLED_SKILLS { fs::remove_dir_all(skill_dir(&tmp, skill.name)).unwrap(); } - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); for skill in BUNDLED_SKILLS { assert!( @@ -326,7 +388,7 @@ mod tests { } fs::write(marker_file(&tmp), "0").unwrap(); // older than BUNDLED_SKILL_VERSION - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); for skill in BUNDLED_SKILLS { let body = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap(); @@ -359,7 +421,7 @@ mod tests { } fs::write(marker_file(&tmp), "2").unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); for skill in BUNDLED_SKILLS { assert_eq!( @@ -379,7 +441,7 @@ mod tests { // before later versions introduced more system skills. fs::write(marker_file(&tmp), "2").unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); assert!( !skill_file(&tmp, "skill-creator").exists(), @@ -408,7 +470,7 @@ mod tests { #[test] fn uninstall_removes_bundled_skills_and_marker() { let tmp = TempDir::new().unwrap(); - install_system_skills(tmp.path()).unwrap(); + install_system_skills(tmp.path(), &mut ProbeRegistry::new()).unwrap(); uninstall_system_skills(tmp.path()).unwrap(); for skill in BUNDLED_SKILLS { diff --git a/crates/tui/src/tui/slash_menu.rs b/crates/tui/src/tui/slash_menu.rs index fb5cd47f98..18cf06e8ad 100644 --- a/crates/tui/src/tui/slash_menu.rs +++ b/crates/tui/src/tui/slash_menu.rs @@ -180,7 +180,18 @@ fn skill_mention_entries( .filter(|(skill_name, _)| skill_name.to_ascii_lowercase().starts_with(&partial_lower)) .map(|(skill_name, skill_desc)| SlashMenuEntry { name: format!("{trigger}{skill_name}"), +<<<<<<< Updated upstream description: skill_desc.clone(), +======= + description: { + let badge = match crate::skills::probe::ProbeRegistry::new().probe(skill_name) { + Some(crate::skills::SkillReadiness::NeedsSetup) => " [needs setup]", + Some(crate::skills::SkillReadiness::Partial) => " [partial]", + _ => "", + }; + format!("{skill_desc}{badge}") + }, +>>>>>>> Stashed changes is_skill: true, alias_hint: None, }) diff --git a/docs/ISSUE_4407_ANALYSIS.md b/docs/ISSUE_4407_ANALYSIS.md new file mode 100644 index 0000000000..fcd83c2f12 --- /dev/null +++ b/docs/ISSUE_4407_ANALYSIS.md @@ -0,0 +1,106 @@ + # Issue #4407 分析文档:工件类型技能的运行时就绪状态报告 + + **Issue:** [v0.9.1: Report artifact-skill readiness and define a managed dependency runtime](https://github.com/Hmbown/CodeWhale/issues/4407) + **作者:** Hunter Bown (`@Hmbown`) + **创建时间:** 2026-07-16 + **状态:** `OPEN` + **标签:** `bug`, `enhancement`, `workflow-runtime`, `tui` + + --- + + ## 出了什么问题 + + CodeWhale 内置了四个工件类型的工作流配方(recipe)——演示文稿、电子表格、PDF 和文档。用户可以在 `/skills` 界面中看到它们列为 **Available / Built-in**(可用/内置),但实际上它们是**依赖条件性的**——要真正跑起来,宿主机需要额外安装 Python、Node.js、Poppler、LibreOffice 等外部工具。 + + **问题在于:用户没有任何办法判断自己的机器是否具备运行这些配方的条件。** + + 界面宣称它们是"可用"的,但如果缺少依赖,用户点击后只会得到失败体验,没有前置提示,也没有诊断手段。 + + --- + + ## 细化:实际哪里有问题、哪里没有问题 + + v0.9.0 审计中曾将这四个技能全部标记为"损坏",但这个判断过于粗糙。实际情况分三类: + + | 技能 | 实际状况 | + |------|----------| + | **文档**(结构写作、CSV 分析) | **可用**:不需要额外依赖,当前宿主就能跑 | + | **PDF 读取** | **可用**:CodeWhale 内置了 Rust PDF 提取器,不需要 Poppler/Python | + | **XLSX/PPTX 创作**、**高级 PDF/文档渲染** | **需要安装依赖**:可能需要 Python、Node.js、Poppler、LibreOffice | + + 所以真正的缺陷不是"技能坏了",而是: + + 1. **运行时就绪状态缺失**——没有探测机制,没有结果展示,用户面对的是一个黑箱。 + 2. **内置/可用标签误导**——"Built-in" 对用户暗示即开即用,但实际上可能是需要手动配环境的。 + 3. **诊断工具不覆盖**——`doctor` 命令(人工模式)可以探测部分宿主机工具,但 `doctor --json` 没有把探测结果映射到具体技能上。 + + --- + + ## 为什么不能简单地把 Codex 的技能复制过来 + + Codex 也有一组同名的文档/电子表格/演示文稿技能,但它们跑在 Codex 自己的**托管运行时(managed runtime)**之上——那个运行时不是 CodeWhale 的一部分。直接把技能文件复制过来不能解决问题。 + + 另外,**在自己的维护机装上 Python 包能让自己跑起来,但解决不了产品层面的缺口**:CodeWhale 有 npm、Cargo、二进制包、Docker 四种分发渠道,其他用户的机器上没有这些依赖,装到维护机只是把问题藏起来了。 + + --- + + ## 问题的影响面 + + 1. **用户体验断层**:用户发现一个新技能 → 尝试使用 → 失败 → 没有提示原因。这直接降低了对产品的信任。 + 2. **诊断空白**:`doctor` 没有映射到技能级别,调试无从下手。 + 3. **Docker 用户完全暴露**:Docker 镜像只打了 Rust 二进制,没有 Python/Node/Poppler/LibreOffice,四个技能全部不可用但不报错。 + 4. **审计误判**:v0.9.0 审计中因为缺少就绪检测,无法区分"真的坏了"和"缺运行时",导致错误地将所有四个技能标记为损坏。 + + --- + + ## 解决方案的范围 + + Issue 提出了以下修复范围: + + ### 核心功能 + + 1. **为每个内置技能添加确定性就绪探测**,状态分为三类: + - `ready`(就绪——依赖满足,可以运行) + - `partial`(部分就绪——部分功能可用) + - `needs_setup`(需要设置——缺少关键依赖) + + 2. **在 `/skills` 和 `doctor --json` 中暴露探测结果**,不要把依赖不全的配方说成即开即用。 + + 3. **明确"内置技能"的定义**:它们是依赖条件性的工作流指南,不是自包含的可执行模块。 + + 4. **确定长期运行时方案**: + - 方案 A:CodeWhale 自己提供校验过的、版本化的、跨平台的工件运行时和加载器 + - 方案 B:明确依赖外部工具,并提供引导安装指南 + + 5. **将 PDF 读取的就绪状态与 PDF 创建/编辑/渲染的就绪状态区分开**——前者已经有 Rust 提取器支撑,后者需要额外依赖。 + + ### 验收标准 + + - 干净的 Docker 测试环境能如实报告四个内置工件技能的状态 + - PDF 读取在 Rust 提取器存在时报告 `ready`,即使没有 Poppler/Python + - XLSX/PPTX 创作在缺少工具时报告 `needs_setup` + - `doctor --json` 输出每个技能的就绪状态和缺失能力 + - `/skills` 界面不把配方发现和运行时就绪混为一谈 + - 测试使用 mock 命令/模块探测,不全局安装包 + - 如果内置 SKILL.md 内容变更,`BUNDLED_SKILL_VERSION` 同步升级,迁移行为有测试覆盖 + + ### 明确不做的事 + + - 不依赖维护者机器上的 Codex 托管运行时 + - 不静默执行 `pip install` 或修改用户的系统 Python + - 不删除 PDF 读取(它已经是自包含的,没有依赖问题) + + --- + + ## 涉及的代码文件 + + - `crates/tui/src/skills/system.rs` —— 内置技能注册入口 + - `crates/tui/assets/skills/{presentations,spreadsheets,pdf,documents}/SKILL.md` —— 四个工件技能配方 + - `crates/tui/src/tools/file.rs` —— PDF 提取工具(Rust 实现,自包含) + - `crates/tui/src/config.rs` —— 可能涉及 `doctor --json` 的结构扩展 + + --- + + ## 总结:一句话 + + **CodeWhale 把四个靠外部依赖才能跑的技能标成了"内置/可用",但没有告诉用户你的机器能不能跑。这是信息鸿沟,不是代码坏了。** 修复方向是给每个技能加一个就绪探测,把结果展示给用户,同时想清楚长期怎么管理这些外部运行时依赖。 diff --git a/issues.json b/issues.json new file mode 100644 index 0000000000..c0d07619b1 --- /dev/null +++ b/issues.json @@ -0,0 +1 @@ +[{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACVXrtDw","name":"question","description":"Further information is requested","color":"d876e3"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"}],"number":4350,"title":"Cargo Build in android with termux meet rquickjs doesn't ship bindings for platform `aarch64-linux-android(n/a)`"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"}],"number":4345,"title":"key 太不友好了,不能放在终端进行吗?"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4335,"title":"Make offline scorecard pricing provider-aware"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4334,"title":"Preserve exact custom provider identity across session restore"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAAChcVBAw","name":"release-blocker","description":"Must be fixed before the next release","color":"B60205"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4333,"title":"Configured picker treats empty provider headers as configured"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"}],"number":4329,"title":"Anthropic API error"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACp2vlgQ","name":"performance","description":"Runtime/render performance","color":"d4c5f9"}],"number":4326,"title":"Perf: explain and bound RSS after cancelling a 32-worker storm"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"}],"number":4324,"title":"Pricing: thread billing surface (PAYG vs Token Plan) into pricing lookup for Xiaomi MiMo"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"}],"number":4320,"title":"Audit: DeepSeek deprecates deepseek-chat/deepseek-reasoner aliases 2026-07-24"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"}],"number":4319,"title":"Pricing: add cache_read to model_catalog.bundled.json CatalogEntry"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"}],"number":4318,"title":"Pricing: cache-write rates dropped by CurrencyPricing/TokenUsage"},{"labels":[],"number":4317,"title":"Pricing: long-context surcharge tiers (GPT-5.5/5.6-sol >272K, MiniMax M3 >512K)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"}],"number":4308,"title":"MCP 发现容错 + 工具描述截断优化"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"}],"number":4270,"title":"流式文本显示太慢了,上一个版本有此问题但问题不明显"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"}],"number":4267,"title":"能不能加一个Windows arm64 的发布版本"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACm4QWXw","name":"agent-ready","description":"Body is self-sufficient per docs/AGENT_RUNNER.md; a remote agent may claim it","color":"0E8A16"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4242,"title":"v0.8.68: Run Termux runtime QA for shell, PTY, config, and TUI startup"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACm4QWXw","name":"agent-ready","description":"Body is self-sufficient per docs/AGENT_RUNNER.md; a remote agent may claim it","color":"0E8A16"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4236,"title":"v0.8.68: Epic: official Termux / Android arm64 support"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACVXrtDw","name":"question","description":"Further information is requested","color":"d876e3"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"}],"number":4227,"title":"feat: 🐋 help JayBeest map the CodeWhale tsunami 🌊"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"}],"number":4208,"title":"TUI copy-paste polluted with box-drawing Unicode decorations (╎ ▎ ● │ ┃)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"}],"number":4196,"title":"feat(tools): agent-callable verify/critique tool (agent decides to spend test-time compute on self-review)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"}],"number":4190,"title":"Add a model-callable send-later tool for delayed thread check-ins"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACqoUtWA","name":"lane-workflow","description":"","color":"ededed"}],"number":4179,"title":"v0.8.68 Phase 3: Workflow gates and handoffs between Fleet roles"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACqoUtWA","name":"lane-workflow","description":"","color":"ededed"}],"number":4178,"title":"v0.8.68: Stopship workflow as fleet-backed lane (dogfood #4090/#4093/#4094)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACqoUtWA","name":"lane-workflow","description":"","color":"ededed"}],"number":4177,"title":"v0.8.68 Phase 2: Workflow steps reference Fleet roles (not raw prompts/profiles)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACqoUtWA","name":"lane-workflow","description":"","color":"ededed"}],"number":4175,"title":"v0.8.68 architecture: Fleet / Workflow / Lane / Runtime product model (canonical tracker)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCJqSw","name":"tools","description":"Tool execution, tool schemas, tool UX, and built-in tool behavior","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4174,"title":"v0.8.68 architecture: reconcile codewhale-tools and TUI ToolRegistry systems"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCJqSw","name":"tools","description":"Tool execution, tool schemas, tool UX, and built-in tool behavior","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4173,"title":"v0.8.68 architecture: de-hardcode model provider and tool registries"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4171,"title":"v0.8.68 deferred: verify refactor epic coverage"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCJqSw","name":"tools","description":"Tool execution, tool schemas, tool UX, and built-in tool behavior","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4170,"title":"v0.8.68 architecture D-6: add MCP capability metadata"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4169,"title":"v0.8.68 architecture D-5: split monolithic runtime and state files"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIzQ","name":"ux","description":"User experience, interaction, or presentation polish","color":"bfd4f2"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4168,"title":"v0.8.68 architecture D-4: add user-defined models config section"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4167,"title":"v0.8.68 architecture D-3: merge JobManager and TaskManager"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4166,"title":"v0.8.68 architecture D-2: unify ModelRegistry with RouteResolver"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4165,"title":"v0.8.68 architecture D-1: consolidate TUI engine onto core Runtime"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4164,"title":"v0.8.68 deferred DD #53-#64: workflow tui core web low bug cluster"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4162,"title":"v0.8.68 deferred DD #35-#52: workflow web cli medium-low bug cluster"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4161,"title":"v0.8.68 deferred DD #26-#32: core and state medium bug cluster"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCJqSw","name":"tools","description":"Tool execution, tool schemas, tool UX, and built-in tool behavior","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4160,"title":"v0.8.68 deferred DD #24: add ToolOutput success field contract"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4159,"title":"v0.8.68 deferred DD #22: move OSC 52 clipboard work off the main loop"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"}],"number":4101,"title":"Feature Request: Native multimodal vision payload support (bypass local OCR)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":4100,"title":"Bug: exec_shell fails with exit code 2147483647 in specific Windows sessions (persistent state corruption)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIzQ","name":"ux","description":"User experience, interaction, or presentation polish","color":"bfd4f2"},{"id":"LA_kwDOQ9AYz88AAAACnL_i-w","name":"v0.8.70","description":"Targeting v0.8.70","color":"1d76db"}],"number":4089,"title":"feat(ui): add option to disable thinking/reasoning background highlight"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4085,"title":"Cannot read/write files under ~/Library/CloudStorage/Dropbox/ (macOS File Provider)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCJqSw","name":"tools","description":"Tool execution, tool schemas, tool UX, and built-in tool behavior","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4083,"title":"v0.8.68 refactor(mcp): move McpConnection/McpPool out of mcp.rs"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4082,"title":"v0.8.68 refactor(hooks): split config types from HookExecutor"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4080,"title":"v0.8.68 refactor(settings): split TuiPrefs, schema, persistence"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4079,"title":"v0.8.68 refactor(project_context): extract constitution, pack, loader modules"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCJqSw","name":"tools","description":"Tool execution, tool schemas, tool UX, and built-in tool behavior","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4077,"title":"v0.8.68 refactor(web_search): split provider backends into submodules"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACnCJqSw","name":"tools","description":"Tool execution, tool schemas, tool UX, and built-in tool behavior","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4070,"title":"feat: standalone read_lints tool for on-demand diagnostics"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAAChLNDLQ","name":"context","description":"Context management / context","color":"0e8a16"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":4069,"title":"feat: indexing privacy controls (.codewhaleignore)"}] diff --git a/issues2.json b/issues2.json new file mode 100644 index 0000000000..a7fd664281 --- /dev/null +++ b/issues2.json @@ -0,0 +1 @@ +[{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACibF8MQ","name":"rust","description":"Pull requests that update rust code","color":"000000"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4161,"title":"v0.8.68 deferred DD #26-#32: core and state medium bug cluster"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":945,"title":"Bug: Compatibility issue with PowerShell 7.6.1"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4164,"title":"v0.8.68 deferred DD #53-#64: workflow tui core web low bug cluster"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAAChqy_PA","name":"v0.9.0","description":"Targeting v0.9.0","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ikQ","name":"v0.8.68","description":"Targeting v0.8.68","color":"1d76db"}],"number":4162,"title":"v0.8.68 deferred DD #35-#52: workflow web cli medium-low bug cluster"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":4100,"title":"Bug: exec_shell fails with exit code 2147483647 in specific Windows sessions (persistent state corruption)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":2369,"title":"CodeWhale Config Paths Fragmented Across OS and Cygwin (Plus Silent Migration Bug)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":3880,"title":"【window】DSML Interrupt Task"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":3916,"title":"bug(plugins): plugin registry only initializes on the plain `codewhale` launch path — resume/fork/exec lose plugin MCP servers"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"}],"number":4196,"title":"feat(tools): agent-callable verify/critique tool (agent decides to spend test-time compute on self-review)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnL_i-w","name":"v0.8.70","description":"Targeting v0.8.70","color":"1d76db"}],"number":1165,"title":"The border lines in the settings interface are rendering incorrectly. This bug has not been fixed yet."},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACnB0eaQ","name":"cleanup","description":"Code cleanup, refactor, or maintenance work","color":"c5def5"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":3946,"title":"v0.8.68 Engine: reduce engine.rs ownership into focused modules"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"}],"number":4345,"title":"key 太不友好了,不能放在终端进行吗?"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":1062,"title":"fix(core): capacity-memory checkpoint and cross-session recovery"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"}],"number":3926,"title":"ux(onboarding): trust step is trust-or-quit — declining exits the app, and Enter is a dead key"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFlA","name":"whaleflow","description":"WhaleFlow branch/leaf workflow runtime and workflow mode","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"}],"number":3230,"title":"WhaleFlow swarm: synthesis/reduce pass (many workers → one coherent output)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":790,"title":"Improve i18n coverage for commands, modals, and widgets"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":2492,"title":"不具备跨会话记忆"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnL_i-w","name":"v0.8.70","description":"Targeting v0.8.70","color":"1d76db"}],"number":864,"title":"输出结果显示不全"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"}],"number":4101,"title":"Feature Request: Native multimodal vision payload support (bypass local OCR)"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnL_i-w","name":"v0.8.70","description":"Targeting v0.8.70","color":"1d76db"}],"number":1687,"title":"Git-bash/window terminal preview Multi-Line"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":894,"title":"执行过程中出现了图片的的混乱。"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtDw","name":"question","description":"Further information is requested","color":"d876e3"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIDg","name":"security","description":"Security, isolation, permissions, or trust-boundary work","color":"ee0701"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"},{"id":"LA_kwDOQ9AYz88AAAACnL_iOQ","name":"v0.8.66","description":"Targeting v0.8.66","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":3275,"title":"CodeWhale is overly involved in making modifications, engaging in self-questioning and self-answering and deviating from user intent"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrs_w","name":"documentation","description":"Improvements or additions to documentation","color":"0075ca"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":2967,"title":"telegram-bridge: streaming & resilience hardening — progress-edit, typing, MarkdownV2, chunking, offset/replay safety, backoff"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACnB0i6g","name":"tui","description":"Terminal UI behavior, rendering, or interaction","color":"5319e7"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnCKMuQ","name":"reliability","description":"Reliability, flaky behavior, retries, fallbacks, and robustness","color":"fbca04"}],"number":4270,"title":"流式文本显示太慢了,上一个版本有此问题但问题不明显"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":4032,"title":"Codewhale not following the constitution"},{"labels":[{"id":"LA_kwDOQ9AYz88AAAACVXrs-w","name":"bug","description":"Something isn't working","color":"d73a4a"},{"id":"LA_kwDOQ9AYz88AAAACVXrtBw","name":"enhancement","description":"New feature or request","color":"a2eeef"},{"id":"LA_kwDOQ9AYz88AAAACl6pFuQ","name":"workflow-runtime","description":"Workflow IR, executor, control flow, and replay runtime","color":"006B75"},{"id":"LA_kwDOQ9AYz88AAAACl78wHA","name":"model-lab","description":"Model Lab open-model discovery, evaluation, routing, and export workflows","color":"BFDADC"},{"id":"LA_kwDOQ9AYz88AAAACnCFIeg","name":"subagents","description":"Sub-agent orchestration, lifecycle, and completion handling","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnL_iDg","name":"v0.8.65","description":"Targeting v0.8.65","color":"1d76db"},{"id":"LA_kwDOQ9AYz88AAAACnL_ivw","name":"v0.8.69","description":"Targeting v0.8.69","color":"1d76db"}],"number":3205,"title":"v0.8.65: Fleet model classes, loadout auto, and semantic route roles"}] diff --git "a/my-docs/01_\346\236\266\346\236\204\346\246\202\350\247\210.md" "b/my-docs/01_\346\236\266\346\236\204\346\246\202\350\247\210.md" new file mode 100644 index 0000000000..a4db023d17 --- /dev/null +++ "b/my-docs/01_\346\236\266\346\236\204\346\246\202\350\247\210.md" @@ -0,0 +1,174 @@ +# 01 — 架构概览 + +## 先看一张图:分层蛋糕 + +CodeWhale 的架构是一个**严格分层**的结构。每层只和上下相邻的层打交道: + +``` +┌──────────────────────────────────────────────┐ +│ 用户界面层 (User Interface) │ +│ TUI (ratatui) / CLI / HTTP API │ ← 你在这里输入 +└──────────────────┬───────────────────────────┘ + │ +┌──────────────────▼───────────────────────────┐ +│ 核心引擎层 (Core Engine) │ +│ Runtime、会话管理、Turn 循环、审批门 │ ← 大脑 +└──────────────────┬───────────────────────────┘ + │ +┌──────────────────▼───────────────────────────┐ +│ 工具与扩展层 (Tools & Extensions) │ +│ Shell / 文件 / Git / MCP / Hooks / Skills │ ← 手和脚 +└──────────────────┬───────────────────────────┘ + │ +┌──────────────────▼───────────────────────────┐ +│ 持久化与任务层 (Persistence & Tasks) │ +│ SQLite 会话存储、后台任务队列 │ ← 记忆 +└──────────────────┬───────────────────────────┘ + │ +┌──────────────────▼───────────────────────────┐ +│ 模型层 (LLM Layer) │ +│ DeepSeek / Claude / GPT / Ollama / … │ ← 推理引擎 +└──────────────────────────────────────────────┘ +``` + +## 16 个 Crate,各司其职 + +> 源码都在 `crates/` 目录下。 + +### 用户界面层 + +| Crate | 路径 | 做什么 | +|-------|------|--------| +| `codewhale-cli` | `crates/cli/` | 命令行入口。`codewhale` 和 `codewhale exec` 命令 | +| `codewhale-tui` | `crates/tui/` | 终端交互界面。你看到的那个黑底彩色终端就是它画的 | +| `codewhale-app-server` | `crates/app-server/` | HTTP/SSE 服务器。让外部程序(VS Code 插件、Telegram 机器人)也能调 CodeWhale | + +### 核心引擎层 + +| Crate | 路径 | 做什么 | +|-------|------|--------| +| `codewhale-core` | `crates/core/` | **最核心**。`Runtime` 结构体把所有东西串在一起:会话循环、工具调用、审批、回滚 | +| `codewhale-agent` | `crates/agent/` | Agent 的定义和生命周期管理 | + +### 工具与扩展层 + +| Crate | 路径 | 做什么 | +|-------|------|--------| +| `codewhale-tools` | `crates/tools/` | 所有内置工具的定义(读文件、执行命令、Git 操作等) | +| `codewhale-mcp` | `crates/mcp/` | MCP 协议实现。可以接入外部工具服务器,也可以把自己暴露成 MCP 服务器 | +| `codewhale-hooks` | `crates/hooks/` | 钩子系统。在工具执行前后插入自定义逻辑(允许/拒绝/询问) | +| `codewhale-execpolicy` | `crates/execpolicy/` | 执行策略:哪些命令允许跑、在什么条件下允许 | + +### 配置与安全层 + +| Crate | 路径 | 做什么 | +|-------|------|--------| +| `codewhale-config` | `crates/config/` | 配置文件解析(`~/.codewhale/config.toml`) | +| `codewhale-secrets` | `crates/secrets/` | 密钥管理(API Key 的安全存储) | +| `codewhale-state` | `crates/state/` | 会话持久化。用 SQLite 存对话历史和任务状态 | + +### 通信与工作流 + +| Crate | 路径 | 做什么 | +|-------|------|--------| +| `codewhale-protocol` | `crates/protocol/` | 通信协议定义。各类消息的 Rust 类型 | +| `codewhale-whaleflow` | `crates/whaleflow/` | WhaleFlow 工作流引擎。定义分支/叶子的任务编排 | +| `codewhale-release` | `crates/release/` | 发布构建辅助工具 | + +--- + +## 一次对话的完整数据流 + +下面追踪用户输入一句话,到模型回复、再到工具执行的全过程: + +``` +你输入 "帮我修复这个测试" 并回车 + │ + ▼ +┌─ TUI ─────────────────────────────────────┐ +│ 捕获键盘事件 → 构造消息 → 发给 Runtime │ +└──────────────────┬────────────────────────┘ + │ + ┌─────────▼──────────┐ + │ Runtime 主循环 │ ← crates/core + │ handle_prompt() │ + └─────────┬──────────┘ + │ + ┌─────────▼──────────┐ + │ 构造系统 Prompt │ ← Global Constitution + 项目法律 + 历史上下文 + │ + 对话历史 + 请求 │ + └─────────┬──────────┘ + │ + ┌─────────▼──────────┐ + │ 调用 LLM API │ ← 25+ provider,统一适配 + │ (HTTP POST) │ + └─────────┬──────────┘ + │ + ┌─────────▼──────────┐ + │ 解析模型响应 │ + │ 可能是:文本回复 │ + │ 或 Tool Call 请求 │ + └─────────┬──────────┘ + │ + ┌────────┴────────┐ + ▼ ▼ + 纯文本回复 工具调用请求 + (直接展示) │ + ┌────────▼──────────┐ + │ Hooks 前置检查 │ ← tool_call_before + │ allow / deny / ask│ + └────────┬──────────┘ + │ allow + ┌────────▼──────────┐ + │ 执行工具 │ ← 读文件 / shell / git / MCP + │ (沙箱 + 审批门) │ + └────────┬──────────┘ + │ + ┌────────▼──────────┐ + │ 工具结果注入对话 │ + │ → 下一轮 LLM 调用 │ + └───────────────────┘ +``` + +### 关键设计决策:分层宪法 (Layered Constitution) + +CodeWhale 不是把所有提示词一股脑塞给模型。它把"法律"分成 4 层,**优先级从高到低固定**: + +| 优先级 | 层 | 说明 | +|--------|-----|------| +| 1(最高) | Global Constitution | 编译进二进制的基础法则,定义了权威顺序 | +| 2 | 项目法律 | 仓库里的 `.codewhale/constitution.json`,声明项目的不变量 | +| 3 | 你的当前请求 | 此刻的操作指令 | +| 4(最低) | 实时证据 | 工具返回的实际结果。模型不能捏造不存在的事实 | + +这 4 层的顺序由**代码强制**保证,不是靠提示词"建议"模型遵守。所以换模型不换规则。 + +### 关键设计决策:Side-Git 回滚 + +CodeWhale 不在你的 `.git` 里乱搞。它维护一份**独立的 side-git** 快照,存在仓库外部。每次工具调用前后自动 snapshot。`/restore` 只动 side-git,你的 Git 历史完全不受影响。 + +--- + +## 三个运行模式 + +| 模式 | 快捷键 | 行为 | +|------|--------|------| +| **Plan** | Tab 切换 | 只读探索。Agent 可以读代码、搜索,但不能改任何东西 | +| **Agent** | Tab 切换 | 正常模式。每次工具调用都要你审批 | +| **YOLO** | Tab 切换 | 自动批准。Agent 可以连续执行,适合你信任的操作 | + +--- + +## 三种运行形态 + +| 形态 | 命令 | 适用场景 | +|------|------|----------| +| TUI 交互 | `codewhale` | 日常开发,人在终端前 | +| CLI 无头 | `codewhale exec "修 bug"` | CI/CD、脚本 | +| HTTP 服务 | `codewhale server` | VS Code 插件、Telegram 机器人等外部客户端 | + +--- + +## 下一步 + +现在你对整体有了概念,下一步看 [02_Crate详解.md](02_Crate详解.md),我会逐个 crate 讲清楚它用了什么 Rust 技巧、关键类型在哪里、你可以从哪个文件开始读。 \ No newline at end of file diff --git "a/my-docs/02_Crate\350\257\246\350\247\243.md" "b/my-docs/02_Crate\350\257\246\350\247\243.md" new file mode 100644 index 0000000000..23767df789 --- /dev/null +++ "b/my-docs/02_Crate\350\257\246\350\247\243.md" @@ -0,0 +1,734 @@ +# 02 — Crate 详解 + +> 逐个 crate 讲清楚:它解决什么问题、用了什么 Rust 技巧、关键类型在哪、从哪个文件开始读。 + +--- + +## 1. `codewhale-core` — 核心引擎 + +**路径:** `crates/core/` +**就是它干的:** 把所有东西串起来的"大脑"。`Runtime` 是整个程序的中心。 + +### 用了什么 Rust 技巧 + +**`Arc` — 共享所有权** +```rust +// 多个模块需要同一份 ToolRegistry,但不希望各自 clone 一份 +// Arc = Atomic Reference Counted,引用计数式的共享指针 +// 类比:JS 中多个变量指向同一个 Object,但 Rust 需要显式管理 +use std::sync::Arc; +let tools = Arc::new(ToolRegistry::new()); +let tools_for_agent = Arc::clone(&tools); // 增加引用计数,不拷贝数据 +``` + +**`async fn` — 异步函数** +```rust +// 几乎所有核心方法都是 async 的 +// 因为调用 LLM API (HTTP)、读写文件、执行命令都有 I/O 等待 +// 类比:JS 的 async/await,但 Rust 需要 tokio 作为"事件循环" +pub async fn handle_prompt(&self, prompt: &str) -> Result { + // 这里会 await LLM 的 HTTP 响应,期间线程可以处理其他事 +} +``` + +**Builder 模式** +```rust +// Runtime 构造时需要很多配置,用 Builder 逐步组装 +// 而不是一个几十个参数的函数 +let runtime = Runtime::builder() + .config(config) + .tools(tools) + .hooks(hooks) + .build()?; +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `Runtime` | `crates/core/src/runtime.rs` | 核心入口。持有所有子系统的 Arc | +| `Turn` | 同上 | 一轮对话的结果 | +| `Session` | `crates/core/src/session.rs` | 一次会话的完整状态 | +| `ToolRegistry` | `crates/core/src/tools.rs` | 工具注册表。通过它找到并调用工具 | + +### 依赖的关键外部库 + +- **tokio** — 异步运行时。所有 async 代码都跑在它上面 +- **serde / serde_json** — JSON 序列化。和 LLM API 通信全靠 JSON +- **tracing** — 结构化日志 + +### 推荐阅读顺序 + +1. `crates/core/src/lib.rs` — 看 pub export 了哪些东西 +2. `crates/core/src/runtime.rs` — Runtime 结构体定义,看字段就知道它管什么 +3. `crates/core/src/session.rs` — 会话是怎么存的 + +--- + +## 2. `codewhale-agent` — Agent 定义 + +**路径:** `crates/agent/` +**就是它干的:** 定义"Agent 是什么"以及它的生命周期。 + +### 用了什么 Rust 技巧 + +**`trait` 定义接口** +```rust +// Agent 是一个 trait,不同的实现可以有不同行为 +// 类比:Go 的 interface, Java 的 interface +pub trait Agent: Send + Sync { + fn name(&self) -> &str; + async fn execute(&self, ctx: AgentContext) -> Result; +} +// Send + Sync 表示这个 trait 对象可以安全地跨线程传递 +``` + +**`enum` 承载状态机** +```rust +// Agent 的状态用 enum 建模,每种状态可以携带不同数据 +pub enum AgentState { + Idle, + Working { turn_count: u32 }, + WaitingApproval { tool_name: String }, + Completed(AgentOutput), + Failed(String), +} +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `Agent` trait | `crates/agent/src/lib.rs` | Agent 的抽象接口 | +| `AgentContext` | 同上 | Agent 执行时需要的上下文 | +| `SubAgentConfig` | 同上 | 子 Agent 的配置(并发数、模型选择等) | + +### 依赖的关键外部库 + +- **async-trait** — Rust 原生不支持 async trait 方法,这个宏补上了这个能力 +- **tokio** — 异步执行 + +--- + +## 3. `codewhale-cli` — 命令行入口 + +**路径:** `crates/cli/` +**就是它干的:** 解析命令行参数,然后交给 core 去干正事。 + +### 用了什么 Rust 技巧 + +**`clap` 声明式参数解析** +```rust +// clap 是 Rust 最主流的 CLI 框架 +// 用 #[derive(Parser)] 自动从 struct 生成参数解析代码 +use clap::Parser; + +#[derive(Parser)] +#[command(name = "codewhale")] +struct Cli { + /// 设置 API provider + #[arg(long)] + provider: Option, + + /// 无头模式:直接执行一条指令 + #[arg(long)] + exec: Option, +} +// clap 会在编译时生成代码,自动处理 --help、参数校验等 +``` + +**`anyhow` — 简化错误处理** +```rust +// 标准库的 Result 需要明确 E 是什么类型 +// anyhow::Result 是 Result 的别名 +// anyhow::Error 可以装任何错误,适合上层(CLI、main)使用 +use anyhow::Result; +fn main() -> Result<()> { // 不用标注具体错误类型 + do_stuff()?; // ? 自动把错误转成 anyhow::Error + Ok(()) +} +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `Cli` | `crates/cli/src/main.rs` | 命令行参数定义 | +| `Commands` enum | 同上或 `commands.rs` | 子命令枚举:`auth`, `exec`, `server`, `doctor` 等 | + +### 依赖的关键外部库 + +- **clap** — CLI 参数解析 +- **anyhow** — 简化错误处理 +- **tokio** — main 函数是 `#[tokio::main]`,启动异步运行时 + +### 推荐阅读 + +`crates/cli/src/main.rs` 就是最好的起点。从 `main` 函数开始追踪。 + +--- + +## 4. `codewhale-tui` — 终端界面 + +**路径:** `crates/tui/` +**就是它干的:** 画那个漂亮的终端交互界面。 + +### 用了什么 Rust 技巧 + +**`ratatui` — 终端 UI 框架** +```rust +// ratatui 是 Rust 生态的终端 UI 库 +// 它用"即时模式"渲染:每帧重新画整个界面 +// 而不是"保留模式"(像 HTML DOM 那样只改变化的部分) +// 优点:状态管理简单,渲染逻辑直观 +``` + +**事件循环** +```rust +// TUI 的核心是一个死循环:读事件 → 更新状态 → 重绘 +loop { + // 1. 读键盘事件(异步,不阻塞) + let event = event_stream.next().await; + // 2. 更新应用状态 + app.handle_event(event); + // 3. 画界面 + terminal.draw(|frame| { + app.render(frame); + })?; +} +``` + +**组件化布局** +```rust +// ratatui 用 Layout 把终端分成多个区域 +let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Min(3), // 对话区 + Constraint::Length(3), // 输入区 + Constraint::Length(1), // 状态栏 + ]) + .split(frame.area()); +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `App` | `crates/tui/src/app.rs` | TUI 应用的状态 | +| `Event` | `crates/tui/src/event.rs` | 键盘/鼠标/系统事件 | +| `UI 组件` | `crates/tui/src/ui/` | 各个渲染组件 | + +### 依赖的关键外部库 + +- **ratatui** — 终端 UI 框架 +- **crossterm** — 跨平台终端控制(光标、颜色、事件) +- **tokio** — 事件流的异步读取 + +### 推荐阅读 + +`crates/tui/src/app.rs` — 看 `App` 怎么管理状态。 + +--- + +## 5. `codewhale-tools` — 工具系统 + +**路径:** `crates/tools/` +**就是它干的:** 定义 Agent 能用的所有工具(读文件、跑命令、Git 操作等)。 + +### 用了什么 Rust 技巧 + +**`trait` 多态 — 工具接口** +```rust +// 所有工具实现同一个 trait +// dyn Tool = 运行时多态,可以存不同类型的工具在同一个 Vec 里 +pub trait Tool: Send + Sync { + fn name(&self) -> &str; + fn description(&self) -> &str; + fn parameters(&self) -> serde_json::Value; // JSON Schema + async fn execute(&self, args: Value) -> Result; +} + +// 使用时: +let tools: Vec> = vec![ + Box::new(ReadFileTool::new()), + Box::new(ExecShellTool::new()), + Box::new(GitTool::new()), +]; +``` + +**JSON Schema 描述工具参数** +```rust +// LLM 需要知道工具的"签名"才能正确调用 +// CodeWhale 用 JSON Schema 描述每个工具的参数 +// 这个 schema 会被填入 LLM 请求的 tools 字段 +fn parameters(&self) -> Value { + json!({ + "type": "object", + "properties": { + "path": {"type": "string", "description": "文件路径"}, + "start_line": {"type": "integer", "description": "起始行"} + }, + "required": ["path"] + }) +} +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `Tool` trait | `crates/tools/src/lib.rs` | 工具接口 | +| `ToolOutput` | 同上 | 工具执行的返回结果 | +| `ReadFileTool` | `crates/tools/src/file.rs` 或类似文件 | 读文件工具 | +| `ExecShellTool` | `crates/tools/src/shell.rs` | 执行 Shell 命令 | + +### 依赖的关键外部库 + +- **serde_json** — JSON 参数解析 +- **tokio** — 异步执行命令(`tokio::process::Command`) + +### 推荐阅读 + +`crates/tools/src/lib.rs` — 看 `Tool` trait 长什么样。 + +--- + +## 6. `codewhale-mcp` — MCP 协议 + +**路径:** `crates/mcp/` +**就是它干的:** 实现 Model Context Protocol(MCP)。CodeWhale 既可以作为 MCP 客户端连别人的工具服务器,也可以自己作为 MCP 服务器暴露出去。 + +### 用了什么 Rust 技巧 + +**双向通信 — `tokio::sync::mpsc` 通道** +```rust +// mpsc = Multiple Producer, Single Consumer +// 多生产者、单消费者通道。用于异步任务间通信 +// 类比:Go 的 channel +use tokio::sync::mpsc; + +let (tx, mut rx) = mpsc::channel(32); // 缓冲区大小 32 + +// 生产者端 +tx.send("hello").await?; + +// 消费者端 +while let Some(msg) = rx.recv().await { + println!("got: {}", msg); +} +``` + +**客户端-服务器模式** +```rust +// MCP 客户端:发现并调用外部服务器提供的工具 +let client = McpClient::connect("http://localhost:3000").await?; +let tools = client.list_tools().await?; +let result = client.call_tool("search", args).await?; + +// MCP 服务器:把 CodeWhale 的工具暴露出去 +let server = McpServer::new(tool_registry); +server.serve("0.0.0.0:3000").await?; +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `McpClient` | `crates/mcp/src/client.rs` | MCP 客户端 | +| `McpServer` | `crates/mcp/src/server.rs` | MCP 服务器 | +| `ToolDefinition` | 同上 | MCP 工具的定义格式 | + +### 依赖的关键外部库 + +- **tokio** — 异步网络通信 +- **serde_json** — JSON-RPC 消息格式 +- **reqwest** — HTTP 客户端(连外部 MCP 服务器) + +--- + +## 7. `codewhale-config` — 配置管理 + +**路径:** `crates/config/` +**就是它干的:** 解析 `~/.codewhale/config.toml` 配置文件。 + +### 用了什么 Rust 技巧 + +**`serde` 反序列化** +```rust +// 定义 Rust struct,用 #[derive(Deserialize)] 自动支持 TOML 解析 +use serde::Deserialize; + +#[derive(Deserialize)] +struct Config { + provider: Option, + model: Option, + + #[serde(default)] + subagents: SubAgentsConfig, // 如果没写就用 Default +} + +// 然后用 toml crate 把文件内容变成 Rust 结构体 +let config: Config = toml::from_str(&file_content)?; +``` + +**默认值处理** +```rust +// serde(default) 会在字段缺失时用类型的 Default 实现 +// serde(default = "function_name") 调用自定义函数生成默认值 +#[derive(Deserialize)] +struct SubAgentsConfig { + #[serde(default = "default_max_concurrency")] + max_concurrency: usize, +} + +fn default_max_concurrency() -> usize { 5 } +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `Config` | `crates/config/src/lib.rs` | 顶层配置结构体 | +| `ProviderConfig` | 同上 | 单个 Provider 的配置 | + +### 依赖的关键外部库 + +- **toml** — TOML 格式解析 +- **serde** — 序列化/反序列化框架 +- **dirs** — 获取系统标准目录(如 `~/.codewhale/`) + +--- + +## 8. `codewhale-state` — 会话持久化 + +**路径:** `crates/state/` +**就是它干的:** 用 SQLite 把对话历史、任务状态存到磁盘上。 + +### 用了什么 Rust 技巧 + +**`rusqlite` — SQLite 绑定** +```rust +// rusqlite 是 Rust 对 SQLite C 库的安全封装 +// 支持参数化查询(防止 SQL 注入) +use rusqlite::Connection; + +let conn = Connection::open("sessions.db")?; + +// 创建表 +conn.execute( + "CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + data TEXT NOT NULL, + created_at TEXT NOT NULL + )", + [], // 空参数列表 +)?; + +// 参数化插入 +conn.execute( + "INSERT INTO sessions (id, data, created_at) VALUES (?1, ?2, ?3)", + rusqlite::params![session_id, json_data, timestamp], +)?; +``` + +**JSON 序列化存储** +```rust +// 复杂的状态对象 → JSON 字符串 → 存进 SQLite TEXT 字段 +// 读出来 → JSON 字符串 → 反序列化回 Rust 对象 +let json = serde_json::to_string(&session)?; +conn.execute("INSERT INTO sessions ...", params![json])?; +// 这就是为什么依赖里同时有 rusqlite 和 serde_json +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `StateStore` | `crates/state/src/lib.rs` | 状态存储的抽象 | +| `Session` | 同上 | 会话的持久化表示 | + +### 依赖的关键外部库 + +- **rusqlite** — SQLite 绑定(`bundled` feature 表示编译时自带 SQLite C 源码,不需要系统安装) +- **serde_json** — 对象到 JSON 的转换 + +--- + +## 9. `codewhale-hooks` — 钩子系统 + +**路径:** `crates/hooks/` +**就是它干的:** 在工具执行前后插入自定义逻辑。比如:工具被调用前先问你、某个命令永远禁止、某些操作自动放行。 + +### 用了什么 Rust 技巧 + +**策略模式 — 钩子链** +```rust +// 多个钩子组成一条链,依次执行 +// 任何一个返回 Deny 就立即中断 +enum HookDecision { + Allow, + Deny(String), // 带拒绝原因 + Ask(String), // 带提示信息,等待用户决定 +} + +// 钩子链:遍历执行,Deny 优先 +async fn run_hooks(hooks: &[Box], call: &ToolCall) -> HookDecision { + for hook in hooks { + match hook.before_call(call).await { + HookDecision::Deny(reason) => return HookDecision::Deny(reason), + HookDecision::Ask(msg) => return HookDecision::Ask(msg), + HookDecision::Allow => continue, // 继续下一个钩子 + } + } + HookDecision::Allow // 全部通过 +} +``` + +**TOML 配置驱动** +```rust +// 钩子规则写在 .codewhale/hooks.toml 里 +// 不需要写 Rust 代码就能定制安全策略 +// 示例: +// [[hooks]] +// type = "deny" +// pattern = "rm -rf /*" +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `Hook` trait | `crates/hooks/src/lib.rs` | 钩子接口 | +| `HookDecision` enum | 同上 | 允许 / 拒绝 / 询问 | + +--- + +## 10. `codewhale-execpolicy` — 执行策略 + +**路径:** `crates/execpolicy/` +**就是它干的:** 更细粒度的命令执行控制。什么样的 shell 命令可以跑、在什么条件下可以跑。 + +### 用了什么 Rust 技巧 + +**规则引擎** +```rust +// 不是简单的黑白名单,而是条件匹配 +// 可以有:允许读任意文件但只允许写 /tmp +// 允许 cargo test 但禁止 cargo publish +struct ExecPolicy { + rules: Vec, +} + +enum Rule { + Allow { command_pattern: String, in_path: Option }, + Deny { command_pattern: String, reason: String }, +} +``` + +--- + +## 11. `codewhale-secrets` — 密钥管理 + +**路径:** `crates/secrets/` +**就是它干的:** 安全存储 API Key。从配置文件读出来,或从环境变量读。 + +### 用了什么 Rust 技巧 + +**多层密钥来源** +```rust +// API Key 来源有优先级: +// 1. 环境变量 (DEEPSEEK_API_KEY) +// 2. 配置文件 (~/.codewhale/config.toml) +// 3. 交互式输入(auth set 命令) +fn resolve_api_key(provider: &str) -> Option { + // 先查环境变量 + if let Ok(key) = std::env::var("DEEPSEEK_API_KEY") { + return Some(key); + } + // 再查配置文件 + // ... +} +``` + +--- + +## 12. `codewhale-protocol` — 通信协议 + +**路径:** `crates/protocol/` +**就是它干的:** 定义消息的 Rust 类型。各个 crate 之间通信用的数据结构都在这里。 + +### 用了什么 Rust 技巧 + +**纯数据类型 crate** +```rust +// 这个 crate 没有业务逻辑,只有类型定义 +// 好处:其他 crate 可以依赖它而不引入重依赖 +// 这是一种常见的 Rust 项目组织模式 + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ToolCall { + pub id: String, + pub name: String, + pub arguments: Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ToolResult { + pub call_id: String, + pub content: String, + pub is_error: bool, +} +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| `Message` | `crates/protocol/src/lib.rs` | 一条对话消息 | +| `ToolCall` | 同上 | 工具调用请求 | +| `ToolResult` | 同上 | 工具执行结果 | + +--- + +## 13. `codewhale-app-server` — HTTP 服务器 + +**路径:** `crates/app-server/` +**就是它干的:** 提供 HTTP/SSE 接口,让外部程序可以调用 CodeWhale。 + +### 用了什么 Rust 技巧 + +**`axum` — Web 框架** +```rust +// axum 是基于 tokio 和 tower 的 Web 框架 +// 用宏定义路由,类型安全 +use axum::{Router, routing::post}; + +let app = Router::new() + .route("/api/chat", post(handle_chat)) + .route("/api/health", get(handle_health)); + +// 启动服务器 +let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; +axum::serve(listener, app).await?; +``` + +**SSE (Server-Sent Events)** +```rust +// SSE 用于流式输出:模型一边生成一边推送给客户端 +// 类比:HTTP 长连接 + 逐条推送 +async fn handle_chat() -> Sse>> { + // 返回一个 Stream,每次模型吐出 token 就推送一条事件 +} +``` + +### 关键类型 + +| 类型 | 在哪 | 做什么 | +|------|------|--------| +| Router | `crates/app-server/src/lib.rs` | HTTP 路由定义 | + +### 依赖的关键外部库 + +- **axum** — Web 框架 +- **tokio** — 异步 HTTP 服务器 +- **tower-http** — HTTP 中间件(CORS 等) + +--- + +## 14. `codewhale-whaleflow` — 工作流引擎 + +**路径:** `crates/whaleflow/` +**就是它干的:** 复杂任务编排。把大任务拆成子任务,按分支/叶子的拓扑结构调度执行。 + +### 用了什么 Rust 技巧 + +**DAG(有向无环图)工作流** +```rust +// 工作流是一个 DAG:节点是任务,边是依赖关系 +// 只有所有依赖完成,当前节点才能执行 +struct Workflow { + nodes: Vec, + edges: Vec, +} + +// 调度器找出所有依赖已满足的节点并行执行 +// 这需要 tokio 的并发原语(JoinSet、Semaphore 等) +``` + +--- + +## 你读代码时最常用的 Rust 模式速查 + +### `?` 操作符 + +```rust +// ? 是 "如果出错就提前返回" 的简写 +// 实际上做两件事:1) 如果是 Err,把错误转成当前函数的错误类型并 return +// 2) 如果是 Ok,把 Ok 里的值取出来 +fn read_config() -> Result { + let content = std::fs::read_to_string("config.toml")?; // 如果读文件失败就 return Err + let config: Config = toml::from_str(&content)?; // 如果解析失败就 return Err + Ok(config) +} +``` + +### `#[tokio::main]` 宏 + +```rust +// 这个宏把普通 main 函数变成异步的 +// 展开后相当于:创建 tokio 运行时 → 运行 async main → 等待完成 +#[tokio::main] +async fn main() { + // 这里可以用 .await + do_stuff().await; +} + +// 等价于手写: +// fn main() { +// tokio::runtime::Runtime::new().unwrap().block_on(async { +// do_stuff().await; +// }); +// } +``` + +### `#[derive(...)]` 宏 + +```rust +// Rust 编译器可以自动生成一些常见 trait 的实现 +#[derive(Debug, Clone, Serialize, Deserialize)] +struct MyStruct { + field: String, +} +// Debug → 可以用 {:?} 打印 +// Clone → 可以 .clone() +// Serialize → 可以序列化成 JSON/TOML +// Deserialize → 可以从 JSON/TOML 反序列化 +``` + +--- + +## 各 Crate 依赖关系图 + +``` +cli ──────┐ +tui ──────┼──→ core ──→ agent +app-server┘ │ + ├──→ tools + ├──→ hooks + ├──→ execpolicy + ├──→ mcp + ├──→ config + ├──→ state + ├──→ secrets + ├──→ protocol + └──→ whaleflow +``` + +`core` 是枢纽,所有其他 crate 要么调用它(上层),要么被它调用(下层)。`protocol` 是纯类型 crate,被几乎所有 crate 依赖。 + +--- + +## 下一步 + +理解了各 crate 的职责和它们用的 Rust 技巧后,看 [03_快速上手.md](03_快速上手.md) 来实际跑起来。 \ No newline at end of file diff --git "a/my-docs/03_\345\277\253\351\200\237\344\270\212\346\211\213.md" "b/my-docs/03_\345\277\253\351\200\237\344\270\212\346\211\213.md" new file mode 100644 index 0000000000..044647335a --- /dev/null +++ "b/my-docs/03_\345\277\253\351\200\237\344\270\212\346\211\213.md" @@ -0,0 +1,254 @@ +# 03 — 快速上手:怎么玩 CodeWhale + +> Release 编译完成后,你会在 `target/release/` 下得到 `codewhale.exe`、`codew.exe` 和 `codewhale-tui.exe`。这是怎么开始用的指南。 + +--- + +## 第一步:确认编译产物 + +release 编译完成后,检查三个二进制文件: + +```cmd +dir target\release\codewhale*.exe +``` + +你应该看到: + +- `codewhale.exe` — 主命令(CLI + TUI) +- `codewhale-tui.exe` — 纯 TUI 入口 +- `codew.exe` — 简短别名 + +--- + +## 第二步:获取 API Key + +CodeWhale 需要一个大模型 API Key。推荐从 DeepSeek 开始(便宜、效果好): + +1. 打开 +2. 注册 / 登录 +3. 在 API Keys 页面创建一个 key +4. 把钱充上(10 块钱够玩很久) + +其他选项:如果你有 Claude / OpenAI / Kimi / GLM 的 key,也可以用。详见 [Provider 注册表](https://github.com/Hmbown/CodeWhale/blob/main/docs/PROVIDERS.md)。 + +--- + +## 第三步:配置 CodeWhale + +### 方式 A:交互式配置(推荐) + +```cmd +target\release\codewhale.exe auth set --provider deepseek +``` + +它会提示你输入 API Key。输完后: + +```cmd +target\release\codewhale.exe auth status +target\release\codewhale.exe doctor +``` + +`doctor` 会检查所有配置是否正确。 + +### 方式 B:环境变量 + +```cmd +set DEEPSEEK_API_KEY=sk-your-key-here +target\release\codewhale.exe +``` + +### 方式 C:配置文件 + +创建 `%USERPROFILE%\.codewhale\config.toml`(即 `C:\Users\你的用户名\.codewhale\config.toml`): + +```toml +[provider] +name = "deepseek" +api_key = "sk-your-key-here" +``` + +--- + +## 第四步:启动 TUI + +```cmd +target\release\codewhale.exe +``` + +你会看到一个终端界面,分成三个区域: + +``` +┌─────────────────────────────────────────┐ +│ 对话区 │ +│ (Agent 的输出显示在这里) │ +│ │ +│ │ +├─────────────────────────────────────────┤ +│ 输入区 │ +│ > 在这里打字 │ +├─────────────────────────────────────────┤ +│ 状态栏: Plan | deepseek-v3 | 0.00 │ +└─────────────────────────────────────────┘ +``` + +### 第一次对话 + +直接打字,回车发送: + +``` +你好,帮我看看这个项目是做什么的 +``` + +Agent 会读你的文件,然后回答。 + +--- + +## 第五步:学会快捷键 + +### 全局快捷键 + +| 按键 | 功能 | +|------|------| +| `Tab` | 切换模式:Plan → Agent → YOLO → Plan → … | +| `Ctrl+C` | 中断当前操作 | +| `Ctrl+D` | 退出程序 | +| `↑` / `↓` | 浏览历史消息 | +| `Ctrl+R` | 搜索历史 | + +### 输入区快捷键 + +| 按键 | 功能 | +|------|------| +| `Enter` | 发送消息 | +| `Shift+Enter` | 换行(多行输入) | +| `Ctrl+P` | 粘贴剪贴板 | +| `Alt+Enter` | 在新的一行开始 | + +### 审批对话框 + +当 Agent 想执行工具时(比如运行命令、修改文件),会弹出一个审批框: + +| 按键 | 功能 | +|------|------| +| `y` | Yes — 批准执行 | +| `n` | No — 拒绝 | +| `a` | Always — 本次会话内一直批准 | + +--- + +## 第六步:学会基础命令 + +### 会话内命令(以 `/` 开头) + +| 命令 | 功能 | 示例 | +|------|------|------| +| `/help` | 显示帮助 | `/help` | +| `/provider` | 查看/切换 provider | `/provider openrouter` | +| `/model` | 查看/切换模型 | `/model deepseek-chat` | +| `/mode` | 切换模式 | `/mode plan` | +| `/goal` | 设定持久目标 | `/goal 修复所有 clippy 警告` | +| `/task` | 管理后台任务 | `/task list` | +| `/restore` | 回滚上一轮改动 | `/restore` | +| `/config` | 编辑运行时配置 | `/config` | +| `/statusline` | 显示当前状态 | `/statusline` | +| `/skills` | 加载 skills 工作流 | `/skills` | +| `/clear` | 清屏 | `/clear` | +| `/quit` | 退出程序 | `/quit` | + +### Shell 命令(以 `!` 开头) + +在 CodeWhale 里可以直接跑 shell 命令,走审批和沙箱路径: + +``` +! cargo build +! dir +! git diff +``` + +--- + +## 三个模式怎么选 + +| 场景 | 用这个模式 | 为什么 | +|------|-----------|--------| +| 第一次接触新项目,想了解结构 | **Plan** | 只读,Agent 可以读文件但不会改任何东西 | +| 日常开发,让 Agent 帮你写代码 | **Agent** | 每次写文件/跑命令都让你审批 | +| 跑测试、格式化、批量重命名 | **YOLO** | 自动批准,效率最高 | + +按 `Tab` 在三个模式之间切换。状态栏会显示当前模式。 + +--- + +## 无头模式(Headless) + +用于 CI/CD 或脚本,不需要 TUI: + +```cmd +target\release\codewhale.exe exec --allowed-tools read_file,exec_shell --max-turns 10 "修复 src/main.rs 里的 clippy 警告" +``` + +参数说明: + +| 参数 | 说明 | +|------|------| +| `exec "指令"` | 要执行的指令 | +| `--allowed-tools` | 允许使用的工具(逗号分隔) | +| `--disallowed-tools` | 禁止使用的工具(deny 优先) | +| `--max-turns` | 最大对话轮数 | +| `--append-system-prompt` | 追加系统提示词 | + +--- + +## 项目级配置 + +在你的项目根目录放一个 `.codewhale/constitution.json`,可以声明项目的规则: + +```json +{ + "protected_invariants": [ + "所有 public API 必须有文档注释", + "不可以使用 unsafe 代码" + ], + "branch_policy": "在 feature 分支上工作,不要直接改 main", + "verification_policy": "每次修改后必须运行 cargo test", + "escalate_when": [ + "测试失败超过 3 次", + "需要更改超过 3 个文件" + ] +} +``` + +CodeWhale 进入这个项目时会自动加载这个文件,作为它行为的约束。 + +--- + +## Windows 特别注意 + +1. **终端推荐用 Windows Terminal**(Microsoft Store 免费下载),不要用老 cmd.exe——颜色和渲染效果差很多。 + +2. **如果 TUI 显示不正常**:确保终端支持 UTF-8。Windows Terminal 默认支持。 + +3. **沙箱功能**:Windows 上沙箱后端较弱(相比 Linux 的 bwrap/seccomp/Landlock),但审批门对所有平台都有效。 + +4. **配置文件路径**:`%USERPROFILE%\.codewhale\config.toml` + +5. **如果 release 编译失败**: + - 确保 Rust 版本 ≥ 1.88(项目要求):`rustc --version` + - 更新 Rust:`rustup update` + - 确保有 C++ 编译工具链:安装 [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) 选 "C++ 桌面开发工作负载" + +--- + +## 从哪开始玩 + +推荐这个顺序: + +1. 先不开 CodeWhale,用 `cargo build --release` 编译好 +2. `codewhale doctor` 确保环境 OK +3. 进入 Plan 模式,输 `帮我看看这个项目的架构,从 Cargo.toml 开始分析` +4. 看 Agent 怎么读文件、怎么分析——这就是代码里 `ReadFileTool` 在工作 +5. 切换到 Agent 模式,试着让它改一段注释:`把 src/main.rs 的注释翻译成中文` +6. 观察审批框——这就是 hooks 和 execpolicy 在工作 +7. 用 `/restore` 回滚——这就是 side-git 快照在工作 + +玩一圈下来,你再回头看 [02_Crate详解.md](02_Crate详解.md),就会发现每个 Rust 代码片段对应到实际行为了。 \ No newline at end of file diff --git a/my-docs/README.md b/my-docs/README.md new file mode 100644 index 0000000000..c5c54aa627 --- /dev/null +++ b/my-docs/README.md @@ -0,0 +1,44 @@ +# CodeWhale 源码阅读指南 + +> 写给会 Rust 基本语法,但不太熟悉标准库 / 常用库 / 设计模式的朋友。 + +## 怎么用这套文档 + +一共 3 份文档,建议按顺序看: + +| 顺序 | 文件 | 内容 | +|------|------|------| +| 1 | [01_架构概览.md](01_架构概览.md) | 整体架构:有哪些 crate,它们怎么连起来,一次对话的数据流是怎样的 | +| 2 | [02_Crate详解.md](02_Crate详解.md) | 每个 crate 逐一讲解:它解决什么问题、用了什么 Rust 技巧、关键类型在哪 | +| 3 | [03_快速上手.md](03_快速上手.md) | 编译好的 `codewhale.exe` 怎么用:配置 API Key、快捷键、常用命令 | + +## 一些有助于开图的外部链接 + +- 项目主页: +- GitHub 仓库: +- DeepWiki 自动生成的项目索引: +- crates.io 发布页: +- npm 发布页: +- Release 下载: +- CNB 镜像(国内更快): + +## 需要用到的 Rust 知识速查 + +以下是你读代码时大概率会遇到的、超出"基础语法"的东西,我把它们和文档里出现的位置列在一起: + +| 概念 | 一句话解释 | 在哪里出现 | +|------|-----------|-----------| +| `async` / `await` | 让函数可以"暂停等待 I/O",不阻塞线程 | 几乎所有 crate | +| `tokio` | Rust 最主流的异步运行时,相当于 JS 的事件循环 | `core`, `agent`, `cli`, `app-server` | +| `Arc` | 多个所有者共享同一份数据,引用计数 | `core`(Runtime、ToolRegistry) | +| `trait` + `dyn` | 定义接口,运行时动态分发(类似 interface) | `tools`, `hooks`, `protocol` | +| `serde` | 序列化/反序列化框架,`#[derive(Serialize, Deserialize)]` | 全项目 | +| `clap` | CLI 参数解析框架,`#[derive(Parser)]` | `cli` | +| `ratatui` | 终端 UI 框架,画 TUI 界面 | `tui` | +| `axum` | HTTP 服务器框架,基于 tokio | `app-server` | +| `rusqlite` | SQLite 绑定,用于会话持久化 | `state` | +| `tracing` | 结构化日志框架(比 `println!` 更专业) | 全局 | + +## 许可证 + +CodeWhale 本身是 MIT 协议。这套文档也是。 \ No newline at end of file diff --git a/my-docs/call-stack/01-call-stack-analysis.md b/my-docs/call-stack/01-call-stack-analysis.md new file mode 100644 index 0000000000..f04120540c --- /dev/null +++ b/my-docs/call-stack/01-call-stack-analysis.md @@ -0,0 +1,129 @@ +# Issue #3915 调用栈分析 + +## 两条路径,两个 Bug + +### 路径 A: `$echo hello world` — 参数传入但被丢弃 + +``` +用户输入: "$echo hello world" + │ + ▼ +commands/mod.rs:109 execute("$echo hello world", app) + │ + ▼ +commands/mod.rs:114-126 检测 $ 前缀 → 剥离 → 空格拆分 + skill_input = "echo hello world" + parts = ["echo", "hello world"] + skill_name = "echo" ← 名字正确 + arg = Some("hello world") ← 参数存在! + │ + ▼ +commands/mod.rs:127 run_skill_by_name(app, "echo", Some("hello world")) + │ + ▼ +skills.rs:288-298 ╔══════════════════════════════════════════╗ + ║ pub fn run_skill_by_name( ║ + ║ app, name: "echo", ║ + ║ _arg: Some("hello world") ← 注意! ║ + ║ ) { ║ + ║ activate_skill(app, "echo"); ║ + ║ // _arg 被标记为未使用 ║ + ║ // "hello world" 在此丢失 ❌ ║ + ║ } ║ + ╚══════════════════════════════════════════╝ + │ + ▼ +skills.rs:326-347 activate_skill(app, "echo") + app.active_skill = Some(instruction_str) + 返回 "Skill 'echo' activated. Type your request..." + │ + ▼ +用户看到: skill 激活了,但 "hello world" 消失了 +用户必须重新输入一遍 +``` + +**根因**: `skills.rs:290` 的参数名是 `_arg`(下划线前缀),编译器会忽略。 +调用者 `mod.rs:127` 明明传了 `arg`,但函数内部完全不使用它。 + +--- + +### 路径 B: `/skill echo hello world` — 名字拆出来了但没用 + +``` +用户输入: "/skill echo hello world" + │ + ▼ +commands/mod.rs:109 execute("/skill echo hello world", app) + │ 没有 $ 前缀,走普通 command 路径 + │ + ▼ +commands/mod.rs:135-145 按空格拆分 + command = "skill" ← 去掉 / 前缀 + arg = Some("echo hello world") ← 整个剩余部分作为 arg + │ + ▼ +commands/mod.rs:173-174 注册表查找 "skill" → SkillCmd::execute(app, arg) + │ + ▼ +skills.rs:300 run_skill(app, Some("echo hello world")) + raw = "echo hello world" + │ + ▼ +skills.rs:312-313 splitn(2, whitespace) + head = "echo" + rest = "hello world" ← 名字和参数正确拆开了! + │ + ▼ +skills.rs:323 activate_skill(app, raw) ╔═══════════════════════╗ + └── 传入整个 raw! ║ + 应该是 activate_skill(app, head) ║ + 但实际传了 "echo hello world" ║ + 查不到这个 skill → 报错 ❌ ║ + ╚═══════════════════════╝ + │ + ▼ +返回: "Skill 'echo hello world' not found." +``` + +**根因**: `skills.rs:323` 应该传 `head`(已拆出的名字),实际传了 `raw`(完整字符串)。 + +--- + +### 对照: `$echo`(无参)— 正常流程 + +``` +用户输入: "$echo" + │ → skill_name = "echo", arg = None + ▼ +run_skill_by_name(app, "echo", None) → activate_skill(app, "echo") + app.active_skill = Some(instruction) + │ +用户下一条消息: "hello world" + 回车 + │ + ▼ +ui.rs:6840-6842 build_queued_message() + skill_instruction = app.active_skill.take() + QueuedMessage::new("hello world", skill_instruction) + │ + ▼ +AI 收到: [skill_instruction] + "hello world" → 正确输出 ✅ +``` + +--- + +## 修复方案 + +### 修复 A: `run_skill_by_name` +当 `arg` 不为空时,激活 skill 后立即将 arg 文本作为 `QueuedMessage` 排队。 + +### 修复 B: `run_skill` +`activate_skill(app, raw)` → `activate_skill(app, head)` +然后如果 `rest` 不为空,将其排队。 + +### 关键机制 +`QueuedMessage` 有两个字段: +- `display: String` — 显示给用户的文本 +- `skill_instruction: Option` — skill 的 system prompt + +`app.queue_message()` 将消息加入队列,`app.active_skill.take()` 取出 skill instruction。 +当用户点击回车时,`ui.rs:6840-6842` 就是这个逻辑——我们的修复就是把这个逻辑提前到 skill 激活时执行。 \ No newline at end of file