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:
1 parent
b18dc78651
commit
cf6caea2dc
11 files changed
+454
-6
No files matched your search
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user