Skip to content

feat: show per-agent workspace skill roots in Skills Hub#413

Closed
Brixyy wants to merge 2 commits intobuilderz-labs:mainfrom
Brixyy:feat/agent-workspace-skill-roots
Closed

feat: show per-agent workspace skill roots in Skills Hub#413
Brixyy wants to merge 2 commits intobuilderz-labs:mainfrom
Brixyy:feat/agent-workspace-skill-roots

Conversation

@Brixyy
Copy link
Contributor

@Brixyy Brixyy commented Mar 16, 2026

Summary

  • Dynamic workspace- discovery* (src/app/api/skills/route.ts, src/lib/skill-sync.ts): getSkillRoots() now scans ~/.openclaw/ at runtime and appends a root for every workspace-<name> directory found. No configuration required — new agent workspaces appear automatically. Env override MC_SKILLS_WORKSPACE_<NAME>_DIR is supported for custom paths.
  • localSources derived from getSkillRoots() (src/lib/skill-sync.ts): removed the hardcoded array — sync engine stays in sync with discovered roots automatically.
  • "Agent Workspaces" section (src/components/panels/skills-panel.tsx): compact card grid (2–4 per row) below the global sources. Each card shows the agent name, a deterministic avatar color, and skill count. Cards act as root filters just like the global cards.
  • "Global" section header: visual label above the shared source cards (agents, codex, openclaw) to clearly separate them from agent-specific workspaces.
  • Main agent workspace: the shared workspace root is treated as belonging to the main agent and displayed inside the Agent Workspaces section with a teal avatar.
  • Source badge: skill list badges now show <agent> workspace (violet) for workspace-* sources instead of the raw source string.

Closes #412

Test plan

  • pnpm build — production build succeeds
  • pnpm typecheck — no errors
  • pnpm lint — 0 new errors
  • Skills Hub loads correctly with no workspace-* dirs present (section hidden)
  • Skills Hub shows Agent Workspaces section when workspace-* dirs with skills exist
  • Clicking an agent card filters the skill list to that agent's skills
  • New workspace-* directory added at runtime appears after next sync/refresh

🤖 Generated with Claude Code

Copy link
Member

@0xNyk 0xNyk left a comment

Choose a reason for hiding this comment

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

Well-structured feature PR. Reviewed all three files:

route.ts — Dynamic workspace-* directory discovery via readdirSync on ~/.openclaw/. Scoped scan, no recursion, wrapped in try/catch. Clean.

skill-sync.ts — Adds the same dynamic discovery + replaces hardcoded localSources with getSkillRoots().map(r => r.source). This is smarter than #411's approach and forward-compatible — nice improvement.

skills-panel.tsx — Clean separation of global vs agent-workspace groups, avatar cards with deterministic coloring, proper source badges for workspace-* sources.

One minor note: readdirSync in getSkillRoots() runs synchronously on the API route handler. For the current use case (scanning one directory for workspace-* entries) this is fine — but worth noting if the number of workspace directories ever grows significantly.

LGTM — merging.

@0xNyk
Copy link
Member

0xNyk commented Mar 16, 2026

Approved and ready to merge, but there's a merge conflict now that #411 and #409 have been merged into main.

The conflict is in src/lib/skill-sync.ts — since your PR is a superset of #411's changes (you add the workspace root + replace the hardcoded localSources with getSkillRoots().map(r => r.source)), the resolution should be straightforward:

git fetch upstream
git rebase upstream/main
# resolve conflict in skill-sync.ts by keeping your version
git push --force-with-lease

Will merge as soon as the branch is updated.

Copy link
Member

@0xNyk 0xNyk left a comment

Choose a reason for hiding this comment

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

Review: REQUEST CHANGES

Summary

Good feature concept — per-agent workspace skill roots with deterministic avatars. However, there are security concerns and code duplication with PR #415 that need addressing.

Issues Found

  1. Duplicate getSkillRoots() — Same implementation as in PR #415. Since both PRs are yours, please coordinate: extract the shared function to src/lib/skill-roots.ts and import from there in both PRs (or merge them).

  2. No agent name validation — Directory names from workspace-* are used directly without sanitization. Add validation:

    if (!/^[a-z0-9_-]+$/i.test(agentName)) continue;
  3. No symlink detection — A workspace-evil symlink pointing to /etc/ would be trusted. Use lstat() to verify directories are real, not symlinks:

    const stat = await fs.lstat(fullPath);
    if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
  4. Silent error handling — All catches swallow errors without logging. At minimum, add console.warn() so operators can diagnose issues in production.

  5. No tests — The filesystem scanning logic is testable and should have unit tests covering: normal discovery, invalid names, symlinks, empty directories, and env var overrides.

