通过 Tauri command 提供 Skill 候选
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m53s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m54s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 3m1s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 3m2s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 1m28s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m20s
Project CI / Frontend tests (pull_request) Failing after 3m52s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m9s
Project CI / Native shell tests (pull_request) Failing after 4m39s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m53s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m54s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 3m1s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 3m2s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 1m28s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m20s
Project CI / Frontend tests (pull_request) Failing after 3m52s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m9s
Project CI / Native shell tests (pull_request) Failing after 4m39s
从已校验的 AGC Skill manifest 派生 list_agc_skill_catalog 命令 移除前端内置 Skill 名称硬编码并增加读取命令测试
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
@@ -121,6 +121,13 @@ struct AgcSkillManifestEntry {
|
||||
sha256: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AgcSkillCatalogEntry {
|
||||
pub(crate) name: String,
|
||||
pub(crate) description: String,
|
||||
}
|
||||
|
||||
fn is_safe_skill_relative_path(value: &str) -> bool {
|
||||
let path = Path::new(value);
|
||||
!value.is_empty()
|
||||
@@ -234,6 +241,21 @@ pub(crate) fn agc_skill_pack_fingerprint() -> Result<String, String> {
|
||||
Ok(format!("{:x}", Sha256::digest(canonical_manifest.as_ref())))
|
||||
}
|
||||
|
||||
/// 返回当前客户端随 AGC 一起启用的内置 Skill 候选。
|
||||
///
|
||||
/// 前端不得复制审核清单;Skill 名称和描述统一从经过校验的资源 manifest 派生。
|
||||
#[tauri::command]
|
||||
pub(crate) fn list_agc_skill_catalog() -> Result<Vec<AgcSkillCatalogEntry>, String> {
|
||||
Ok(validated_skill_pack_manifest()?
|
||||
.skills
|
||||
.into_iter()
|
||||
.map(|entry| AgcSkillCatalogEntry {
|
||||
name: entry.name,
|
||||
description: entry.purpose,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn render_agc_skill_pack_index() -> Result<String, String> {
|
||||
let manifest = validated_skill_pack_manifest()?;
|
||||
let mut lines = vec![format!(
|
||||
@@ -328,6 +350,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_catalog_is_derived_from_the_validated_manifest() {
|
||||
let catalog = list_agc_skill_catalog().expect("skill catalog");
|
||||
assert_eq!(catalog.len(), AGC_SKILL_PACK_EXPECTED_NAMES.len());
|
||||
for expected_name in AGC_SKILL_PACK_EXPECTED_NAMES {
|
||||
let entry = catalog
|
||||
.iter()
|
||||
.find(|entry| entry.name == expected_name)
|
||||
.expect("expected bundled skill");
|
||||
assert!(!entry.description.trim().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_content_digest_is_stable_across_lf_and_crlf() {
|
||||
fn digest(bytes: &[u8]) -> String {
|
||||
|
||||
@@ -2655,6 +2655,7 @@ fn main() {
|
||||
pick_client_extension_file,
|
||||
pick_client_extension_directory,
|
||||
list_client_extensions,
|
||||
list_agc_skill_catalog,
|
||||
import_client_extension,
|
||||
set_client_extension_enabled,
|
||||
rename_client_extension,
|
||||
|
||||
+19
-11
@@ -187,15 +187,6 @@ class SkillMentionOption extends MenuOption {
|
||||
}
|
||||
}
|
||||
|
||||
const BUILTIN_SKILL_REFERENCES: SkillReference[] = [
|
||||
'agc-browser-playtest',
|
||||
'agc-client-projection',
|
||||
'agc-game-production-workflow',
|
||||
'agc-project-structure',
|
||||
'agc-web-game-development',
|
||||
'taonier-art-assets',
|
||||
].map((name) => ({ type: 'skill', name }));
|
||||
|
||||
function appendInputText(content: DirectCodexUserContentPart[], text: string) {
|
||||
const previous = content[content.length - 1];
|
||||
if (previous?.type === 'input_text') {
|
||||
@@ -491,6 +482,7 @@ function ResourceReferenceEditor({
|
||||
const skipInitialDraftChangeRef = useRef(false);
|
||||
const [query, setQuery] = useState<string | null>(null);
|
||||
const [skillQuery, setSkillQuery] = useState<string | null>(null);
|
||||
const [builtinSkills, setBuiltinSkills] = useState<SkillReference[]>([]);
|
||||
const [clientSkills, setClientSkills] = useState<SkillReference[]>([]);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
// Enter 提交要避开候选浮层。用 ref 让命令处理器读到最新状态,不必因为
|
||||
@@ -517,6 +509,22 @@ function ResourceReferenceEditor({
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke) return;
|
||||
let cancelled = false;
|
||||
void invoke<Array<{ name: string; description: string }>>(
|
||||
'list_agc_skill_catalog',
|
||||
)
|
||||
.then((items) => {
|
||||
if (cancelled) return;
|
||||
setBuiltinSkills(
|
||||
items.map((item) => ({
|
||||
type: 'skill' as const,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
})),
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setBuiltinSkills([]);
|
||||
});
|
||||
void invoke<
|
||||
Array<{
|
||||
name: string;
|
||||
@@ -607,7 +615,7 @@ function ResourceReferenceEditor({
|
||||
const skillOptions = useMemo(() => {
|
||||
if (skillQuery === null) return [];
|
||||
const normalized = skillQuery.trim().toLowerCase();
|
||||
const allSkills = [...BUILTIN_SKILL_REFERENCES, ...clientSkills, ...skills];
|
||||
const allSkills = [...builtinSkills, ...clientSkills, ...skills];
|
||||
const seen = new Set<string>();
|
||||
return allSkills
|
||||
.filter((skill) => {
|
||||
@@ -621,7 +629,7 @@ function ResourceReferenceEditor({
|
||||
})
|
||||
.slice(0, 8)
|
||||
.map((skill) => new SkillMentionOption(skill));
|
||||
}, [clientSkills, skillQuery, skills]);
|
||||
}, [builtinSkills, clientSkills, skillQuery, skills]);
|
||||
|
||||
const triggerFn = useBasicTypeaheadTriggerMatch('@', {
|
||||
minLength: 0,
|
||||
|
||||
@@ -155,6 +155,36 @@ async function insertAssetThroughPicker(ariaLabel: string, optionName: RegExp) {
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('ResourceReferenceInput', () => {
|
||||
test('从 Tauri command 读取内置 Skill catalog', async () => {
|
||||
const invoke = vi.fn(async (command: string) => {
|
||||
if (command === 'list_agc_skill_catalog') {
|
||||
return [{ name: 'agc-test-skill', description: '测试 Skill' }];
|
||||
}
|
||||
if (command === 'list_client_extensions') return [];
|
||||
throw new Error(`unexpected invoke ${command}`);
|
||||
});
|
||||
window.__TAURI__ = { core: { invoke: invoke as never } };
|
||||
|
||||
try {
|
||||
render(
|
||||
<ResourceReferenceInput
|
||||
value=""
|
||||
references={[]}
|
||||
onChange={vi.fn()}
|
||||
assets={assets}
|
||||
projectPath="C:/project"
|
||||
ariaLabel="聊天"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith('list_agc_skill_catalog');
|
||||
});
|
||||
} finally {
|
||||
delete window.__TAURI__;
|
||||
}
|
||||
});
|
||||
|
||||
test('运行画面引用的判别指纹带上了绑定素材、版本、元素角色与尺寸', () => {
|
||||
const base: RuntimeRegionReference = {
|
||||
type: 'runtime-region',
|
||||
|
||||
Reference in New Issue
Block a user