Persistent home directory, skills seeding/update, and Telegram audit cards#40
Persistent home directory, skills seeding/update, and Telegram audit cards#40chinkan wants to merge 36 commits into
Conversation
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
Pull request overview
This PR refactors Telegram verbose-mode progress reporting into a single persistent "audit card" managed by ToolCallNotifier, and removes the duplicate path that previously streamed tool-status lines into the assistant answer. It also adds a privacy-aware argument preview formatter and a structured plan/tool display model.
Changes:
- Adds a
ToolDisplayStatewith plan checklist + tool activity rendering, parsesplan_create/plan_updatearguments, and tightensformat_args_previewto an allowlist/denylist model. - Introduces
ToolEvent::Finishedand reworksToolCallNotifier::finishto edit the live message into a persistent completed/failed card (or delete it when no activity occurred). - Removes the agent-side
stream_status_txpath so tool status lines no longer enter the answer stream, and adds a source-inspection regression test.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/platform/tool_notifier.rs | Adds display state, planning parsers, privacy-aware preview, persistent finish card, edit retry, and new tests. |
| src/platform/telegram.rs | Forwards a terminal Finished event to the notifier and finalizes the notifier even if the channel closes unexpectedly. |
| src/agent.rs | Removes duplicate tool-status streaming; sends raw arguments_json to the notifier; adds source-inspection regression test. |
| docs/superpowers/specs/2026-05-28-telegram-plan-tool-visuals-design.md | New design spec for verbose-mode audit card. |
| docs/superpowers/plans/2026-05-28-telegram-plan-tool-visuals.md | New implementation plan with step-by-step tasks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
| if !lock_path.exists() { | ||
| let lock = rustfox::skills::update::SkillLock { | ||
| version: 1, | ||
| skills: rustfox::skills::seed::lock_map_for(&config.skills.directory), | ||
| }; |
| .config | ||
| .resolved_home | ||
| .clone() | ||
| .map(|h| h.join("skills-lock.json")) | ||
| .unwrap_or_else(|| std::path::PathBuf::from("skills-lock.json")); |
| impl UpdateReport { | ||
| pub fn summary(&self) -> String { | ||
| format!( | ||
| "Skill update: {} updated, {} backed-up, {} unchanged.", |
| anyhow::bail!("No bundled skills found at {}", bundled.display()); | ||
| } | ||
| let mut lock = read_lock(lock_path); | ||
| tokio::fs::create_dir_all(instance).await.ok(); |
| } | ||
| } | ||
| s | ||
| text |
| /// Resolve the home root and every data path, create directories, and write | ||
| /// the resolved absolute paths back into the config fields. Returns any | ||
| /// legacy-path warnings (relative overrides) for the caller to log. |
| > **Persistent home:** RustFox keeps all state under `~/.rustfox` by default | ||
| > (config, database, skills, agents, and a durable `workspace/` sandbox). | ||
| > Override with the `RUSTFOX_HOME` environment variable or `[general].home`. | ||
| > See [docs/persistent-home-directory.md](docs/persistent-home-directory.md). |
…nd bundled layers - Introduced `SkillSource` enum to differentiate between instance and bundled skills. - Refactored `SkillRegistry` to maintain separate maps for instance and bundled skills, along with a mapping for base directories. - Updated `load_skills_from_dir` to accept a `SkillSource` parameter and adjusted all relevant calls. - Added `bundled_directory` configuration fields to `SkillsConfig` and `AgentsConfig`. - Enhanced agent tools to resolve skill and agent file paths using the new registry structure. - Modified startup logic to load both instance and bundled skills, merging them appropriately. - Updated tests to cover new functionality and ensure correct behavior of skill loading and resolution.
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
…nd specification checks
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
| { | ||
| let mut skills = self.skills.write().await; | ||
| if let Ok(reg) = s_instance { | ||
| skills.instance_skills = reg.instance_skills; | ||
| for (k, v) in ®.skill_base_dirs { | ||
| skills.skill_base_dirs.insert(k.clone(), v.clone()); | ||
| } | ||
| } | ||
| if let Ok(reg) = s_bundled { | ||
| skills.bundled_skills = reg.bundled_skills; | ||
| for (k, v) in ®.skill_base_dirs { | ||
| skills | ||
| .skill_base_dirs | ||
| .entry(k.clone()) | ||
| .or_insert_with(|| v.clone()); | ||
| } | ||
| } | ||
| } |
| if instance_hash.as_deref() == Some(bundled_hash.as_str()) { | ||
| report.skipped.push(name); | ||
| continue; | ||
| } |
| pub fn hash_skill_dir(dir: &Path) -> Option<String> { | ||
| let primary = ["SKILL.md", "AGENT.md"] | ||
| .into_iter() | ||
| .map(|f| dir.join(f)) | ||
| .find(|p| p.is_file())?; | ||
| let bytes = std::fs::read(&primary).ok()?; | ||
| let mut h = Sha256::new(); | ||
| h.update(&bytes); | ||
| Some(format!("{:x}", h.finalize())) | ||
| } |
| } | ||
| // Fallback: truncate raw JSON | ||
| crate::utils::strings::truncate_chars(args_json, 60) | ||
| let joined = parts.join(", "); | ||
| crate::utils::strings::truncate_chars(&joined, MAX_DISPLAY_FIELD_CHARS) |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
| if !unmodified { | ||
| // Back up the primary file to a sibling path OUTSIDE the dir being replaced. | ||
| for f in ["SKILL.md", "AGENT.md"] { | ||
| let p = dst.join(f); | ||
| if p.is_file() { | ||
| let bak = instance.join(format!("{name}.{f}.bak.tmp")); | ||
| let _ = tokio::fs::copy(&p, &bak).await; | ||
| } | ||
| } | ||
| let _ = tokio::fs::remove_dir_all(&dst).await; | ||
| copy_dir_recursive_pub(&src, &dst).await?; | ||
| // Move the temp backups into the freshly-copied dir as <file>.bak | ||
| for f in ["SKILL.md", "AGENT.md"] { | ||
| let bak = instance.join(format!("{name}.{f}.bak.tmp")); | ||
| if bak.is_file() { | ||
| let _ = tokio::fs::rename(&bak, dst.join(format!("{f}.bak"))).await; | ||
| } | ||
| } |
There was a problem hiding this comment.
Fixed in the latest commit. The backup logic now renames the entire instance directory to <name>.bak (removing a stale .bak dir first if one exists) instead of selectively copying only SKILL.md/AGENT.md. This preserves all user files. The docstring and test have been updated to match.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
…f only SKILL.md/AGENT.md
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
This PR introduces three interconnected features:
1. Persistent home directory model (
~/.rustfox)RUSTFOX_HOMEenv →[general].homeconfig →~/.rustfoxconfig.toml,rustfox.db,skills/,agents/,workspace/,artifacts/,user_model.mdsrc/home.rs+Config::resolve()— unset paths default under home, absolute overrides preserved, relative overrides emit legacy warnings2. Skills/agents seeding and update engine (
src/skills/)skills/andagents/directories are seed-copied to the home on first run (seed_dir_if_empty)/update-skillscommand re-syncs bundled content using content-hash lock files (skills-lock.json,agents-lock.json)*.bakbefore being overwritten3. Telegram audit cards (
ToolCallNotifier)format_args_previewusing allowlist/denylist model to avoid leaking secretsplan_create/plan_updateparsed into a visual checklist with step status markersFinishedevent persists a completed/failed summary card (or deletes if no activity)Additional changes