function agentUiStatus(status) { if (status === 'published') return 'published'; if (status === 'archived') return 'archived'; return status || 'draft'; } function mapAgent(agent) { var mapped = { 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 || [], tool_selection_policy: agent.tool_selection_policy || { mode: 'direct', groups: [], search: { max_results: 8 } }, 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, catalog_revision: typeof agent.catalog_revision === 'number' ? agent.catalog_revision : 0, mcp_endpoint: agent.mcp_endpoint || '', }; return window.localizeDemoAgent ? window.localizeDemoAgent(mapped) : mapped; } function mapOperation(operation) { var mapped = { 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, }; return window.localizeDemoOperation ? window.localizeDemoOperation(mapped) : mapped; } function currentLocale() { return localStorage.getItem('crank_lang') === 'ru' ? 'ru-RU' : 'en-US'; } document.addEventListener('alpine:init', function() { Alpine.data('agents', function() { return { agents: [], operations: [], capabilities: null, loading: true, loadError: '', workspaceId: null, agentSearch: '', openDropdown: null, drawerOpen: false, drawerMode: 'create', editingId: null, saving: false, lifecycleBusyId: null, form: { display_name: '', slug: '', description: '', status: 'published', selectedOps: [], accessMode: 'direct', groups: [], searchMaxResults: 8, }, opSearch: '', slugManuallyEdited: false, searchPreviewQuery: '', searchPreviewGroup: '', searchPreviewItems: [], searchPreviewLoading: false, searchPreviewRan: false, _loadGeneration: 0, _mutationGeneration: 0, _queryHydrated: 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.loadCapabilities(); await this.reload(); this.hydrateOnboardingQuery(); 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._loadGeneration += 1; self._mutationGeneration += 1; self._queryHydrated = false; self.workspaceId = event.detail ? event.detail.id : null; await self.loadCapabilities(); await self.reload(); }); }, async loadCapabilities() { if (!window.CrankApi || typeof window.CrankApi.getCapabilities !== 'function') { this.capabilities = null; return; } try { this.capabilities = await window.CrankApi.getCapabilities(); } catch (_error) { this.capabilities = null; } }, async reload() { var generation = ++this._loadGeneration; var workspaceId = this.workspaceId; this.loading = true; this.loadError = ''; if (!this.workspaceId || !window.CrankApi) { this.agents = []; this.operations = []; this.loading = false; this.loadError = this.tKey('agents.error.api'); return; } try { var responses = await Promise.all([ window.CrankApi.listAgents(workspaceId), window.CrankApi.listOperations(workspaceId), ]); if (generation !== this._loadGeneration || workspaceId !== this.workspaceId) return; this.agents = ((responses[0] && responses[0].items) || []).map(mapAgent); this.operations = ((responses[1] && responses[1].items) || []).map(mapOperation); } catch (error) { if (generation !== this._loadGeneration || workspaceId !== this.workspaceId) return; this.agents = []; this.operations = []; this.loadError = error.message || this.tKey('agents.error.load'); } if (generation === this._loadGeneration && workspaceId === this.workspaceId) this.loading = false; }, hydrateOnboardingQuery() { if (this._queryHydrated) return; this._queryHydrated = true; var params = new URLSearchParams(window.location.search); if (params.get('onboarding') !== '1' || params.get('action') !== 'create') return; var operationId = params.get('operationId') || ''; var expectedVersion = Number(params.get('operationVersion') || 0); var operation = this.operations.find(function(item) { return item.id === operationId; }); this.openCreate(); if (operation && operation.latest_published_version && (!expectedVersion || operation.latest_published_version === expectedVersion)) { this.form.selectedOps = [operation.id]; } setTimeout(function() { var input = document.querySelector('.drawer input.form-input'); if (input) input.focus(); }, 0); }, 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 === 'published'; }).length; }, get totalOpsExposed() { return this.agents.reduce(function(sum, agent) { return sum + (agent.operation_count || 0); }, 0); }, get isCommunityBuild() { return !!(this.capabilities && this.capabilities.edition === 'community'); }, agentCalloutText() { if (this.isCommunityBuild) { return this.tfKey('agents.callout.community', { count: this.operations.length }); } return this.tfKey('agents.callout.default', { count: this.operations.length }); }, subtitleText() { return this.tKey(this.isCommunityBuild ? 'agents.subtitle.community' : 'agents.subtitle'); }, emptyStateText() { return this.tKey(this.isCommunityBuild ? 'agents.empty.initial.sub_community' : 'agents.empty.initial.sub'); }, drawerSubText() { if (this.drawerMode === 'edit') { return this.tKey('agents.drawer.edit_sub'); } return this.tKey(this.isCommunityBuild ? 'agents.drawer.new_sub_community' : 'agents.drawer.new_sub'); }, operationsSubText() { return this.tKey(this.isCommunityBuild ? 'agents.drawer.operations_sub_community' : 'agents.drawer.operations_sub'); }, get selectedOperations() { var selected = this.form.selectedOps; return this.operations.filter(function(operation) { return selected.includes(operation.id); }); }, get agentToolFindings() { var findings = []; var selected = this.selectedOperations; if (selected.length > 8 && this.form.accessMode === 'direct') { findings.push(this.tKey('agents.drawer.finding.too_many_tools')); } for (var leftIndex = 0; leftIndex < selected.length; leftIndex += 1) { for (var rightIndex = leftIndex + 1; rightIndex < selected.length; rightIndex += 1) { if (this.operationsLookSimilar(selected[leftIndex], selected[rightIndex])) { findings.push(this.tfKey('agents.drawer.finding.similar_tools', { left: selected[leftIndex].display_name || selected[leftIndex].name, right: selected[rightIndex].display_name || selected[rightIndex].name, })); return findings; } } } return findings; }, 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: 'published', selectedOps: [], accessMode: 'direct', groups: [], searchMaxResults: 8, catalogRevision: 0, currentDraftVersion: 1, latestPublishedVersion: null, }; this.opSearch = ''; this.slugManuallyEdited = false; this.resetSearchPreview(); this.drawerOpen = true; }, openEdit(agent) { var policy = agent.tool_selection_policy || {}; this.drawerMode = 'edit'; this.editingId = agent.id; this.form = { display_name: agent.display_name, slug: agent.slug, description: agent.description, status: agent.raw_status || agent.status || 'draft', selectedOps: [].concat(agent.operation_ids || []), accessMode: policy.mode === 'search' ? 'search' : 'direct', groups: (policy.groups || []).map(function(group) { return { id: group.id || '', name: group.name || '', description: group.description || '', tool_names: [].concat(group.tool_names || []), }; }), searchMaxResults: policy.search && policy.search.max_results ? policy.search.max_results : 8, catalogRevision: agent.catalog_revision || 0, currentDraftVersion: agent.current_draft_version || 1, latestPublishedVersion: agent.latest_published_version, }; this.opSearch = ''; this.slugManuallyEdited = true; this.resetSearchPreview(); this.drawerOpen = true; }, closeDrawer() { this.drawerOpen = false; this.saving = false; this.lifecycleBusyId = null; }, 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); var operation = this.operations.find(function(item) { return item.id === operationId; }); if (operation) { this.form.groups.forEach(function(group) { group.tool_names = group.tool_names.filter(function(name) { return name !== operation.name; }); }); } } this.resetSearchPreview(); }, isOpSelected(operationId) { return this.form.selectedOps.includes(operationId); }, clearSelectedOperations() { this.form.selectedOps = []; this.form.groups.forEach(function(group) { group.tool_names = []; }); this.resetSearchPreview(); }, setAccessMode(mode) { this.form.accessMode = mode === 'search' ? 'search' : 'direct'; this.resetSearchPreview(); }, addToolGroup() { this.form.groups.push({ id: '', name: '', description: '', tool_names: [] }); this.resetSearchPreview(); }, removeToolGroup(index) { this.form.groups.splice(index, 1); this.resetSearchPreview(); }, onToolGroupName(index, value) { var group = this.form.groups[index]; if (!group) return; var previousSlug = this.slugifyGroupName(group.name); group.name = value; if (!group.id || group.id === previousSlug) { group.id = this.slugifyGroupName(value); } this.resetSearchPreview(); }, onToolGroupId(index, value) { var group = this.form.groups[index]; if (!group) return; group.id = this.slugifyGroupName(value); this.resetSearchPreview(); }, slugifyGroupName(value) { return String(value || '') .toLowerCase() .replace(/\s+/g, '-') .replace(/[^a-z0-9-]/g, '') .replace(/-+/g, '-') .replace(/^-|-$/g, ''); }, toggleToolGroup(groupIndex, toolName) { var group = this.form.groups[groupIndex]; if (!group) return; var index = group.tool_names.indexOf(toolName); if (index === -1) group.tool_names.push(toolName); else group.tool_names.splice(index, 1); this.resetSearchPreview(); }, toolInGroup(groupIndex, toolName) { var group = this.form.groups[groupIndex]; return Boolean(group && group.tool_names.includes(toolName)); }, toolSelectionPolicy() { return { mode: this.form.accessMode, groups: this.form.accessMode === 'search' ? this.form.groups.map(function(group) { return { id: group.id.trim(), name: group.name.trim(), description: group.description.trim(), tool_names: [].concat(group.tool_names || []), }; }) : [], search: { max_results: Math.max(1, Math.min(20, Number(this.form.searchMaxResults) || 8)), }, }; }, get catalogConfigValid() { if (this.form.accessMode !== 'search') return true; var ids = []; for (var index = 0; index < this.form.groups.length; index += 1) { var group = this.form.groups[index]; if (!group.id.trim() || !group.name.trim() || !group.description.trim()) return false; if (ids.includes(group.id.trim())) return false; ids.push(group.id.trim()); } return true; }, agentBindings() { var self = this; return 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, }; }); }, resetSearchPreview() { this.searchPreviewItems = []; this.searchPreviewRan = false; }, async previewToolSearch() { if ( this.searchPreviewLoading || !this.workspaceId || !this.searchPreviewQuery.trim() || this.form.accessMode !== 'search' ) return; this.searchPreviewLoading = true; this.searchPreviewRan = false; try { var response = await window.CrankApi.previewAgentToolSearch(this.workspaceId, { query: this.searchPreviewQuery.trim(), group_ids: this.searchPreviewGroup ? [this.searchPreviewGroup] : [], bindings: this.agentBindings(), tool_selection_policy: this.toolSelectionPolicy(), }); this.searchPreviewItems = response.items || []; this.searchPreviewRan = true; } catch (error) { this.searchPreviewItems = []; if (window.CrankUi) { window.CrankUi.error( error.message || this.tKey('agents.drawer.search.preview_error'), this.tKey('agents.drawer.search.preview_error_title') ); } } finally { this.searchPreviewLoading = false; } }, operationsLookSimilar(left, right) { var leftTokens = this.operationTokens(left); var rightTokens = this.operationTokens(right); if (!leftTokens.length || !rightTokens.length) return false; var shared = leftTokens.filter(function(token) { return rightTokens.includes(token); }).length; var smaller = Math.min(leftTokens.length, rightTokens.length); return shared >= 2 && (shared / smaller) >= 0.6; }, operationTokens(operation) { var text = [ operation.name || '', operation.display_name || '', ].join(' ').toLowerCase(); var tokens = []; text.split(/[^a-z0-9а-яё]+/i).forEach(function(token) { if (token.length < 3 || tokens.includes(token)) return; tokens.push(token); }); return tokens; }, async saveAgent() { var self = this; if ( this.saving || !this.workspaceId || !this.form.display_name.trim() || !this.form.slug.trim() ) { return; } this.saving = true; var mutationGeneration = ++this._mutationGeneration; var workspaceId = this.workspaceId; try { var agentId = this.editingId; var currentVersion = 1; var existingAgent = this.drawerMode === 'edit' ? this.agents.find(function(item) { return item.id === agentId; }) : null; var previousStatus = existingAgent ? (existingAgent.raw_status || 'draft') : 'draft'; if (this.drawerMode === 'create') { var created = await window.CrankApi.createAgent(workspaceId, { slug: this.form.slug, display_name: this.form.display_name, description: this.form.description, instructions: {}, tool_selection_policy: this.toolSelectionPolicy(), }); agentId = created.agent_id; currentVersion = created.version || 1; } else { await window.CrankApi.updateAgent(workspaceId, this.editingId, { slug: this.form.slug, display_name: this.form.display_name, description: this.form.description, }); var agent = await window.CrankApi.getAgent(workspaceId, this.editingId); currentVersion = agent.current_draft_version || 1; } var savedVersion = await window.CrankApi.saveAgentBindings( workspaceId, agentId, { bindings: this.agentBindings(), tool_selection_policy: this.toolSelectionPolicy(), }, ); currentVersion = savedVersion.version || currentVersion; if (this.form.status === 'published') { await window.CrankApi.publishAgent(workspaceId, agentId, { version: currentVersion, }); } else if (this.form.status === 'archived') { await window.CrankApi.archiveAgent(workspaceId, agentId); } else if (this.drawerMode === 'edit' && previousStatus !== 'draft') { await window.CrankApi.unpublishAgent(workspaceId, agentId); } if (mutationGeneration !== this._mutationGeneration || workspaceId !== this.workspaceId) return; await this.reload(); if (mutationGeneration !== this._mutationGeneration || workspaceId !== this.workspaceId) return; if (window.CrankUi) { window.CrankUi.success( this.tfKey('agents.toast.saved_message', { name: this.form.display_name, count: this.form.selectedOps.length }), this.drawerMode === 'create' ? this.tKey('agents.toast.saved_title_create') : this.tKey('agents.toast.saved_title_update') ); } this.closeDrawer(); if (window.CrankOnboarding) window.CrankOnboarding.signalRefresh(); } catch (error) { if (mutationGeneration !== this._mutationGeneration || workspaceId !== this.workspaceId) return; if (window.CrankUi) { window.CrankUi.error( this.agentMutationErrorMessage(error, this.tKey('agents.toast.save_error_message')), this.tKey('agents.toast.save_error_title') ); } this.saving = false; } }, async deleteAgent(id) { if (this.lifecycleBusyId) return; if (!confirm(this.tKey('agents.toast.delete_confirm'))) return; try { this.lifecycleBusyId = id; var agent = this.agents.find(function(item) { return item.id === id; }); await window.CrankApi.deleteAgent(this.workspaceId, id); await this.reload(); if (window.CrankUi) { window.CrankUi.success( this.tfKey('agents.toast.delete_message', { name: agent ? agent.display_name : '' }), this.tKey('agents.toast.delete_title') ); } } catch (error) { if (window.CrankUi) { window.CrankUi.error( this.agentMutationErrorMessage(error, this.tKey('agents.toast.delete_error_message')), this.tKey('agents.toast.delete_error_title') ); } } finally { this.lifecycleBusyId = null; } }, async applyLifecycle(agent, action) { if (!this.workspaceId || !window.CrankApi || this.lifecycleBusyId) { return; } var confirmKey = action === 'publish' ? 'agents.toast.lifecycle_publish_confirm' : action === 'unpublish' ? 'agents.toast.lifecycle_unpublish_confirm' : 'agents.toast.lifecycle_archive_confirm'; if (!confirm(this.tfKey(confirmKey, { name: agent.display_name }))) { return; } try { var mutationGeneration = ++this._mutationGeneration; var workspaceId = this.workspaceId; this.lifecycleBusyId = agent.id; if (action === 'publish') { await window.CrankApi.publishAgent(workspaceId, agent.id, { version: agent.current_draft_version || 1, }); } else if (action === 'unpublish') { await window.CrankApi.unpublishAgent(workspaceId, agent.id); } else if (action === 'archive') { await window.CrankApi.archiveAgent(workspaceId, agent.id); } if (mutationGeneration !== this._mutationGeneration || workspaceId !== this.workspaceId) return; await this.reload(); if (mutationGeneration !== this._mutationGeneration || workspaceId !== this.workspaceId) return; if (window.CrankUi) { window.CrankUi.success( action === 'publish' ? this.tfKey('agents.toast.lifecycle_publish', { name: agent.display_name }) : action === 'unpublish' ? this.tfKey('agents.toast.lifecycle_unpublish', { name: agent.display_name }) : this.tfKey('agents.toast.lifecycle_archive', { name: agent.display_name }), this.tKey('agents.toast.lifecycle_title') ); } if (window.CrankOnboarding) window.CrankOnboarding.signalRefresh(); } catch (error) { if (workspaceId !== this.workspaceId) return; if (window.CrankUi) { window.CrankUi.error( this.agentMutationErrorMessage(error, this.tKey('agents.toast.lifecycle_error_message')), this.tKey('agents.toast.lifecycle_error_title') ); } } finally { this.lifecycleBusyId = null; } }, lifecycleAction(agent) { if (agent.raw_status === 'published') { return { key: 'unpublish', label: this.tKey('agents.lifecycle.unpublish') }; } if (agent.raw_status === 'archived') { return { key: 'archive', label: this.tKey('agents.lifecycle.archived') }; } return { key: 'publish', label: this.tKey('agents.lifecycle.publish') }; }, lifecycleLabel(agent) { if (agent.raw_status === 'published') return this.tKey('agents.lifecycle.published'); if (agent.raw_status === 'archived') return this.tKey('agents.lifecycle.archived'); return this.tKey('agents.lifecycle.draft'); }, lifecycleDisabled(agent) { return this.saving || agent.raw_status === 'archived' || (this.lifecycleBusyId && this.lifecycleBusyId !== agent.id); }, agentRevisionText(agent) { return this.tfKey('agents.card.revision', { draft: agent.current_draft_version || 1, published: agent.latest_published_version || '—', revision: agent.catalog_revision || 0, }); }, drawerRevisionText() { if (this.drawerMode !== 'edit') return ''; return this.tfKey('agents.drawer.revision', { draft: this.form.currentDraftVersion || 1, published: this.form.latestPublishedVersion || '—', revision: this.form.catalogRevision || 0, }); }, agentMutationErrorMessage(error, fallback) { var errorCode = error && error.payload && error.payload.error && error.payload.error.context && error.payload.error.context.error_code; if (errorCode === 'agent_stale_revision' || errorCode === 'agent_precondition_required') { return this.tKey('agents.toast.stale_message'); } if (errorCode === 'agent_delete_forbidden') { return this.tKey('agents.toast.delete_forbidden_message'); } return error && error.message ? error.message : fallback; }, 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; }, endpointHelpText(agent) { return this.tfKey( this.isCommunityBuild ? 'agents.card.endpoint_help_community' : 'agents.card.endpoint_help', { count: agent.key_count || 0 } ); }, copyEndpoint(agent) { var endpoint = this.mcpEndpoint(agent); navigator.clipboard.writeText(endpoint).catch(function() {}); if (window.CrankUi) { window.CrankUi.info(endpoint, this.tKey('agents.toast.endpoint_title')); } }, statusClass(status) { return 'agent-status-badge agent-status-' + status; }, protocolBadge(operation) { return 'badge badge-rest'; }, protocolLabel(operation) { return 'REST'; }, formatDate(dateStr) { if (!dateStr) return '—'; return new Date(dateStr).toLocaleDateString(currentLocale(), { month: 'short', day: 'numeric', year: 'numeric', }); }, formatCalls(value) { if (!value) return '0'; return value >= 1000 ? (value / 1000).toFixed(1) + 'k' : String(value); }, tKey(key) { return window.t ? t(key) : key; }, tfKey(key, vars) { return window.tf ? tf(key, vars) : this.tKey(key); }, }; }); });