feat: complete Epic 1 production foundation
This commit is contained in:
+123
-18
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user