Implement Skills feature: filesystem-based skills with agent assignment

- Add SkillService: scans data/skills/ directory, parses _meta.json and SKILL.md frontmatter
- Add SkillEndpoint: REST API for listing skills and managing agent-skill assignments
- Add agent_skills DB table for agent-to-skill mapping (only relation in DB, skills live on filesystem)
- Extend SystemPromptBuilder: inject assigned skills' SKILL.md content into system prompt
- Extend ChatEndpoint + SessionEndpoint: load agent skills and pass to prompt builder
- Frontend: skill checkboxes in agent form, API functions for skill management
- Add form.css styling for skill list items
This commit is contained in:
Meleki committed 2026-03-01 17:22:14 +01:00
1 parent b18dc78651
commit cf6caea2dc
11 files changed
+454 -6

No files matched your search

+29
View File
@@ -144,6 +144,35 @@ async function apiGitStatus(sessionId) {
return resp.json();
}
// ── Skills API ──
async function apiGetSkills() {
const resp = await fetch('/api/skills');
return resp.json();
}
async function apiGetAgentSkills(agentId) {
const resp = await fetch(`/api/agents/${agentId}/skills`);
return resp.json();
}
async function apiSetAgentSkills(agentId, skills) {
const resp = await fetch(`/api/agents/${agentId}/skills`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ skills })
});
return resp.json();
}
async function apiAddAgentSkill(agentId, slug) {
return fetch(`/api/agents/${agentId}/skills/${slug}`, { method: 'POST' });
}
async function apiRemoveAgentSkill(agentId, slug) {
return fetch(`/api/agents/${agentId}/skills/${slug}`, { method: 'DELETE' });
}
async function apiBackupDb() {
const resp = await fetch('/api/backup', { method: 'POST' });
if (!resp.ok) throw new Error('Backup fehlgeschlagen');
+53 -1
View File
@@ -35,6 +35,8 @@ registerPage('agent', {
const addCommandBtn = document.getElementById('agentAddCommandBtn');
const filesSection = document.getElementById('agentFilesSection');
const filesBtn = document.getElementById('agentFilesBtn');
const skillsSection = document.getElementById('agentSkillsSection');
const skillList = document.getElementById('agentSkillList');
_agentAvatarData = null;
@@ -59,6 +61,22 @@ registerPage('agent', {
// Custom Commands laden
loadCommandsIntoUI(commandsList, agent.customCommands);
// Skills laden
const [allSkills, agentSkills] = await Promise.all([
apiGetSkills(),
apiGetAgentSkills(agentId)
]);
const assignedSlugs = agentSkills.map(s => s.slug);
skillsSection.hidden = false;
renderSkillCheckboxes(skillList, allSkills, assignedSlugs);
} else {
// Neue Agents: Skills-Liste auch anzeigen
const allSkills = await apiGetSkills();
if (allSkills.length > 0) {
skillsSection.hidden = false;
renderSkillCheckboxes(skillList, allSkills, []);
}
}
// Agent-Dateien öffnen
@@ -125,9 +143,17 @@ registerPage('agent', {
};
if (isNew) {
await apiCreateAgent(data);
const created = await apiCreateAgent(data);
// Skills zuweisen falls welche ausgewählt
const selectedSlugs = getSelectedSkills(skillList);
if (selectedSlugs.length > 0) {
await apiSetAgentSkills(created.agentId, selectedSlugs);
}
} else {
await apiUpdateAgent(agentId, data);
// Skills aktualisieren
const selectedSlugs = getSelectedSkills(skillList);
await apiSetAgentSkills(agentId, selectedSlugs);
}
navigateTo('dashboard');
});
@@ -150,3 +176,29 @@ registerPage('agent', {
_agentAvatarData = null;
}
});
function renderSkillCheckboxes(container, allSkills, assignedSlugs) {
container.innerHTML = '';
if (allSkills.length === 0) {
container.innerHTML = '<div class="skill-empty">Keine Skills verfügbar. Lege Skills in <code>data/skills/</code> ab.</div>';
return;
}
for (const skill of allSkills) {
const checked = assignedSlugs.includes(skill.slug) ? 'checked' : '';
const version = skill.version ? ` <span class="skill-version">v${skill.version}</span>` : '';
const desc = skill.description ? `<span class="skill-desc">${skill.description}</span>` : '';
const row = document.createElement('label');
row.className = 'skill-item';
row.innerHTML = `
<input type="checkbox" value="${skill.slug}" ${checked}>
<span class="skill-name">${skill.name}${version}</span>
${desc}
`;
container.appendChild(row);
}
}
function getSelectedSkills(container) {
return Array.from(container.querySelectorAll('input[type="checkbox"]:checked'))
.map(cb => cb.value);
}