refactor: split tool declarations into domain files + role config UI
tools/__init__.py shrinks from 1,137 → 250 lines. Each domain file now owns both its callables and its FunctionDeclarations (DECLARATIONS list), so adding a new tool only touches one file. New TOOL_CATEGORIES dict exported from __init__ — used by the UI for grouped tool checkboxes. Role config UI (Settings → Model Registry → Role Assignments): - ⚙ button per role expands an inline configure panel - Textarea for system_append (injected into system prompt for this role) - Grouped checkboxes for tool allow-list (all checked = no restriction) - POST /api/models/role-config saves both fields; updates ROLE_CONFIG_DATA in-page so re-open reflects current state without a page reload Backend: - model_registry.set_role_config() writes system_append + tools to registry - TOOL_CATEGORIES exported from tools/__init__ for UI rendering - TOOLS.md header updated: 30 → 39 tools (ae_journal_* and cortex_* additions) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Tool Reference
|
||||
|
||||
> This reference covers all 30 orchestrator tools available when the ⚡ toggle is on.
|
||||
> This reference covers all 39 orchestrator tools available when the ⚡ toggle is on.
|
||||
> Tools are invoked automatically by the orchestrator — you don't call them directly.
|
||||
|
||||
¹ **Admin only** — requires the `admin` role. Invisible to regular users.
|
||||
|
||||
@@ -223,7 +223,6 @@
|
||||
display: flex; align-items: flex-start; gap: 1rem;
|
||||
padding: 0.6rem 0; border-bottom: 1px solid var(--pg-border-deep);
|
||||
}
|
||||
.role-row:last-child { border-bottom: none; }
|
||||
.role-name { font-size: 0.82rem; font-weight: 600; color: #a78bfa; min-width: 6rem; padding-top: 0.45rem; }
|
||||
.role-slots { display: flex; flex-wrap: wrap; gap: 0.5rem; flex: 1; }
|
||||
.role-slot { display: flex; flex-direction: column; gap: 0.2rem; flex: 1; min-width: 8rem; }
|
||||
@@ -238,6 +237,36 @@
|
||||
.role-select.saved { border-color: #166534; }
|
||||
.role-select.saving { border-color: #92400e; }
|
||||
.role-select.err { border-color: #7f1d1d; }
|
||||
.role-cfg-btn {
|
||||
flex-shrink: 0; padding: 0.3rem 0.55rem; font-size: 0.8rem;
|
||||
background: none; border: 1px solid var(--pg-border); border-radius: 6px;
|
||||
color: var(--pg-dim); cursor: pointer; transition: color 0.15s, border-color 0.15s;
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
.role-cfg-btn:hover { color: #a78bfa; border-color: #a78bfa; }
|
||||
.role-cfg-btn.active { color: #a78bfa; border-color: #a78bfa; background: rgba(167,139,250,0.08); }
|
||||
/* Role config panel */
|
||||
.role-config-panel {
|
||||
display: none; margin: 0 0 0.75rem 7rem;
|
||||
border: 1px solid var(--pg-border); border-radius: 8px;
|
||||
background: var(--pg-surface); padding: 1rem;
|
||||
}
|
||||
.role-config-panel.open { display: block; }
|
||||
.rcp-field { margin-bottom: 0.75rem; }
|
||||
.rcp-label { display: block; font-size: 0.75rem; font-weight: 600; color: var(--pg-muted); margin-bottom: 0.35rem; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
.rcp-hint { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--pg-dimmer); }
|
||||
.rcp-textarea {
|
||||
width: 100%; resize: vertical; min-height: 4rem;
|
||||
background: var(--pg-bg); border: 1px solid var(--pg-border); border-radius: 6px;
|
||||
color: var(--pg-text); font-family: inherit; font-size: 0.85rem;
|
||||
padding: 0.5rem 0.6rem; outline: none; transition: border-color 0.15s;
|
||||
}
|
||||
.rcp-textarea:focus { border-color: #7c3aed; }
|
||||
.rcp-tools { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; }
|
||||
.rcp-cat { width: 100%; margin: 0.4rem 0 0.1rem; font-size: 0.7rem; font-weight: 600; color: var(--pg-dimmer); text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.rcp-check { display: flex; align-items: center; gap: 0.35rem; font-size: 0.8rem; color: var(--pg-bright); cursor: pointer; }
|
||||
.rcp-check input { accent-color: #a78bfa; cursor: pointer; }
|
||||
.rcp-actions { display: flex; gap: 0.5rem; padding-top: 0.25rem; }
|
||||
|
||||
/* Model select picker */
|
||||
#model-select-wrap { display: none; margin-bottom: 0.75rem; }
|
||||
@@ -496,6 +525,8 @@
|
||||
<script>
|
||||
// ── Injected data ─────────────────────────────────────────────────────────
|
||||
const ROLE_DATA = {{ role_data_js }};
|
||||
const ROLE_CONFIG_DATA = {{ role_config_data_js }};
|
||||
const TOOL_CATEGORIES = {{ tool_categories_js }};
|
||||
const GOOGLE_ACCOUNTS = {{ google_accounts_js }};
|
||||
const GOOGLE_CATALOG = {{ google_catalog_js }};
|
||||
const ANTHROPIC_CATALOG = {{ anthropic_catalog_js }};
|
||||
@@ -543,6 +574,112 @@
|
||||
});
|
||||
});
|
||||
|
||||
// ── Role config panels ────────────────────────────────────────────────────
|
||||
|
||||
// All tool names in category order (for checkbox rendering)
|
||||
const ALL_TOOLS_ORDERED = Object.entries(TOOL_CATEGORIES).flatMap(([,tools]) => tools);
|
||||
|
||||
function buildToolChecklist(role, savedTools) {
|
||||
// savedTools: null = all, array = explicit allow-list
|
||||
const wrap = document.getElementById(`rcp-tools-${role}`);
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = '';
|
||||
for (const [cat, tools] of Object.entries(TOOL_CATEGORIES)) {
|
||||
const catEl = document.createElement('div');
|
||||
catEl.className = 'rcp-cat';
|
||||
catEl.style.width = '100%';
|
||||
catEl.textContent = cat;
|
||||
wrap.appendChild(catEl);
|
||||
for (const tool of tools) {
|
||||
const label = document.createElement('label');
|
||||
label.className = 'rcp-check';
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.value = tool;
|
||||
cb.checked = savedTools === null || savedTools.includes(tool);
|
||||
label.appendChild(cb);
|
||||
label.appendChild(document.createTextNode(tool));
|
||||
wrap.appendChild(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openRolePanel(role) {
|
||||
const panel = document.getElementById(`rcp-${role}`);
|
||||
const btn = document.querySelector(`.role-cfg-btn[data-role="${role}"]`);
|
||||
const cfg = ROLE_CONFIG_DATA[role] || {};
|
||||
if (!panel) return;
|
||||
// Populate textarea
|
||||
panel.querySelector('.rcp-textarea').value = cfg.system_append || '';
|
||||
// Build tool checklist
|
||||
buildToolChecklist(role, cfg.tools || null);
|
||||
panel.classList.add('open');
|
||||
btn && btn.classList.add('active');
|
||||
}
|
||||
|
||||
function closeRolePanel(role) {
|
||||
const panel = document.getElementById(`rcp-${role}`);
|
||||
const btn = document.querySelector(`.role-cfg-btn[data-role="${role}"]`);
|
||||
panel && panel.classList.remove('open');
|
||||
btn && btn.classList.remove('active');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.role-cfg-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const role = btn.dataset.role;
|
||||
const panel = document.getElementById(`rcp-${role}`);
|
||||
if (panel.classList.contains('open')) {
|
||||
closeRolePanel(role);
|
||||
} else {
|
||||
// Close any other open panels first
|
||||
document.querySelectorAll('.role-config-panel.open').forEach(p => {
|
||||
closeRolePanel(p.id.replace('rcp-', ''));
|
||||
});
|
||||
openRolePanel(role);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.rcp-cancel').forEach(btn => {
|
||||
btn.addEventListener('click', () => closeRolePanel(btn.dataset.role));
|
||||
});
|
||||
|
||||
document.querySelectorAll('.rcp-save').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const role = btn.dataset.role;
|
||||
const panel = document.getElementById(`rcp-${role}`);
|
||||
const ta = panel.querySelector('.rcp-textarea');
|
||||
const checks = [...panel.querySelectorAll('.rcp-check input[type=checkbox]')];
|
||||
const allChecked = checks.every(c => c.checked);
|
||||
const someChecked = checks.some(c => c.checked);
|
||||
const tools = allChecked ? null : (someChecked ? checks.filter(c => c.checked).map(c => c.value) : []);
|
||||
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/api/models/role-config', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({role, system_append: ta.value, tools}),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.ok) {
|
||||
// Update local state so re-open shows current values
|
||||
if (!ROLE_CONFIG_DATA[role]) ROLE_CONFIG_DATA[role] = {};
|
||||
ROLE_CONFIG_DATA[role].system_append = ta.value;
|
||||
ROLE_CONFIG_DATA[role].tools = tools;
|
||||
showToast(`${role} config saved`);
|
||||
closeRolePanel(role);
|
||||
} else {
|
||||
showToast(data.error || 'Save failed', true);
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(e.message, true);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Provider tabs ─────────────────────────────────────────────────────────
|
||||
const providerVal = document.getElementById('add-provider-val');
|
||||
const pfields = {
|
||||
|
||||
Reference in New Issue
Block a user