feat(store): source-bucket from CapabilityEntry.source_kind
Some checks failed
Security / Security check (push) Failing after 1s

Studio's `_sourceForItem` now prefers the wire-level
`sourceKind` field (populated by 0.12+ hubs) over the legacy
name-prefix heuristic. Adds a "temporal" bucket alongside
"mcp" / "n8n" / "native" / "federated" so SPARK-federated
workflows render with their own transport pill on the store
grid + filter chip.

Pre-0.12 hubs return empty `sourceKind` and the legacy
sniffing path still works -- back-compat preserved.

`StoreItem` data class accepts an optional `sourceKind`
parameter (defaults to empty). dart analyze green.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-28 12:06:59 +02:00
parent d697339393
commit 2c13ef58ae
2 changed files with 28 additions and 4 deletions

View file

@ -597,6 +597,7 @@ class HubService {
docsUrl: e.docsUrl, docsUrl: e.docsUrl,
kind: e.kind, kind: e.kind,
provider: e.provider, provider: e.provider,
sourceKind: e.sourceKind,
), ),
) )
.toList(); .toList();
@ -1399,6 +1400,12 @@ class StoreItem {
/// Provider id for federated entries. Empty for native. /// Provider id for federated entries. Empty for native.
/// E.g. "filesystem" for an `mcp.filesystem.read_file` tool. /// E.g. "filesystem" for an `mcp.filesystem.read_file` tool.
final String provider; final String provider;
/// Transport delivering this entry. Distinct from `kind`
/// `sourceKind` answers "HOW does the hub reach this", not
/// "is it installable". Values: "bundle", "mcp", "n8n",
/// "temporal", "webhook". Empty falls back to deriving
/// from `kind` for pre-0.12 hubs.
final String sourceKind;
bool get isFederated => kind == 'federated'; bool get isFederated => kind == 'federated';
@ -1423,5 +1430,6 @@ class StoreItem {
required this.docsUrl, required this.docsUrl,
required this.kind, required this.kind,
required this.provider, required this.provider,
this.sourceKind = '',
}); });
} }

View file

@ -641,11 +641,27 @@ class _StorePageState extends State<StorePage> {
} }
/// Maps a store entry to its provenance bucket: 'native' for /// Maps a store entry to its provenance bucket: 'native' for
/// non-federated entries, 'mcp' / 'n8n' for federated tools that /// non-federated entries, 'mcp' / 'n8n' / 'temporal' for federated
/// flow through one of the bridges. Used by the source filter /// tools that flow through one of the bridges. Used by the
/// chip and the per-card provenance pill so operators can tell /// source filter chip and the per-card provenance pill so
/// at a glance which entries come from where. /// operators can tell at a glance which entries come from where.
///
/// Prefers the wire-level `sourceKind` field (0.12+); falls back
/// to the legacy name-prefix heuristic for pre-0.12 hubs that
/// don't populate it.
String _sourceForItem(StoreItem i) { String _sourceForItem(StoreItem i) {
if (i.sourceKind.isNotEmpty) {
switch (i.sourceKind) {
case 'bundle':
return 'native';
case 'mcp':
case 'n8n':
case 'temporal':
return i.sourceKind;
default:
return 'federated';
}
}
if (!i.isFederated) return 'native'; if (!i.isFederated) return 'native';
if (i.name.startsWith('mcp.')) return 'mcp'; if (i.name.startsWith('mcp.')) return 'mcp';
if (i.name.startsWith('n8n.')) return 'n8n'; if (i.name.startsWith('n8n.')) return 'n8n';