Skip to content
Draft
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.5.0

- Fill in the Agentic Commerce settings page once the extension is installed: show the readiness counts and add a "Prepare sales channels" flow that enables UCP and the AI files for the selected Storefront and Headless channels at once.

# 1.4.0

- Show the UCP tools only to AI agents that connect through `/ucp/mcp`, instead of to every client of Shopware's own MCP server at `/store-api/_mcp`. 1.3.0 put them into the group Shopware reserves for its own discovery tools, so every client connecting to `/store-api/_mcp` saw the thirteen UCP tools next to Shopware's instruction that no tools are listed until a toolset is enabled -- and models followed that instruction, enabling toolsets for tools they already had. The tools now form their own `ucp` toolset, and `/ucp/mcp` selects it at connect time, so a UCP agent still finds them on its first tool listing while a plain `/store-api/_mcp` connection lists only Shopware's discovery tools. On Shopware versions before `6.7.15.0`, which cannot select a toolset at connect time, the tools stay listed on every connection as before.
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG_de-DE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.5.0

- Die Agentic-Commerce-Einstellungsseite wird nach der Installation der Erweiterung mit Inhalt gefüllt: Sie zeigt die Bereitschaftszahlen an und bietet eine Funktion „Verkaufskanäle vorbereiten“, die UCP und die KI-Dateien für die ausgewählten Storefront- und Headless-Kanäle gesammelt aktiviert.

# 1.4.0

- Die UCP-Tools sind jetzt nur noch für KI-Agenten sichtbar, die sich über `/ucp/mcp` verbinden, und nicht mehr für jeden Client von Shopwares eigenem MCP-Server unter `/store-api/_mcp`. In 1.3.0 lagen sie in der Gruppe, die Shopware für seine eigenen Discovery-Tools vorsieht. Jeder Client, der sich mit `/store-api/_mcp` verband, bekam deshalb alle dreizehn UCP-Tools angezeigt, obwohl Shopware ihm gleichzeitig mitteilt, dass Tools erst nach dem Aktivieren eines Toolsets erscheinen. Die Modelle haben sich daran gehalten und Toolsets für Tools aktiviert, die sie längst hatten. Jetzt bilden die Tools ein eigenes Toolset `ucp`, das `/ucp/mcp` direkt beim Verbindungsaufbau auswählt: Ein UCP-Agent sieht sie weiterhin sofort, eine normale Verbindung zu `/store-api/_mcp` zeigt nur noch die Discovery-Tools von Shopware. Unter Shopware-Versionen vor `6.7.15.0`, in denen sich beim Verbindungsaufbau kein Toolset auswählen lässt, bleibt alles wie bisher.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "shopware/agentic-commerce",
"description": "Shopware plugin for agentic commerce discovery, product feeds, and Universal Commerce Protocol shopping flows",
"version": "1.4.0",
"version": "1.5.0",
"type": "shopware-platform-plugin",
"license": "MIT",
"authors": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ class UcpAdminApiService extends ApiService {
super(httpClient, loginService, apiEndpoint);
}

getReadiness() {
return this.httpClient.get('/_admin/ucp/readiness', this.options());
}

prepareSalesChannels(salesChannelIds) {
return this.httpClient.post('/_admin/ucp/prepare', { salesChannelIds }, this.options());
}

