function agentUiStatus(status) { if (status === 'published') return 'active'; if (status === 'archived') return 'inactive'; return status || 'draft'; } function mapAgent(agent) { return { id: agent.id, slug: agent.slug, display_name: agent.display_name, description: agent.description || '', status: agentUiStatus(agent.status), raw_status: agent.status, operation_count: agent.operation_count || 0, operation_ids: agent.operation_ids || [], key_count: agent.key_count || 0, calls_today: agent.calls_today || 0, created_at: agent.created_at, current_draft_version: agent.current_draft_version || 1, latest_published_version: agent.latest_published_version, mcp_endpoint: agent.mcp_endpoint || '', }; } function mapOperation(operation) { return { id: operation.id, name: operation.name, display_name: operation.display_name, protocol: operation.protocol, status: operation.status, current_draft_version: operation.current_draft_version || 1, latest_published_version: operation.latest_published_version, }; } document.addEventListener('alpine:init', function() { Alpine.data('agents', function() { return { agents: [], operations: [], loading: true, loadError: '', workspaceId: null, agentSearch: '', openDropdown: null, drawerOpen: false, drawerMode: 'create', editingId: null, saving: false, form: { display_name: '', slug: '', description: '', status: 'active', selectedOps: [], }, opSearch: '', slugManuallyEdited: false, async init() { var self = this; await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve()); var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; this.workspaceId = workspace ? workspace.id : null; await this.reload(); document.addEventListener('keydown', function(event) { if (event.key === 'Escape' && self.drawerOpen) { self.closeDrawer(); } }); document.addEventListener('click', function() { self.openDropdown = null; }); window.addEventListener('crank:workspacechange', async function(event) { self.workspaceId = event.detail ? event.detail.id : null; await self.reload(); }); }, async reload() { this.loading = true; this.loadError = ''; if (!this.workspaceId || !window.CrankApi) { this.agents = []; this.operations = []; this.loading = false; this.loadError = 'Workspace or API is unavailable'; return; } try { var responses = await Promise.all([ window.CrankApi.listAgents(this.workspaceId), window.CrankApi.listOperations(this.workspaceId), ]); this.agents = ((responses[0] && responses[0].items) || []).map(mapAgent); this.operations = ((responses[1] && responses[1].items) || []).map(mapOperation); } catch (error) { this.agents = []; this.operations = []; this.loadError = error.message || 'Failed to load agents'; } this.loading = false; }, get filteredAgents() { var query = this.agentSearch.toLowerCase().trim(); if (!query) return this.agents; return this.agents.filter(function(agent) { return agent.display_name.toLowerCase().includes(query) || agent.slug.toLowerCase().includes(query) || (agent.description || '').toLowerCase().includes(query); }); }, get totalCalls() { return this.agents.reduce(function(sum, agent) { return sum + (agent.calls_today || 0); }, 0); }, get activeCount() { return this.agents.filter(function(agent) { return agent.status === 'active'; }).length; }, get totalOpsExposed() { return this.agents.reduce(function(sum, agent) { return sum + (agent.operation_count || 0); }, 0); }, get filteredOps() { var query = this.opSearch.toLowerCase().trim(); if (!query) return this.operations; return this.operations.filter(function(operation) { return operation.name.toLowerCase().includes(query) || operation.display_name.toLowerCase().includes(query); }); }, openCreate() { this.drawerMode = 'create'; this.editingId = null; this.form = { display_name: '', slug: '', description: '', status: 'active', selectedOps: [], }; this.opSearch = ''; this.slugManuallyEdited = false; this.drawerOpen = true; }, openEdit(agent) { this.drawerMode = 'edit'; this.editingId = agent.id; this.form = { display_name: agent.display_name, slug: agent.slug, description: agent.description, status: agent.status === 'active' ? 'active' : 'draft', selectedOps: [].concat(agent.operation_ids || []), }; this.opSearch = ''; this.slugManuallyEdited = true; this.drawerOpen = true; }, closeDrawer() { this.drawerOpen = false; this.saving = false; }, onNameInput(value) { this.form.display_name = value; if (!this.slugManuallyEdited) { this.form.slug = value .toLowerCase() .replace(/\s+/g, '-') .replace(/[^a-z0-9-]/g, ''); } }, onSlugInput(value) { this.form.slug = value.toLowerCase().replace(/[^a-z0-9-]/g, ''); this.slugManuallyEdited = true; }, toggleOp(operationId) { var index = this.form.selectedOps.indexOf(operationId); if (index === -1) { this.form.selectedOps.push(operationId); } else { this.form.selectedOps.splice(index, 1); } }, isOpSelected(operationId) { return this.form.selectedOps.includes(operationId); }, async saveAgent() { var self = this; if ( this.saving || !this.workspaceId || !this.form.display_name.trim() || !this.form.slug.trim() ) { return; } this.saving = true; try { var agentId = this.editingId; var currentVersion = 1; if (this.drawerMode === 'create') { var created = await window.CrankApi.createAgent(this.workspaceId, { slug: this.form.slug, display_name: this.form.display_name, description: this.form.description, instructions: {}, tool_selection_policy: {}, }); agentId = created.agent_id; currentVersion = created.version || 1; } else { await window.CrankApi.updateAgent(this.workspaceId, this.editingId, { slug: this.form.slug, display_name: this.form.display_name, description: this.form.description, }); var agent = await window.CrankApi.getAgent(this.workspaceId, this.editingId); currentVersion = agent.current_draft_version || 1; } await window.CrankApi.saveAgentBindings( this.workspaceId, agentId, this.form.selectedOps.map(function(operationId) { var operation = self.operations.find(function(item) { return item.id === operationId; }); return { operation_id: operationId, operation_version: operation && operation.latest_published_version ? operation.latest_published_version : operation && operation.current_draft_version ? operation.current_draft_version : 1, tool_name: operation ? operation.name : operationId, tool_title: operation ? (operation.display_name || operation.name) : operationId, tool_description_override: null, enabled: true, }; }), ); if (this.form.status === 'active') { await window.CrankApi.publishAgent(this.workspaceId, agentId, { version: currentVersion, }); } await this.reload(); this.closeDrawer(); } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to save agent', 'Agent update failed'); } this.saving = false; } }, async deleteAgent(id) { if (!confirm('Delete this agent? This cannot be undone.')) return; try { await window.CrankApi.deleteAgent(this.workspaceId, id); await this.reload(); } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to delete agent', 'Delete failed'); } } }, mcpEndpoint(agent) { if (agent.mcp_endpoint) return agent.mcp_endpoint; var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; var workspaceSlug = workspace ? workspace.slug : 'default'; return '/mcp/v1/' + workspaceSlug + '/' + agent.slug; }, copyEndpoint(agent) { navigator.clipboard.writeText(this.mcpEndpoint(agent)).catch(function() {}); }, statusClass(status) { return 'agent-status-badge agent-status-' + status; }, protocolBadge(operation) { if (operation.protocol === 'rest') return 'badge badge-rest'; if (operation.protocol === 'graphql') return 'badge badge-graphql'; return 'badge badge-grpc'; }, protocolLabel(operation) { if (operation.protocol === 'rest') return 'REST'; if (operation.protocol === 'graphql') return 'GQL'; return 'gRPC'; }, formatDate(dateStr) { if (!dateStr) return '—'; return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', }); }, formatCalls(value) { if (!value) return '0'; return value >= 1000 ? (value / 1000).toFixed(1) + 'k' : String(value); }, }; }); });