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:
Scott Idem
2026-05-01 20:40:50 -04:00
parent 49123cdd5c
commit eab92d876d
15 changed files with 993 additions and 974 deletions

View File

@@ -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.

View File

@@ -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 = {