diff --git a/openclaw-manager.js b/openclaw-manager.js index 817801c..7ad0db7 100644 --- a/openclaw-manager.js +++ b/openclaw-manager.js @@ -352,8 +352,12 @@ async function handleApi(req, res, urlObj, body) { // Check if agent has its own bot account (binding with accountId but no peer, or accountId matching agent ID) const botBinding = bindings.find(b => b.agentId === a.id && b.match?.accountId && !b.match?.peer); const hasOwnBot = botBinding ? true : false; + const accountId = botBinding?.match?.accountId || null; + // For sub-agents (with peer match), find which accountId they belong to + const parentBinding = bindings.find(b => b.agentId === a.id && b.match?.accountId); + const parentAccountId = parentBinding?.match?.accountId || null; return { ...a, groupId, requireMention: groupId ? (groups[groupId]?.requireMention ?? true) : null, - effectiveModel: modelVal || defaults.model?.primary || '默认', hasOwnBot }; + effectiveModel: modelVal || defaults.model?.primary || '默认', hasOwnBot, accountId, parentAccountId }; }); res.writeHead(200); res.end(JSON.stringify({ agents: enriched, defaults })); @@ -1466,12 +1470,9 @@ main { padding:20px; max-width:1280px; margin:0 auto; } .inline-sel { background:var(--bg); border:1px solid var(--border); color:var(--text); border-radius:6px; padding:5px 8px; font-size:12px; } .inline-sel:focus { outline:none; border-color:var(--accent); } -/* Agents split layout */ -.agents-split { display:flex; gap:20px; height:calc(100vh - 160px); } -.agents-left { flex:0 0 40%; display:flex; flex-direction:column; gap:12px; overflow-y:auto; } -.agents-right { flex:1; overflow-y:auto; padding-right:4px; } -.agents-left-btns { display:flex; gap:8px; } -.agents-left-btns .btn-primary { flex:1; } +/* Agents layout — buttons top, tree below */ +.agents-top-btns { display:flex; gap:10px; justify-content:center; margin-bottom:18px; } +.agents-tree-wrap { max-width:720px; margin:0 auto; overflow-y:auto; } /* Add form */ .add-form { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:16px; } @@ -1488,7 +1489,12 @@ main { padding:20px; max-width:1280px; margin:0 auto; } .agent-tree-root .tree-main:hover { border-color:var(--accent); } .agent-tree-root .tree-main .tree-title { font-size:14px; font-weight:600; display:flex; align-items:center; gap:8px; } .agent-tree-root .tree-main .tree-meta { font-size:11px; color:var(--muted); margin-top:4px; } -.tree-children { margin-left:20px; border-left:2px solid var(--border); padding-left:14px; margin-top:6px; } +.tree-children-wrap { position:relative; margin-left:20px; padding-left:14px; margin-top:6px; } +.tree-children-wrap::before { content:''; position:absolute; left:0; top:0; bottom:8px; width:2px; background:var(--border); } +.tree-toggle { position:absolute; left:-8px; top:-4px; width:18px; height:18px; border-radius:50%; background:var(--surface); border:1.5px solid var(--border); color:var(--muted); font-size:12px; line-height:15px; text-align:center; cursor:pointer; z-index:2; padding:0; } +.tree-toggle:hover { border-color:var(--accent); color:var(--accent); } +.tree-children { } +.tree-children.collapsed { display:none; } .tree-child { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:10px 12px; margin-bottom:8px; } .tree-child:hover { border-color:var(--accent); } .tree-child .tree-title { font-size:13px; font-weight:600; display:flex; align-items:center; gap:6px; } @@ -1729,19 +1735,13 @@ const MAIN_HTML_BODY = String.raw`
-
- -
-
- - -
-
-
- -
-
No Agents
-
+
+ + +
+
+
+
No Agents
@@ -2631,63 +2631,92 @@ function renderAgents() { const el = document.getElementById('agentTree'); if (!S.agents.length) { el.innerHTML = '
' + t('agents.empty') + '
'; return; } - // Build tree: main agent as root, subagents as children - const mainAgent = S.agents.find(a => a.id === 'main'); - const subs = S.agents.filter(a => a.id !== 'main'); + // Build tree: each hasOwnBot agent is a root, others are sub-agents grouped by parentAccountId + const roots = S.agents.filter(a => a.hasOwnBot); + const subs = S.agents.filter(a => !a.hasOwnBot); + + // Map accountId -> root agent for sub-agent grouping + const rootByAccount = {}; + roots.forEach(a => { if (a.accountId) rootByAccount[a.accountId] = a; }); + + // Group subs under their parent + const subsByRoot = {}; + const orphanSubs = []; + subs.forEach(a => { + const parentAcct = a.parentAccountId; + if (parentAcct && rootByAccount[parentAcct]) { + const rootId = rootByAccount[parentAcct].id; + if (!subsByRoot[rootId]) subsByRoot[rootId] = []; + subsByRoot[rootId].push(a); + } else { + orphanSubs.push(a); + } + }); + + function agentCard(a, isRoot) { + let h = ''; + const icon = isRoot ? '🤖' : '📱'; + const cls = isRoot ? 'tree-main' : 'tree-child'; + h += '
'; + h += '
' + icon + ' ' + esc(a.name || a.id); + if (isRoot) h += ' ' + (a.id === 'main' ? t('agents.main') : 'Bot') + ''; + if (a.groupId) h += ' ' + esc(a.groupId) + ''; + h += '
'; + h += '
🧠 ' + esc(a.effectiveModel) + '
'; + if (a.workspace) h += '
📁 ' + esc(a.workspace) + '
'; + h += '
'; + h += ''; + h += ''; + if (a.workspace || !isRoot) h += ''; + if (a.id !== 'main') h += ''; + h += '
'; + return h; + } let html = ''; - // Main agent tree - if (mainAgent) { + // Render each root with its children + roots.forEach(root => { + const children = subsByRoot[root.id] || []; html += '
'; - html += '
'; - html += '
🤖 ' + esc(mainAgent.name || 'main') + ' ' + t('agents.main') + '
'; - html += '
🧠 ' + esc(mainAgent.effectiveModel) + '
'; - if (mainAgent.workspace) html += '
📁 ' + esc(mainAgent.workspace) + '
'; - html += '
'; - html += ''; - html += ''; - html += '
'; - - // Sub-agents as children - if (subs.length) { - html += '
'; - subs.forEach(a => { - html += '
'; - html += '
📱 ' + esc(a.name || a.id); - if (a.groupId) html += ' ' + esc(a.groupId) + ''; - html += '
'; - html += '
🧠 ' + esc(a.effectiveModel) + '
'; - if (a.workspace) html += '
📁 ' + esc(a.workspace) + '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += ''; - html += '
'; - }); - html += '
'; + html += agentCard(root, true); + if (children.length) { + const treeId = 'tree-' + root.id; + html += '
'; + html += ''; + html += '
'; + children.forEach(c => { html += agentCard(c, false); }); + html += '
'; } html += '
'; - } else if (subs.length) { - // No main agent but has subs (edge case) - subs.forEach(a => { - html += '
'; - html += '
📱 ' + esc(a.name || a.id); - if (a.groupId) html += ' ' + esc(a.groupId) + ''; - html += '
'; - html += '
🧠 ' + esc(a.effectiveModel) + '
'; - if (a.workspace) html += '
📁 ' + esc(a.workspace) + '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += ''; - html += '
'; - }); - } + }); + + // Orphan subs (no matching root — edge case) + orphanSubs.forEach(a => { + html += '
'; + html += agentCard(a, false); + html += '
'; + }); el.innerHTML = html; + + // Event delegation for agent tree buttons + el.onclick = function(ev) { + const btn = ev.target.closest('[data-action]'); + if (!btn) return; + const action = btn.dataset.action; + const id = btn.dataset.id; + if (action === 'saveModel') saveAgentModel(id); + else if (action === 'viewWs') viewWorkspace(id); + else if (action === 'delAgent') deleteAgent(id, btn.dataset.name); + }; +} + +function toggleTree(treeId, btn) { + const el = document.getElementById(treeId); + if (!el) return; + el.classList.toggle('collapsed'); + btn.textContent = el.classList.contains('collapsed') ? '+' : '−'; } function buildModelOpts(selected){