Merge pull request #526 from iflytek/feature/add-agentguard-skill

feat(bootstrap): add agentguard builtin skill
This commit is contained in:
dongmucat 2026-06-16 14:27:30 +08:00 committed by GitHub
commit 05e1968563
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 75 additions and 16 deletions

View file

@ -50,6 +50,7 @@ public class BuiltinSkillInitializer {
private static final Logger log = LoggerFactory.getLogger(BuiltinSkillInitializer.class);
private static final Set<String> SYSTEM_PUBLISHER_ROLES = Set.of("SUPER_ADMIN");
private static final boolean CONFIRM_BUILTIN_PUBLISH_WARNINGS = true;
private final BuiltinSkillProperties properties;
private final BuiltinSkillManifestLoader manifestLoader;
@ -243,7 +244,7 @@ public class BuiltinSkillInitializer {
SYSTEM_PUBLISHER_ID,
SkillVisibility.PUBLIC,
SYSTEM_PUBLISHER_ROLES,
false
CONFIRM_BUILTIN_PUBLISH_WARNINGS
);
log.info("Published built-in skill slug={} version={} to @{}",
item.slug(), item.version(), GLOBAL_NAMESPACE);

View file

@ -4,6 +4,11 @@
"slug": "skillhub-hello",
"version": "1.0.0",
"url": "https://bjcdn.openstorage.cn/aicontest/2026-06-11/f8a59af3-30d4-4031-80f6-ebff74b05195.zip"
},
{
"slug": "agentguard",
"version": "1.1",
"url": "https://bjcdn.openstorage.cn/aicontest/2026-06-12/9d063bc7-223a-4762-adeb-305c268aa29e.zip"
}
]
}

View file

@ -2,6 +2,7 @@ package com.iflytek.skillhub.bootstrap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
@ -106,7 +107,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(manifestLoader, never()).load();
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -116,7 +117,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(manifestLoader, never()).load();
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -144,7 +145,7 @@ class BuiltinSkillInitializerTest {
verify(namespaceMemberRepository, never()).save(any());
verify(downloader, never()).download(any());
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -162,7 +163,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(downloader, never()).download(any());
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -180,7 +181,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(downloader, never()).download(any());
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -192,7 +193,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(downloader, never()).download(any());
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -206,7 +207,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(downloader).download(URI.create(ITEM.url()));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -222,7 +223,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(downloader, never()).download(any());
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
assertThat(output).doesNotContain("Failed to synchronize built-in skill slug=skillhub-hello");
}
@ -232,7 +233,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -241,7 +242,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), eq(false));
verify(skillPublishService, never()).publishFromEntries(any(), any(), any(), any(), any(), anyBoolean());
}
@Test
@ -259,7 +260,7 @@ class BuiltinSkillInitializerTest {
eq(PUBLISHER),
eq(SkillVisibility.PUBLIC),
eq(Set.of("SUPER_ADMIN")),
eq(false)
eq(true)
);
assertThat(entriesCaptor.getValue()).isEqualTo(entries);
}
@ -297,7 +298,7 @@ class BuiltinSkillInitializerTest {
.thenReturn(List.of())
.thenReturn(List.of(builtinSkill));
when(skillPublishService.publishFromEntries(
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(false)))
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(true)))
.thenThrow(new DomainBadRequestException("error.skill.version.exists", "1.0.0"));
when(skillVersionRepository.findBySkillIdAndVersion(100L, "1.0.0")).thenReturn(Optional.of(published));
when(skillFileRepository.findByVersionId(200L)).thenReturn(skillFilesFor(entries, 200L));
@ -305,7 +306,7 @@ class BuiltinSkillInitializerTest {
runInitializer();
verify(skillPublishService).publishFromEntries(
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(false));
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(true));
}
@Test
@ -318,7 +319,7 @@ class BuiltinSkillInitializerTest {
.thenReturn(List.of())
.thenReturn(List.of(builtinSkill));
when(skillPublishService.publishFromEntries(
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(false)))
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(true)))
.thenThrow(new DomainBadRequestException("error.skill.version.exists", "1.0.0"));
when(skillVersionRepository.findBySkillIdAndVersion(100L, "1.0.0")).thenReturn(Optional.of(published));
when(skillFileRepository.findByVersionId(200L)).thenReturn(List.of(
@ -329,7 +330,7 @@ class BuiltinSkillInitializerTest {
verify(skillFileRepository).findByVersionId(200L);
verify(skillPublishService).publishFromEntries(
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(false));
eq(GLOBAL), any(), eq(PUBLISHER), eq(SkillVisibility.PUBLIC), eq(Set.of("SUPER_ADMIN")), eq(true));
assertThat(output).contains("Failed to publish built-in skill slug=skillhub-hello version=1.0.0");
assertThat(output).doesNotContain("was published concurrently, skipping");
}

View file

@ -50,6 +50,9 @@ public class SkillMetadataParser {
String name = extractRequiredField(frontmatter, "name");
String description = extractRequiredField(frontmatter, "description");
String version = extractOptionalField(frontmatter, "version");
if (version == null) {
version = extractNestedOptionalField(frontmatter, "metadata", "version");
}
return new SkillMetadata(name, description, version, body, frontmatter);
}
@ -122,4 +125,13 @@ public class SkillMetadataParser {
Object value = frontmatter.get(fieldName);
return value == null ? null : value.toString();
}
private String extractNestedOptionalField(Map<String, Object> frontmatter, String objectFieldName, String fieldName) {
Object nestedValue = frontmatter.get(objectFieldName);
if (!(nestedValue instanceof Map<?, ?> nestedMap)) {
return null;
}
Object value = nestedMap.get(fieldName);
return value == null ? null : value.toString();
}
}

View file

@ -123,6 +123,46 @@ class SkillMetadataParserTest {
assertNull(metadata.version());
}
@Test
void testUsesMetadataVersionWhenTopLevelVersionIsMissing() {
String content = """
---
name: agentguard
description: Agent security guard
metadata:
author: GoPlusSecurity
version: "1.1"
---
Body
""";
SkillMetadata metadata = parser.parse(content);
assertEquals("agentguard", metadata.name());
assertEquals("Agent security guard", metadata.description());
assertEquals("1.1", metadata.version());
}
@Test
void testTopLevelVersionTakesPrecedenceOverMetadataVersion() {
String content = """
---
name: versioned-skill
description: Prefer top-level version
version: 2.0.0
metadata:
version: "1.1"
---
Body
""";
SkillMetadata metadata = parser.parse(content);
assertEquals("versioned-skill", metadata.name());
assertEquals("Prefer top-level version", metadata.description());
assertEquals("2.0.0", metadata.version());
}
@Test
void testFallsBackToLooseFrontmatterParsingWhenYamlSyntaxIsNotStrict() {
String content = """