feat: complete Epic 1 production foundation

This commit is contained in:
2026-08-25 01:24:11 +03:00
parent 767428436d
commit 182bde8ac0
298 changed files with 35719 additions and 5299 deletions
+123 -18
View File
@@ -20,6 +20,7 @@ function mapAgent(agent) {
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 || '',
};
@@ -60,6 +61,7 @@ document.addEventListener('alpine:init', function() {
drawerMode: 'create',
editingId: null,
saving: false,
lifecycleBusyId: null,
form: {
display_name: '',
@@ -79,6 +81,9 @@ document.addEventListener('alpine:init', function() {
searchPreviewItems: [],
searchPreviewLoading: false,
searchPreviewRan: false,
_loadGeneration: 0,
_mutationGeneration: 0,
_queryHydrated: false,
async init() {
var self = this;
@@ -88,6 +93,7 @@ document.addEventListener('alpine:init', function() {
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) {
@@ -100,6 +106,9 @@ document.addEventListener('alpine:init', function() {
});
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();
@@ -119,6 +128,8 @@ document.addEventListener('alpine:init', function() {
},
async reload() {
var generation = ++this._loadGeneration;
var workspaceId = this.workspaceId;
this.loading = true;
this.loadError = '';
@@ -132,18 +143,39 @@ document.addEventListener('alpine:init', function() {
try {
var responses = await Promise.all([
window.CrankApi.listAgents(this.workspaceId),
window.CrankApi.listOperations(this.workspaceId),
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');
}
this.loading = false;
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() {
@@ -254,6 +286,9 @@ document.addEventListener('alpine:init', function() {
accessMode: 'direct',
groups: [],
searchMaxResults: 8,
catalogRevision: 0,
currentDraftVersion: 1,
latestPublishedVersion: null,
};
this.opSearch = '';
this.slugManuallyEdited = false;
@@ -283,6 +318,9 @@ document.addEventListener('alpine:init', function() {
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;
@@ -293,6 +331,7 @@ document.addEventListener('alpine:init', function() {
closeDrawer() {
this.drawerOpen = false;
this.saving = false;
this.lifecycleBusyId = null;
},
onNameInput(value) {
@@ -514,6 +553,8 @@ document.addEventListener('alpine:init', function() {
}
this.saving = true;
var mutationGeneration = ++this._mutationGeneration;
var workspaceId = this.workspaceId;
try {
var agentId = this.editingId;
@@ -524,7 +565,7 @@ document.addEventListener('alpine:init', function() {
var previousStatus = existingAgent ? (existingAgent.raw_status || 'draft') : 'draft';
if (this.drawerMode === 'create') {
var created = await window.CrankApi.createAgent(this.workspaceId, {
var created = await window.CrankApi.createAgent(workspaceId, {
slug: this.form.slug,
display_name: this.form.display_name,
description: this.form.description,
@@ -534,17 +575,17 @@ document.addEventListener('alpine:init', function() {
agentId = created.agent_id;
currentVersion = created.version || 1;
} else {
await window.CrankApi.updateAgent(this.workspaceId, this.editingId, {
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(this.workspaceId, this.editingId);
var agent = await window.CrankApi.getAgent(workspaceId, this.editingId);
currentVersion = agent.current_draft_version || 1;
}
var savedVersion = await window.CrankApi.saveAgentBindings(
this.workspaceId,
workspaceId,
agentId,
{
bindings: this.agentBindings(),
@@ -554,16 +595,19 @@ document.addEventListener('alpine:init', function() {
currentVersion = savedVersion.version || currentVersion;
if (this.form.status === 'published') {
await window.CrankApi.publishAgent(this.workspaceId, agentId, {
await window.CrankApi.publishAgent(workspaceId, agentId, {
version: currentVersion,
});
} else if (this.form.status === 'archived') {
await window.CrankApi.archiveAgent(this.workspaceId, agentId);
await window.CrankApi.archiveAgent(workspaceId, agentId);
} else if (this.drawerMode === 'edit' && previousStatus !== 'draft') {
await window.CrankApi.unpublishAgent(this.workspaceId, agentId);
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', {
@@ -576,10 +620,12 @@ document.addEventListener('alpine:init', function() {
);
}
this.closeDrawer();
if (window.CrankOnboarding) window.CrankOnboarding.signalRefresh();
} catch (error) {
if (mutationGeneration !== this._mutationGeneration || workspaceId !== this.workspaceId) return;
if (window.CrankUi) {
window.CrankUi.error(
error.message || this.tKey('agents.toast.save_error_message'),
this.agentMutationErrorMessage(error, this.tKey('agents.toast.save_error_message')),
this.tKey('agents.toast.save_error_title')
);
}
@@ -588,9 +634,11 @@ document.addEventListener('alpine:init', function() {
},
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();
@@ -603,29 +651,44 @@ document.addEventListener('alpine:init', function() {
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(
error.message || this.tKey('agents.toast.delete_error_message'),
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) {
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(this.workspaceId, agent.id, {
await window.CrankApi.publishAgent(workspaceId, agent.id, {
version: agent.current_draft_version || 1,
});
} else if (action === 'unpublish') {
await window.CrankApi.unpublishAgent(this.workspaceId, agent.id);
await window.CrankApi.unpublishAgent(workspaceId, agent.id);
} else if (action === 'archive') {
await window.CrankApi.archiveAgent(this.workspaceId, agent.id);
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'
@@ -636,13 +699,17 @@ document.addEventListener('alpine:init', function() {
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(
error.message || this.tKey('agents.toast.lifecycle_error_message'),
this.agentMutationErrorMessage(error, this.tKey('agents.toast.lifecycle_error_message')),
this.tKey('agents.toast.lifecycle_error_title')
);
}
} finally {
this.lifecycleBusyId = null;
}
},
@@ -651,7 +718,7 @@ document.addEventListener('alpine:init', function() {
return { key: 'unpublish', label: this.tKey('agents.lifecycle.unpublish') };
}
if (agent.raw_status === 'archived') {
return { key: 'unpublish', label: this.tKey('agents.lifecycle.restore_draft') };
return { key: 'archive', label: this.tKey('agents.lifecycle.archived') };
}
return { key: 'publish', label: this.tKey('agents.lifecycle.publish') };
},
@@ -662,6 +729,44 @@ document.addEventListener('alpine:init', function() {
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;