From 42aef6e13e8ea740866739502ff8bacdfc95d181 Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Wed, 25 Mar 2026 15:37:08 +0800 Subject: [PATCH] fix: address review issues in skills system - Fix authorization bypass in DeleteSkillFile (missing workspace/role check) - Extract skills page into features/skills/ module (thin route shell) - Fix skill files disappearing after save (use API return values + merge in refreshSkills) - Fix silently swallowed DB errors in ListAgents/GetAgent skill queries Co-Authored-By: Claude Opus 4.6 --- apps/web/app/(dashboard)/skills/page.tsx | 539 +---------------- apps/web/features/skills/components/index.ts | 1 + .../skills/components/skills-page.tsx | 548 ++++++++++++++++++ apps/web/features/skills/index.ts | 1 + apps/web/features/workspace/store.ts | 32 +- server/internal/handler/agent.go | 12 +- server/internal/handler/skill.go | 9 + 7 files changed, 599 insertions(+), 543 deletions(-) create mode 100644 apps/web/features/skills/components/index.ts create mode 100644 apps/web/features/skills/components/skills-page.tsx create mode 100644 apps/web/features/skills/index.ts diff --git a/apps/web/app/(dashboard)/skills/page.tsx b/apps/web/app/(dashboard)/skills/page.tsx index 95017940..309fe556 100644 --- a/apps/web/app/(dashboard)/skills/page.tsx +++ b/apps/web/app/(dashboard)/skills/page.tsx @@ -1,538 +1 @@ -"use client"; - -import { useState, useEffect, useCallback } from "react"; -import { - Sparkles, - Plus, - Trash2, - Save, - FileText, - FolderOpen, - AlertCircle, - X, -} from "lucide-react"; -import type { Skill, CreateSkillRequest, UpdateSkillRequest } from "@multica/types"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, -} from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { Label } from "@/components/ui/label"; -import { api } from "@/shared/api"; -import { useAuthStore } from "@/features/auth"; -import { useWorkspaceStore } from "@/features/workspace"; -import { useWSEvent } from "@/features/realtime"; - -// --------------------------------------------------------------------------- -// Create Skill Dialog -// --------------------------------------------------------------------------- - -function CreateSkillDialog({ - onClose, - onCreate, -}: { - onClose: () => void; - onCreate: (data: CreateSkillRequest) => Promise; -}) { - const [name, setName] = useState(""); - const [description, setDescription] = useState(""); - const [creating, setCreating] = useState(false); - - const handleSubmit = async () => { - if (!name.trim()) return; - setCreating(true); - try { - await onCreate({ name: name.trim(), description: description.trim() }); - onClose(); - } catch { - setCreating(false); - } - }; - - return ( - { if (!v) onClose(); }}> - - - Create Skill - - Create a reusable skill that can be assigned to agents. - - - -
-
- - setName(e.target.value)} - placeholder="e.g. Code Review, Bug Triage" - className="mt-1" - onKeyDown={(e) => e.key === "Enter" && handleSubmit()} - /> -
-
- - setDescription(e.target.value)} - placeholder="Brief description of what this skill does" - className="mt-1" - /> -
-
- - - - - -
-
- ); -} - -// --------------------------------------------------------------------------- -// Skill List Item -// --------------------------------------------------------------------------- - -function SkillListItem({ - skill, - isSelected, - onClick, -}: { - skill: Skill; - isSelected: boolean; - onClick: () => void; -}) { - return ( - - ); -} - -// --------------------------------------------------------------------------- -// File Editor -// --------------------------------------------------------------------------- - -function FileEditor({ - files, - onFilesChange, -}: { - files: { path: string; content: string }[]; - onFilesChange: (files: { path: string; content: string }[]) => void; -}) { - const [editingIndex, setEditingIndex] = useState(null); - - const addFile = () => { - onFilesChange([...files, { path: "", content: "" }]); - setEditingIndex(files.length); - }; - - const updateFile = (index: number, field: "path" | "content", value: string) => { - const updated = files.map((f, i) => - i === index ? { ...f, [field]: value } : f, - ); - onFilesChange(updated); - }; - - const removeFile = (index: number) => { - onFilesChange(files.filter((_, i) => i !== index)); - if (editingIndex === index) setEditingIndex(null); - }; - - return ( -
-
-
-

Supporting Files

-

- Templates, scripts, or reference files available to the agent. -

-
- -
- - {files.length === 0 ? ( -
- -

No supporting files

-
- ) : ( -
- {files.map((file, index) => ( -
-
- - updateFile(index, "path", e.target.value)} - placeholder="path/to/file.md" - className="h-7 border-0 p-0 text-xs font-mono shadow-none focus-visible:ring-0" - /> - - -
- {editingIndex === index && ( -