getSalesChannels() {
return this.httpClient.get('/_admin/ucp/sales-channels', this.options());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import template from './sw-settings-agentic-commerce.html.twig';
import './prepare-modal';

const { Component } = Shopware;

/**
* Drives the counts on the core Agentic Commerce settings page and owns the
* "prepare sales channels" flow once the plugin is active. The counts are
* computed server-side (UCP config lives in the plugin config table, not on the
* sales_channel entity) and fetched via `ucpAdminApiService.getReadiness()`.
*
* The core settings page only exists on Shopware versions that ship it, so the
* override is registered conditionally — on older versions there is nothing to
* override and the plugin keeps its sales-channel-scoped admin instead.
*/
export const swSettingsAgenticCommerceOverride = {
template,

inject: ['acl'],

data() {
return {
showPrepareModal: false,
transactionalSalesChannelCount: 0,
};
},

computed: {
canViewUcpStatus() {
return this.acl?.can?.('ucp.viewer') === true;
},

// Marks the "prepare" step complete once every transactional (Storefront/Headless)
// sales channel has UCP enabled. Comparing against the total keeps the flag false
// before the counts load (all zero) and before any channel exists.
allSalesChannelsPrepared() {
return (
this.transactionalSalesChannelCount > 0 &&
this.preparedSalesChannelCount >= this.transactionalSalesChannelCount
);
},

// The plugin's admin bundle only loads when the plugin is installed and active,
// so here the extension step is always complete; the prepare step reflects the
// actual preparation state.
steps() {
return this.$super('steps').map((step) => {
if (step.key === 'extension') {
return {
...step,
complete: true,
description: this.$t('swagAgenticCommerce.extensionStep.activeDescription'),
};
}

if (step.key === 'prepareSalesChannels') {
const complete = this.allSalesChannelsPrepared;

return {
...step,
complete,
description: complete
? this.$t('swagAgenticCommerce.prepareStep.completedDescription')
: step.description,
};
}

return step;
});
},

canPrepareSalesChannels() {
return (
this.acl?.can?.('ucp.editor') === true &&
this.preparedSalesChannelCount < this.transactionalSalesChannelCount
);
},

prepareSalesChannelsTooltip() {
return {
message: this.$t('swagAgenticCommerce.prepareStep.missingPermissionTooltip'),
disabled: this.acl?.can?.('ucp.editor') === true,
showOnDisabledElements: true,
};
},

readinessSubtitle() {
return this.isReady
? this.$t('swagAgenticCommerce.subtitleReady')
: this.$t('swagAgenticCommerce.subtitleActive');
},

extensionStatusLabel() {
return this.$t('swagAgenticCommerce.shopReadiness.extensionInstalled');
},
},

created() {
this.loadReadinessCounts();
},

methods: {
loadReadinessCounts() {
if (!this.canViewUcpStatus) {
return;
}

const service = Shopware.Service('ucpAdminApiService');
if (!service?.getReadiness) {
return;
}

service
.getReadiness()
.then((response) => {
this.applyReadiness(response?.data?.data ?? {});
})
.catch(() => {});
},

applyReadiness(data) {
this.preparedSalesChannelCount = data.preparedSalesChannels ?? 0;
this.agenticSalesChannelCount = data.agenticSalesChannels ?? 0;
this.transactionalSalesChannelCount = data.transactionalSalesChannels ?? 0;
},

// Overrides the core no-op: open the selection modal instead.
onPrepareSalesChannels() {
this.showPrepareModal = true;
},

onPrepareModalClose() {
this.showPrepareModal = false;
},

onSalesChannelsPrepared(summary) {
this.showPrepareModal = false;

if (summary) {
this.applyReadiness(summary);
return;
}

this.loadReadinessCounts();
},
},
};

if (Component.getComponentRegistry().has('sw-settings-agentic-commerce')) {
Component.override('sw-settings-agentic-commerce', swSettingsAgenticCommerceOverride);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import template from './sw-settings-agentic-commerce-prepare-modal.html.twig';
import './sw-settings-agentic-commerce-prepare-modal.scss';

const { Component, Mixin } = Shopware;
const { Criteria, EntityCollection } = Shopware.Data;

/**
* Modal for the "prepare sales channels" flow. Lists the transactional
* (Storefront/Headless) sales channels that do not have UCP enabled yet in a
* multi-select, pre-selects all of them, and — on confirm — activates UCP plus
* the AI files for the selected channels via
* `ucpAdminApiService.prepareSalesChannels()`.
*
* Which channels are still unprepared is known only to the plugin (UCP config
* lives in its own table), so that set comes from `getSalesChannels()`. The
* multi-select itself is bound to the `sales_channel` repository so it renders
* the translated channel names, and is constrained to the unprepared ids.
*/
export const swSettingsAgenticCommercePrepareModal = {
template,

inject: ['repositoryFactory'],

mixins: [Mixin.getByName('notification')],

emits: ['prepared', 'close'],

data() {
return {
unpreparedIds: [],
selectedSalesChannelsCollection: null,
isLoading: false,
isPreparing: false,
};
},

computed: {
salesChannelRepository() {
return this.repositoryFactory.create('sales_channel');
},

salesChannelCriteria() {
const criteria = new Criteria(1, 25);
criteria.addFilter(Criteria.equalsAny('id', this.unpreparedIds));

return criteria;
},

hasUnpreparedSalesChannels() {
return this.unpreparedIds.length > 0;
},

selectedIds() {
return this.selectedSalesChannelsCollection ? this.selectedSalesChannelsCollection.getIds() : [];
},

hasSelection() {
return this.selectedIds.length > 0;
},
},

created() {
this.loadUnpreparedSalesChannels();
},

methods: {
loadUnpreparedSalesChannels() {
const service = Shopware.Service('ucpAdminApiService');
if (!service?.getSalesChannels) {
return;
}

this.isLoading = true;

service
.getSalesChannels()
.then((response) => {
// The endpoint already returns only transactional channels; keep the ones
// without UCP active, i.e. the ones still to prepare.
this.unpreparedIds = (response?.data?.data ?? [])
.filter((salesChannel) => !salesChannel.ucp?.active)
.map((salesChannel) => salesChannel.id);

return this.buildSelectedSalesChannelsCollection();
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
this.createNotificationError({
message: this.$t('swagAgenticCommerce.prepareModal.loadErrorMessage'),
});
});
},

buildSelectedSalesChannelsCollection() {
if (this.unpreparedIds.length === 0) {
this.selectedSalesChannelsCollection = new EntityCollection(
'/sales-channel',
'sales_channel',
Shopware.Context.api,
new Criteria(1, 25),
);

return Promise.resolve();
}

// Pre-select all unprepared channels (per the ticket).
return this.salesChannelRepository.search(this.salesChannelCriteria, Shopware.Context.api).then((result) => {
this.selectedSalesChannelsCollection = result;
});
},

onCancel() {
this.$emit('close');
},

onConfirm() {
if (!this.hasSelection || this.isPreparing) {
return;
}

const service = Shopware.Service('ucpAdminApiService');
this.isPreparing = true;

service
.prepareSalesChannels(this.selectedIds)
.then((response) => {
this.isPreparing = false;
this.createNotificationSuccess({
message: this.$t('swagAgenticCommerce.prepareModal.successMessage'),
});
this.$emit('prepared', response?.data?.data ?? null);
})
.catch(() => {
this.isPreparing = false;
this.createNotificationError({
message: this.$t('swagAgenticCommerce.prepareModal.errorMessage'),
});
});
},
},
};

Component.register('sw-settings-agentic-commerce-prepare-modal', swSettingsAgenticCommercePrepareModal);
Loading
Loading