feat(dashboard): SKILL.md editor on Skills page + attach-skill selector in cron modals (#44231)
Headless/VPS users (dashboard-over-Tailscale, no comfortable SSH) could list/toggle/install skills and create/edit cron jobs, but not author a custom skill or link one to a cron job — the UI set WHEN a job runs, but not WHICH skill it uses. - Skills page: 'New skill' button + per-row edit pencil open a SKILL.md editor dialog (frontmatter + body, server-side validation via the same _create_skill/_edit_skill path as the agent's skill_manage tool). - New endpoints: GET /api/skills/content, POST /api/skills, PUT /api/skills/content — all profile-scoped via _profile_scope(), which now also retargets tools.skill_manager_tool's import-time SKILLS_DIR binding. - Cron page: skills multi-select in both create and edit modals (parity with hermes cron --skill / edit --add-skill); CronJobCreate gains a skills field; job cards show an attached-skills badge. update_job already accepted skills in updates. - Tests: 17 new endpoint tests (content read, create/edit validation + profile scoping + auth gate, cron skills round-trip).
This commit is contained in:
+32
-2
@@ -469,7 +469,7 @@ export const api = {
|
||||
fetchJSON<CronJob[]>(`/api/cron/jobs?profile=${encodeURIComponent(profile)}`),
|
||||
getCronDeliveryTargets: () =>
|
||||
fetchJSON<{ targets: CronDeliveryTarget[] }>("/api/cron/delivery-targets"),
|
||||
createCronJob: (job: { prompt: string; schedule: string; name?: string; deliver?: string }, profile = "default") =>
|
||||
createCronJob: (job: { prompt: string; schedule: string; name?: string; deliver?: string; skills?: string[] }, profile = "default") =>
|
||||
fetchJSON<CronJob>(`/api/cron/jobs?profile=${encodeURIComponent(profile)}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -479,7 +479,7 @@ export const api = {
|
||||
fetchJSON<CronJob>(`/api/cron/jobs/${encodeURIComponent(id)}/pause?profile=${encodeURIComponent(profile)}`, { method: "POST" }),
|
||||
updateCronJob: (
|
||||
id: string,
|
||||
updates: { prompt?: string; schedule?: string; name?: string; deliver?: string },
|
||||
updates: { prompt?: string; schedule?: string; name?: string; deliver?: string; skills?: string[] },
|
||||
profile = "default",
|
||||
) =>
|
||||
fetchJSON<CronJob>(
|
||||
@@ -605,6 +605,22 @@ export const api = {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, enabled, profile: profile || undefined }),
|
||||
}),
|
||||
getSkillContent: (name: string, profile?: string) =>
|
||||
fetchJSON<SkillContent>(
|
||||
`/api/skills/content?name=${encodeURIComponent(name)}${profile ? `&profile=${encodeURIComponent(profile)}` : ""}`,
|
||||
),
|
||||
createSkill: (skill: { name: string; content: string; category?: string }, profile?: string) =>
|
||||
fetchJSON<SkillWriteResult>("/api/skills", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ...skill, profile: profile || undefined }),
|
||||
}),
|
||||
updateSkillContent: (name: string, content: string, profile?: string) =>
|
||||
fetchJSON<SkillWriteResult>("/api/skills/content", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, content, profile: profile || undefined }),
|
||||
}),
|
||||
getToolsets: (profile?: string) =>
|
||||
fetchJSON<ToolsetInfo[]>(`/api/tools/toolsets${profileQuery(profile)}`),
|
||||
toggleToolset: (name: string, enabled: boolean, profile?: string) =>
|
||||
@@ -1791,6 +1807,7 @@ export interface CronJob {
|
||||
name?: string | null;
|
||||
prompt?: string | null;
|
||||
script?: string | null;
|
||||
skills?: string[] | null;
|
||||
schedule?: { kind?: string; expr?: string; display?: string };
|
||||
schedule_display?: string | null;
|
||||
enabled: boolean;
|
||||
@@ -1815,6 +1832,19 @@ export interface SkillInfo {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface SkillContent {
|
||||
name: string;
|
||||
content: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface SkillWriteResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
path?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ToolsetInfo {
|
||||
name: string;
|
||||
label: string;
|
||||
|
||||
Reference in New Issue
Block a user