fix: Add Agent creates runtime dirs + SOUL.md personality support

- POST /api/agents/bot now creates agents/<id>/sessions/ runtime directory
- POST /api/agents (sub-agent) also creates agents/<id>/sessions/
- Add Agent form now includes Purpose and Personality fields
- Personality keywords generate structured SOUL.md, empty uses default growth prompt
- Notes now remind to restart gateway for new bots

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 21:15:29 +00:00
co-authored by Claude Opus 4.6
parent c6150d117b
commit 69269963d0
+22 -6
View File
@@ -369,7 +369,7 @@ async function handleApi(req, res, urlObj, body) {
// POST /api/agents/bot — create agent with its own bot token // POST /api/agents/bot — create agent with its own bot token
if (method === 'POST' && pathname === '/api/agents/bot') { if (method === 'POST' && pathname === '/api/agents/bot') {
const { botToken, agentId, name, model, workspace } = body; const { botToken, agentId, name, model, workspace, purpose, personality } = body;
if (!botToken || !botToken.trim()) { if (!botToken || !botToken.trim()) {
res.writeHead(400); res.end(JSON.stringify({ error: 'Bot Token is required' })); return; res.writeHead(400); res.end(JSON.stringify({ error: 'Bot Token is required' })); return;
} }
@@ -425,16 +425,21 @@ async function handleApi(req, res, urlObj, body) {
await fsp.mkdir(wsPath, { recursive: true }); await fsp.mkdir(wsPath, { recursive: true });
await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true }); await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true });
const agentName = name || agentId; const agentName = name || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(agentName, '', ''), 'utf8'); await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(agentName, purpose || '', personality || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(agentName, ''), 'utf8'); await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(agentName, ''), 'utf8');
// Create agents/<id>/ runtime directory (required by OpenClaw gateway)
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
await fsp.mkdir(path.join(agentRuntimeDir, 'sessions'), { recursive: true });
res.writeHead(200); res.writeHead(200);
res.end(JSON.stringify({ res.end(JSON.stringify({
ok: true, agentId, workspacePath: wsPath, configBackup: bakPath, ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [ notes: [
'Agent created with its own bot token', 'Agent created with its own bot token',
'Workspace directory created with SOUL.md and MEMORY.md', 'Workspace directory created with SOUL.md and MEMORY.md',
'Runtime directory created at agents/' + agentId + '/',
'Configuration updated and backed up', 'Configuration updated and backed up',
'Changes take effect in ~300ms without restart' 'Restart gateway to load new bot: openclaw gateway restart'
] ]
})); }));
return; return;
@@ -487,13 +492,17 @@ async function handleApi(req, res, urlObj, body) {
const name = displayName || agentId; const name = displayName || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose, personality), 'utf8'); await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose, personality), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory), 'utf8'); await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory), 'utf8');
// Create agents/<id>/ runtime directory (required by OpenClaw gateway)
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
await fsp.mkdir(path.join(agentRuntimeDir, 'sessions'), { recursive: true });
res.writeHead(200); res.writeHead(200);
res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath, res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [ notes: [
'Sub-agent created and shares parent bot', 'Sub-agent created and shares parent bot',
'Workspace directory created with SOUL.md and MEMORY.md', 'Workspace and runtime directories created',
'Configuration updated and backed up', 'Configuration updated and backed up',
'Changes take effect in ~300ms without restart' 'Restart gateway to apply: openclaw gateway restart'
], ],
})); }));
return; return;
@@ -2533,6 +2542,11 @@ function buildAddAgentForm() {
'<div class="form-group"><label>' + t('wiz.model') + '</label>' + '<div class="form-group"><label>' + t('wiz.model') + '</label>' +
'<select id="fa-model">' + modelOpts + '</select>' + '<select id="fa-model">' + modelOpts + '</select>' +
'</div>' + '</div>' +
'<div class="form-group"><label>' + t('wiz.purpose') + '</label>' +
'<textarea id="fa-purpose" placeholder="' + t('wiz.purposePh') + '" rows="2"></textarea></div>' +
'<div class="form-group"><label>' + t('wiz.soul') + ' <span style="color:var(--muted);font-weight:400">' + t('wiz.soulHint') + '</span></label>' +
'<input id="fa-soul" placeholder="' + t('wiz.soulPh') + '">' +
'<span class="hint-text">' + t('wiz.soulTip') + '</span></div>' +
'<div style="display:flex;gap:8px;margin-top:14px">' + '<div style="display:flex;gap:8px;margin-top:14px">' +
'<button class="btn-primary" onclick="submitAddAgent()">' + t('agents.addAgentSubmit') + '</button>' + '<button class="btn-primary" onclick="submitAddAgent()">' + t('agents.addAgentSubmit') + '</button>' +
'<button class="btn-ghost" onclick="clearAddForm()">' + t('btn.cancel') + '</button>' + '<button class="btn-ghost" onclick="clearAddForm()">' + t('btn.cancel') + '</button>' +
@@ -2590,13 +2604,15 @@ async function submitAddAgent() {
const name = document.getElementById('fa-name').value.trim(); const name = document.getElementById('fa-name').value.trim();
const workspace = document.getElementById('fa-workspace').value.trim(); const workspace = document.getElementById('fa-workspace').value.trim();
const model = document.getElementById('fa-model').value; const model = document.getElementById('fa-model').value;
const purpose = (document.getElementById('fa-purpose')?.value||'').trim();
const personality = (document.getElementById('fa-soul')?.value||'').trim();
if (!token) { toast(t('agents.errToken'), 'err'); return; } if (!token) { toast(t('agents.errToken'), 'err'); return; }
if (!agentId) { toast('Agent ID is required', 'err'); return; } if (!agentId) { toast('Agent ID is required', 'err'); return; }
if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) { toast('Agent ID must contain only alphanumeric characters, underscores, or dashes', 'err'); return; } if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) { toast('Agent ID must contain only alphanumeric characters, underscores, or dashes', 'err'); return; }
if (!name) { toast(t('agents.errName'), 'err'); return; } if (!name) { toast(t('agents.errName'), 'err'); return; }
if (!workspace) { toast('Workspace name is required', 'err'); return; } if (!workspace) { toast('Workspace name is required', 'err'); return; }
try { try {
const payload = { botToken: token, agentId, name, workspace, model: model === '__default__' ? '' : model }; const payload = { botToken: token, agentId, name, workspace, model: model === '__default__' ? '' : model, purpose, personality };
const r = await api('POST', '/api/agents/bot', payload); const r = await api('POST', '/api/agents/bot', payload);
toast('Agent created successfully', 'ok'); toast('Agent created successfully', 'ok');
closePopover(); closePopover();