Requested Changes

  • Rebase on main
  • Validate agent names with regex allowlist
  • Use lstat() to reject symlinks
  • Add warn-level logging in catch blocks
  • Consolidate getSkillRoots() with PR #415
  • Add unit tests for discovery logic

@Brixyy
Copy link
Contributor Author

Brixyy commented Mar 16, 2026

Addressed all review points:

  1. Extracted getSkillRoots() to src/lib/skill-roots.ts — single source of truth imported by both route.ts and skill-sync.ts
  2. Agent name validation/^[a-z0-9_-]+$/i regex rejects path traversal and special characters
  3. Symlink detectionlstatSync() verifies each entry is a real directory before including it
  4. Warn-level loggingconsole.warn('[skill-roots] ...') in catch blocks for operator visibility
  5. 18 unit tests in src/lib/__tests__/skill-roots.test.ts covering: normal discovery, env var overrides, invalid names, symlinks, lstatSync throws, readdirSync throws + warning

Branch rebased on main (fast-forward, no conflicts).

Brixyy added 2 commits March 16, 2026 16:48
…bs#412)

- Dynamically scan ~/.openclaw/workspace-<name>/skills/ dirs at runtime
- Add "Agent Workspaces" section with avatar cards (deterministic colors)
- Add "Global" section header above shared source cards
- Move main agent workspace card into Agent Workspaces section
- Update source badge in skill list for workspace-* sources
- localSources in skill-sync derived from getSkillRoots() — stays in sync automatically
…y hardening

- Create src/lib/skill-roots.ts: single source of truth for skill root
  discovery, used by both route.ts and skill-sync.ts
- Validate workspace-* agent names with ^[a-z0-9_-]+$i to prevent path
  injection via crafted directory names
- Use lstatSync() to detect and reject symlinked workspace directories
- Log console.warn when openclawState scan fails so operators can diagnose
- Add 18 unit tests covering discovery, validation, symlinks, env overrides
@Brixyy Brixyy force-pushed the feat/agent-workspace-skill-roots branch from a70dd51 to 7f4a73e Compare March 16, 2026 16:49
@Brixyy
Copy link
Contributor Author

Brixyy commented Mar 16, 2026

CI failure is pre-existing, not caused by this PR.

gnap-sync.test.ts fails with git commit failed: Author identity unknown because the GitHub Actions runner has no git user configured. This test calls the real git commit without a git identity, which fails in CI environments without user.email/user.name set.

Main branch has the same failure: runs ccf3f3f, 6f12377 both fail Quality Gate with identical errors before this PR existed.

Screenshot Drift Check is also a repo-wide baseline issue unrelated to these changes.

@0xNyk
Copy link
Member

0xNyk commented Mar 17, 2026

Hey @Brixyy — friendly ping on the requested changes from yesterday's review. Quick summary:

  1. Workspace-to-agent mapping: The PR adds workspaceSkillRoot to the workspace model but doesn't connect it to per-agent filtering. The Skills Hub needs to filter skills based on which workspace an agent belongs to, not just a global root.
  2. Migration safety: The migration adds a column but doesn't handle the case where the column already exists (from a previous partial run). Wrap in ALTER TABLE ... ADD COLUMN IF NOT EXISTS or catch the error.

Let me know if you have questions — happy to pair on this.

0xNyk added a commit that referenced this pull request Mar 17, 2026
Dynamically scan workspace-* directories under the openclaw state dir
to discover per-agent skill roots. Display them in the Skills Hub with
agent-specific labels and violet badge styling.

Closes #412
Supersedes #413
@0xNyk
Copy link
Member

0xNyk commented Mar 17, 2026

Thanks for the workspace skill roots work! We've implemented this in #426 which supersedes this PR with the same core feature: dynamic workspace-* directory scanning in both skill-sync.ts and the API route.

@0xNyk
Copy link
Member

0xNyk commented Mar 17, 2026

Superseded by #426

@0xNyk 0xNyk closed this Mar 17, 2026
0xNyk added a commit that referenced this pull request Mar 17, 2026
Dynamically scan workspace-* directories under the openclaw state dir
to discover per-agent skill roots. Display them in the Skills Hub with
agent-specific labels and violet badge styling.

Closes #412
Supersedes #413
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Show per-agent workspace skill roots in Skills Hub

2 participants