mirror of
https://github.com/block/buzz.git
synced 2026-11-08 18:31:56 +00:00
A printed Builderlab agent card's QR leads to its share page (app.builderlab.xyz/a/<id>), whose "Add to Buzz" button opens buzz://agent/new?card=<id>. Mobile now handles that link: it fetches the card's agent.json from the avatars CDN for this build's Builderlab environment and opens the Agents page with an editable, prefilled Create agent form. Opening a link never creates an agent. - shared/deeplink: strict parser for buzz://agent/new?card=<token>; any other shape is not this link. - HostedEnvironment.avatarsCdnOrigin maps each account environment to its CDN; the link can never name the host. - AgentCreationDraft.fetch reads name, avatarUrl, instructions, model and reasoningEffort. Every field is optional: published cards have only name and avatar today. snake_case and a few synonyms are also read so a later agent.json revision degrades to a partial draft instead of failing. The avatar is used only from the same CDN; the body is capped at 256 KiB. - AgentsPage waits for sign-in, the registry load and remote-agent support, drops the link with a message if a setup is unfinished or the card can't be loaded, and replaces an open Agents stack rather than stacking one. - CreateAgentPage takes an optional draft; model and effort apply only if the live catalog offers them. - Table tests for the link and agent.json, and a journey test from link to Create against a fake CDN (needs BUZZ_AUTH_DOMAINS, like the other hosted agent tests). Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Signed-off-by: Tom Brow <tomb@squareup.com>
109 lines
3.9 KiB
Dart
109 lines
3.9 KiB
Dart
/// Trusted build configuration, never supplied by a deep link or relay.
|
|
abstract final class HostedEnvironment {
|
|
/// Account UI and startup work are opt-in, independently of the backend URL.
|
|
static bool get enabled => authDomains.isNotEmpty;
|
|
|
|
static final Uri origin = _origin();
|
|
static const _communityAccounts = {
|
|
'communities.buzz.xyz': 'https://app.builderlab.xyz',
|
|
'communities.public.builderlabstaging.xyz':
|
|
'https://app.builderlabstaging.xyz',
|
|
};
|
|
|
|
/// Public CDN serving each published Builderlab agent card's
|
|
/// `<id>/agent.json` and images, for this build's account environment.
|
|
static Uri get avatarsCdnOrigin => Uri.parse(switch (origin.host) {
|
|
'app.builderlab.xyz' => 'https://d2z0pbxrxbsiz5.cloudfront.net',
|
|
'app.builderlabstaging.xyz' => 'https://d10hx6rb3fdt3l.cloudfront.net',
|
|
_ => throw StateError('Unsupported hosted account environment'),
|
|
});
|
|
|
|
/// Shortens hostname fallbacks for display without changing relay identity.
|
|
static String communityDisplayName(String name, String relayUrl) {
|
|
final relay = Uri.tryParse(relayUrl);
|
|
if (relay == null ||
|
|
name != relay.host ||
|
|
accountOriginFor(relay) == null) {
|
|
return name;
|
|
}
|
|
for (final domain in _communityAccounts.keys) {
|
|
final suffix = '.$domain';
|
|
if (name.endsWith(suffix)) {
|
|
return name.substring(0, name.length - suffix.length);
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/// Creation and admission use the same trusted build-specific host mapping.
|
|
static String get communitySuffix => _communityAccounts.entries
|
|
.singleWhere((entry) => Uri.parse(entry.value) == origin)
|
|
.key;
|
|
|
|
/// Comma-separated DNS domains requiring Builderlab authentication.
|
|
/// Empty builds keep Nostr-only entry available, including hosted QR pairing.
|
|
static final List<String> authDomains = List.unmodifiable(
|
|
const String.fromEnvironment('BUZZ_AUTH_DOMAINS')
|
|
.split(',')
|
|
.map(
|
|
(domain) =>
|
|
domain.trim().toLowerCase().replaceFirst(RegExp(r'\.+$'), ''),
|
|
)
|
|
.where((domain) => domain.isNotEmpty)
|
|
.map((domain) {
|
|
if (!RegExp(
|
|
r'^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*$',
|
|
).hasMatch(domain)) {
|
|
throw StateError(
|
|
'BUZZ_AUTH_DOMAINS must contain DNS names, not URLs or wildcards',
|
|
);
|
|
}
|
|
return domain;
|
|
}),
|
|
);
|
|
|
|
/// Matches a configured domain and its subdomains, never a string suffix.
|
|
static bool requiresAuth(String host) {
|
|
final normalized = host.toLowerCase().replaceFirst(RegExp(r'\.+$'), '');
|
|
return authDomains.any(
|
|
(domain) => normalized == domain || normalized.endsWith('.$domain'),
|
|
);
|
|
}
|
|
|
|
/// Explicit community-to-account mapping. Never infer an account endpoint
|
|
/// from arbitrary relay metadata or send this build's session across realms.
|
|
static Uri? accountOriginFor(Uri relay) {
|
|
if (!['wss', 'https'].contains(relay.scheme) ||
|
|
relay.userInfo.isNotEmpty ||
|
|
relay.hasQuery ||
|
|
relay.hasFragment ||
|
|
(relay.path.isNotEmpty && relay.path != '/') ||
|
|
(relay.hasPort && relay.port != 443)) {
|
|
return null;
|
|
}
|
|
final host = relay.host.toLowerCase();
|
|
for (final entry in _communityAccounts.entries) {
|
|
if (host.endsWith('.${entry.key}')) return Uri.parse(entry.value);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Whether this build can use its account session for this relay.
|
|
static bool supportsRelay(Uri relay) => accountOriginFor(relay) == origin;
|
|
|
|
static Uri _origin() {
|
|
const configured = String.fromEnvironment(
|
|
'BUZZ_BUILDERLAB_URL',
|
|
defaultValue: 'https://app.builderlab.xyz',
|
|
);
|
|
final uri = Uri.parse(configured);
|
|
if (!{
|
|
'https://app.builderlab.xyz',
|
|
'https://app.builderlabstaging.xyz',
|
|
}.contains(configured)) {
|
|
throw StateError('Unsupported hosted account environment');
|
|
}
|
|
return uri;
|
|
}
|
|
}
|