mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
feat(execution): add ExecutionBridge for parallel task execution
Implements the Execution Optimizer Rework plan that enables the system to actually use the optimizer metadata (parallelGroups, agentType, recommendedModel, requiresFreshContext, estimatedTokens) that was previously generated but ignored. New components: - ExecutionBridge: Central coordinator that loads optimized plans, manages parallel execution within groups, and coordinates with SpawnOrchestrator for session-based execution - ModelSelector: Routes tasks to appropriate models (opus/sonnet/haiku) based on user defaults and agent type overrides. Optimizer recommendations are advisory only - user preferences always win - GroupScheduler: Builds topologically ordered execution groups, manages dependencies, determines execution mode (session vs task-tool) - ContextManager: Handles fresh context requirements via /clear+/init or new session spawning Features: - Parallel task execution within groups (configurable limit) - Group-level dependency tracking (lower groups complete first) - Partial failure handling (continue with non-dependent tasks) - Model configuration in App Settings > Models tab - Agent type overrides (explore, implement, test, review) - Execution control API endpoints (start, pause, resume, cancel) - SSE events for real-time execution progress visibility - Execution history tracking API endpoints: - GET/POST/PUT /api/execution/* for execution control - GET/PUT /api/execution/model-config for model settings Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6193,6 +6193,7 @@ class ClaudemanApp {
|
||||
document.getElementById('modalAutoCompactPrompt').value = session.autoCompactPrompt ?? '';
|
||||
document.getElementById('modalAutoClearEnabled').checked = session.autoClearEnabled ?? false;
|
||||
document.getElementById('modalAutoClearThreshold').value = session.autoClearThreshold ?? 140000;
|
||||
document.getElementById('modalImageWatcherEnabled').checked = session.imageWatcherEnabled ?? true;
|
||||
|
||||
// Populate Ralph Wiggum form with current session values
|
||||
const ralphState = this.ralphStates.get(sessionId);
|
||||
@@ -6234,6 +6235,26 @@ class ClaudemanApp {
|
||||
} catch { /* silent */ }
|
||||
}
|
||||
|
||||
async toggleSessionImageWatcher() {
|
||||
if (!this.editingSessionId) return;
|
||||
const enabled = document.getElementById('modalImageWatcherEnabled').checked;
|
||||
try {
|
||||
await fetch(`/api/sessions/${this.editingSessionId}/image-watcher`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled })
|
||||
});
|
||||
// Update local session state
|
||||
const session = this.sessions.get(this.editingSessionId);
|
||||
if (session) {
|
||||
session.imageWatcherEnabled = enabled;
|
||||
}
|
||||
this.showToast(`Image watcher ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
||||
} catch (err) {
|
||||
this.showToast('Failed to toggle image watcher', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async autoSaveRespawnConfig() {
|
||||
if (!this.editingSessionId) return;
|
||||
const config = {
|
||||
@@ -6834,6 +6855,7 @@ class ClaudemanApp {
|
||||
document.getElementById('appSettingsShowSubagents').checked = settings.showSubagents ?? true;
|
||||
document.getElementById('appSettingsSubagentTracking').checked = settings.subagentTrackingEnabled ?? true;
|
||||
document.getElementById('appSettingsSubagentActiveTabOnly').checked = settings.subagentActiveTabOnly ?? false;
|
||||
document.getElementById('appSettingsImageWatcherEnabled').checked = settings.imageWatcherEnabled ?? true;
|
||||
// Claude CLI settings
|
||||
const claudeModeSelect = document.getElementById('appSettingsClaudeMode');
|
||||
const allowedToolsRow = document.getElementById('allowedToolsRow');
|
||||
@@ -6848,6 +6870,8 @@ class ClaudemanApp {
|
||||
const niceSettings = settings.nice || {};
|
||||
document.getElementById('appSettingsNiceEnabled').checked = niceSettings.enabled ?? false;
|
||||
document.getElementById('appSettingsNiceValue').value = niceSettings.niceValue ?? 10;
|
||||
// Model configuration (loaded from server)
|
||||
this.loadModelConfigForSettings();
|
||||
// Notification settings
|
||||
const notifPrefs = this.notificationManager?.preferences || {};
|
||||
document.getElementById('appSettingsNotifEnabled').checked = notifPrefs.enabled ?? true;
|
||||
@@ -6906,6 +6930,7 @@ class ClaudemanApp {
|
||||
showSubagents: document.getElementById('appSettingsShowSubagents').checked,
|
||||
subagentTrackingEnabled: document.getElementById('appSettingsSubagentTracking').checked,
|
||||
subagentActiveTabOnly: document.getElementById('appSettingsSubagentActiveTabOnly').checked,
|
||||
imageWatcherEnabled: document.getElementById('appSettingsImageWatcherEnabled').checked,
|
||||
// Claude CLI settings
|
||||
claudeMode: document.getElementById('appSettingsClaudeMode').value,
|
||||
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
|
||||
@@ -6947,6 +6972,10 @@ class ClaudemanApp {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...settings, notificationPreferences: notifPrefsToSave })
|
||||
});
|
||||
|
||||
// Save model configuration separately
|
||||
await this.saveModelConfigFromSettings();
|
||||
|
||||
this.showToast('Settings saved', 'success');
|
||||
} catch (err) {
|
||||
// Server save failed but localStorage succeeded
|
||||
@@ -6956,6 +6985,71 @@ class ClaudemanApp {
|
||||
this.closeAppSettings();
|
||||
}
|
||||
|
||||
// Load model configuration from server for the settings modal
|
||||
async loadModelConfigForSettings() {
|
||||
try {
|
||||
const res = await fetch('/api/execution/model-config');
|
||||
const data = await res.json();
|
||||
if (data.success && data.data) {
|
||||
const config = data.data;
|
||||
// Default model
|
||||
const defaultModelEl = document.getElementById('appSettingsDefaultModel');
|
||||
if (defaultModelEl) {
|
||||
defaultModelEl.value = config.defaultModel || 'sonnet';
|
||||
}
|
||||
// Show recommendations
|
||||
const showRecsEl = document.getElementById('appSettingsShowModelRecommendations');
|
||||
if (showRecsEl) {
|
||||
showRecsEl.checked = config.showRecommendations ?? true;
|
||||
}
|
||||
// Agent type overrides
|
||||
const overrides = config.agentTypeOverrides || {};
|
||||
const exploreEl = document.getElementById('appSettingsModelExplore');
|
||||
const implementEl = document.getElementById('appSettingsModelImplement');
|
||||
const testEl = document.getElementById('appSettingsModelTest');
|
||||
const reviewEl = document.getElementById('appSettingsModelReview');
|
||||
if (exploreEl) exploreEl.value = overrides.explore || '';
|
||||
if (implementEl) implementEl.value = overrides.implement || '';
|
||||
if (testEl) testEl.value = overrides.test || '';
|
||||
if (reviewEl) reviewEl.value = overrides.review || '';
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to load model config:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Save model configuration from settings modal to server
|
||||
async saveModelConfigFromSettings() {
|
||||
const defaultModelEl = document.getElementById('appSettingsDefaultModel');
|
||||
const showRecsEl = document.getElementById('appSettingsShowModelRecommendations');
|
||||
const exploreEl = document.getElementById('appSettingsModelExplore');
|
||||
const implementEl = document.getElementById('appSettingsModelImplement');
|
||||
const testEl = document.getElementById('appSettingsModelTest');
|
||||
const reviewEl = document.getElementById('appSettingsModelReview');
|
||||
|
||||
const agentTypeOverrides = {};
|
||||
if (exploreEl?.value) agentTypeOverrides.explore = exploreEl.value;
|
||||
if (implementEl?.value) agentTypeOverrides.implement = implementEl.value;
|
||||
if (testEl?.value) agentTypeOverrides.test = testEl.value;
|
||||
if (reviewEl?.value) agentTypeOverrides.review = reviewEl.value;
|
||||
|
||||
const config = {
|
||||
defaultModel: defaultModelEl?.value || 'sonnet',
|
||||
showRecommendations: showRecsEl?.checked ?? true,
|
||||
agentTypeOverrides,
|
||||
};
|
||||
|
||||
try {
|
||||
await fetch('/api/execution/model-config', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Failed to save model config:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the global Ralph tracker enabled setting
|
||||
isRalphTrackerEnabledByDefault() {
|
||||
const settings = this.loadAppSettingsFromStorage();
|
||||
@@ -7067,6 +7161,40 @@ class ClaudemanApp {
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(settings));
|
||||
}
|
||||
|
||||
async clearAllSubagents() {
|
||||
const count = this.subagents.size;
|
||||
if (count === 0) {
|
||||
this.showToast('No subagents to clear', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Clear all ${count} tracked subagent(s)? This removes them from the UI but does not affect running processes.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/subagents', { method: 'DELETE' });
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
// Clear local state
|
||||
this.subagents.clear();
|
||||
this.subagentActivity.clear();
|
||||
this.subagentToolResults.clear();
|
||||
// Close any open subagent windows
|
||||
this.cleanupAllFloatingWindows();
|
||||
// Update UI
|
||||
this.renderSubagentPanel();
|
||||
this.renderMonitorSubagents();
|
||||
this.updateSubagentBadge();
|
||||
this.showToast(`Cleared ${data.data.cleared} subagent(s)`, 'success');
|
||||
} else {
|
||||
this.showToast('Failed to clear subagents: ' + data.error, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
this.showToast('Failed to clear subagents', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
toggleSubagentsPanel() {
|
||||
const panel = document.getElementById('subagentsPanel');
|
||||
const toggleBtn = document.getElementById('subagentsToggleBtn');
|
||||
@@ -8713,6 +8841,9 @@ class ClaudemanApp {
|
||||
// Always update badge count
|
||||
this.updateSubagentBadge();
|
||||
|
||||
// Always update monitor panel (even if subagent panel is hidden)
|
||||
this.renderMonitorSubagents();
|
||||
|
||||
// If panel is not visible, don't render content
|
||||
if (!this.subagentPanelVisible) {
|
||||
return;
|
||||
@@ -11002,6 +11133,49 @@ class ClaudemanApp {
|
||||
body.innerHTML = html;
|
||||
}
|
||||
|
||||
renderMonitorSubagents() {
|
||||
const body = document.getElementById('monitorSubagentsBody');
|
||||
const stats = document.getElementById('monitorSubagentStats');
|
||||
if (!body) return;
|
||||
|
||||
const subagents = Array.from(this.subagents.values());
|
||||
const activeCount = subagents.filter(s => s.status === 'active' || s.status === 'idle').length;
|
||||
|
||||
if (stats) {
|
||||
stats.textContent = `${subagents.length} tracked` + (activeCount > 0 ? `, ${activeCount} active` : '');
|
||||
}
|
||||
|
||||
if (subagents.length === 0) {
|
||||
body.innerHTML = '<div class="monitor-empty">No background agents</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
for (const agent of subagents) {
|
||||
const statusClass = agent.status === 'active' ? 'active' : agent.status === 'idle' ? 'idle' : 'completed';
|
||||
const modelBadge = agent.modelShort ? `<span class="model-badge ${agent.modelShort}">${agent.modelShort}</span>` : '';
|
||||
const desc = agent.description ? this.escapeHtml(agent.description.substring(0, 40)) : agent.agentId;
|
||||
|
||||
html += `
|
||||
<div class="process-item">
|
||||
<span class="process-mode ${statusClass}">${agent.status}</span>
|
||||
<div class="process-info">
|
||||
<div class="process-name">${modelBadge} ${desc}</div>
|
||||
<div class="process-meta">
|
||||
<span>ID: ${agent.agentId}</span>
|
||||
<span>${agent.toolCallCount || 0} tools</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="process-actions">
|
||||
${agent.status !== 'completed' ? `<button class="btn-toolbar btn-sm btn-danger" onclick="app.killSubagent('${agent.agentId}')" title="Kill agent">Kill</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
body.innerHTML = html;
|
||||
}
|
||||
|
||||
async killScreen(sessionId) {
|
||||
if (!confirm('Kill this screen session?')) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user