Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions src/dashboard/frontend/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ export function initElements(): DOMElements {
threadSendBtn: document.getElementById('thread-send-btn') as HTMLButtonElement,
mentionAutocomplete: document.getElementById('mention-autocomplete')!,
mentionAutocompleteList: document.getElementById('mention-autocomplete-list')!,
// Spawn modal elements
spawnAgentBtn: document.getElementById('spawn-agent-btn') as HTMLButtonElement,
spawnModalOverlay: document.getElementById('spawn-modal-overlay')!,
spawnModalClose: document.getElementById('spawn-modal-close') as HTMLButtonElement,
spawnAgentName: document.getElementById('spawn-agent-name') as HTMLInputElement,
spawnAgentCli: document.getElementById('spawn-agent-cli') as HTMLSelectElement,
spawnAgentModel: document.getElementById('spawn-agent-model') as HTMLInputElement,
spawnAgentTask: document.getElementById('spawn-agent-task') as HTMLTextAreaElement,
spawnModalCancel: document.getElementById('spawn-modal-cancel') as HTMLButtonElement,
spawnModalSubmit: document.getElementById('spawn-modal-submit') as HTMLButtonElement,
};
return elements;
}
Expand Down Expand Up @@ -800,3 +810,94 @@ export function getCurrentMentionQuery(): string | null {

return null;
}

// Track spawned agents
let spawnedAgents: string[] = [];

/**
* Open the spawn agent modal
*/
export function openSpawnModal(): void {
elements.spawnModalOverlay.classList.add('visible');
elements.spawnAgentName.value = '';
elements.spawnAgentCli.value = 'claude';
elements.spawnAgentModel.value = '';
elements.spawnAgentTask.value = '';
elements.spawnAgentName.focus();
}

/**
* Close the spawn agent modal
*/
export function closeSpawnModal(): void {
elements.spawnModalOverlay.classList.remove('visible');
}

/**
* Spawn a new agent via the API
*/
export async function spawnAgent(): Promise<{ success: boolean; error?: string }> {
const name = elements.spawnAgentName.value.trim();
const cli = elements.spawnAgentCli.value || 'claude';
const model = elements.spawnAgentModel.value.trim();
const task = elements.spawnAgentTask.value.trim();

if (!name) {
return { success: false, error: 'Agent name is required' };
}

elements.spawnModalSubmit.disabled = true;

try {
const response = await fetch('/api/spawn', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, cli, model, task }),
});

const result = await response.json();

if (response.ok && result.success) {
// Refresh spawned agents list
await fetchSpawnedAgents();

// Close modal after brief delay
setTimeout(() => {
closeSpawnModal();
}, 500);

return { success: true };
} else {
throw new Error(result.error || 'Failed to spawn agent');
}
} catch (err: any) {
return { success: false, error: err.message };
} finally {
elements.spawnModalSubmit.disabled = false;
}
}

/**
* Fetch list of spawned agents from API
*/
export async function fetchSpawnedAgents(): Promise<void> {
try {
const response = await fetch('/api/spawned');
const result = await response.json();

if (result.success && Array.isArray(result.agents)) {
spawnedAgents = result.agents.map((a: any) => a.name);
// Re-render agents to show spawned status
renderAgents();
}
} catch (err) {
console.error('[UI] Failed to fetch spawned agents:', err);
}
}

/**
* Check if an agent is spawned
*/
export function isSpawnedAgent(name: string): boolean {
return spawnedAgents.includes(name);
}
13 changes: 7 additions & 6 deletions src/dashboard/frontend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,15 @@ export interface DOMElements {
mentionAutocomplete: HTMLElement;
mentionAutocompleteList: HTMLElement;
// Spawn modal elements
spawnBtn: HTMLButtonElement;
spawnAgentBtn: HTMLButtonElement;
spawnModalOverlay: HTMLElement;
spawnModalClose: HTMLButtonElement;
spawnNameInput: HTMLInputElement;
spawnCliInput: HTMLInputElement;
spawnTaskInput: HTMLTextAreaElement;
spawnSubmitBtn: HTMLButtonElement;
spawnStatus: HTMLElement;
spawnAgentName: HTMLInputElement;
spawnAgentCli: HTMLSelectElement;
spawnAgentModel: HTMLInputElement;
spawnAgentTask: HTMLTextAreaElement;
spawnModalCancel: HTMLButtonElement;
spawnModalSubmit: HTMLButtonElement;
}

export interface SpawnedAgent {
Expand Down
12 changes: 11 additions & 1 deletion src/dashboard/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,17 @@ interface AgentSummary {
context?: string;
}

export async function startDashboard(port: number, dataDir: string, teamDir: string, dbPath?: string): Promise<number> {
export interface DashboardOptions {
port: number;
dataDir: string;
teamDir: string;
dbPath?: string;
enableSpawner?: boolean;
projectRoot?: string;
}

export async function startDashboard(options: DashboardOptions): Promise<number> {
const { port, dataDir, teamDir, dbPath, enableSpawner, projectRoot } = options;
console.log('Starting dashboard...');
console.log('__dirname:', __dirname);
const publicDir = path.join(__dirname, 'public');
Expand Down
7 changes: 6 additions & 1 deletion src/dashboard/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ console.log(`Starting dashboard for project: ${paths.projectRoot}`);
console.log(`Data dir: ${paths.dataDir}`);
console.log(`Database: ${paths.dbPath}`);

startDashboard(port, paths.dataDir, paths.teamDir, paths.dbPath).catch(console.error);
startDashboard({
port,
dataDir: paths.dataDir,
teamDir: paths.teamDir,
dbPath: paths.dbPath,
}).catch(console.error);