Skip to content

Commit cd3f8b4

Browse files
committed
feat(skills): store skills in a tenant catalog and install per sandbox
A skill is a workspace definition, not a sandbox setting. Keep one catalog row and run a full image install for each backend, so agents only enable skills that are already ready on the sandbox they use.
1 parent f79052f commit cd3f8b4

34 files changed

Lines changed: 3817 additions & 296 deletions

frontend/src/api/skill/index.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,87 @@
1-
import { get } from "../../utils/request";
1+
import { del, get, post, postUpload } from "../../utils/request";
2+
import type { ConfigSkillFileContent, ConfigSkillFileEntry } from "../system";
23

34
// Skill信息
45
export interface SkillInfo {
56
name: string;
67
description: string;
78
}
89

10+
export interface SkillCatalogInstall {
11+
skill_id: string;
12+
sandbox_config_id: string;
13+
sandbox_config_name?: string;
14+
sandbox_type?: string;
15+
status: string;
16+
enabled: boolean;
17+
error?: string;
18+
updated_at: string;
19+
}
20+
21+
export interface SkillCatalogItem {
22+
id: string;
23+
name: string;
24+
version?: string;
25+
description?: string;
26+
bundle_sha256?: string;
27+
created_at: string;
28+
updated_at: string;
29+
installations: SkillCatalogInstall[];
30+
}
31+
32+
export interface SkillCatalogRegisterResult {
33+
id: string;
34+
name: string;
35+
version?: string;
36+
description?: string;
37+
}
38+
939
// 获取当前沙箱配置上可执行的 Skills;未传 sandboxConfigId 或
1040
// skills_available 为 false 时,前端应隐藏/禁用 Skills 配置
1141
export function listSkills(sandboxConfigId?: string) {
1242
return get<{ data: SkillInfo[]; skills_available?: boolean }>('/api/v1/skills', {
1343
params: sandboxConfigId ? { sandbox_config_id: sandboxConfigId } : {},
1444
});
1545
}
46+
47+
export function listSkillCatalog() {
48+
return get<{ data: SkillCatalogItem[] }>('/api/v1/skills/catalog');
49+
}
50+
51+
export function registerSkillCatalogFromSource(source: string) {
52+
return post<{ data: SkillCatalogRegisterResult }>('/api/v1/skills/catalog', { source }, {
53+
timeout: 2 * 60 * 1000,
54+
});
55+
}
56+
57+
export function registerSkillCatalogFromFile(
58+
file: File,
59+
onProgress?: (percent: number) => void,
60+
) {
61+
const form = new FormData();
62+
form.append('file', file);
63+
return postUpload('/api/v1/skills/catalog', form, (e: any) => {
64+
if (e.total) onProgress?.(Math.round((e.loaded * 100) / e.total));
65+
}, { timeout: 5 * 60 * 1000 }) as Promise<{ data: SkillCatalogRegisterResult }>;
66+
}
67+
68+
export function installSkillCatalog(catalogId: string, sandboxConfigIds: string[]) {
69+
return post<{ data: { installs: Record<string, string> } }>(
70+
`/api/v1/skills/catalog/${catalogId}/install`,
71+
{ sandbox_config_ids: sandboxConfigIds },
72+
);
73+
}
74+
75+
export function deleteSkillCatalog(catalogId: string) {
76+
return del(`/api/v1/skills/catalog/${catalogId}`);
77+
}
78+
79+
export function listCatalogSkillFiles(catalogId: string) {
80+
return get<{ data: ConfigSkillFileEntry[] }>(`/api/v1/skills/catalog/${catalogId}/files`);
81+
}
82+
83+
export function getCatalogSkillFile(catalogId: string, path: string) {
84+
return get<{ data: ConfigSkillFileContent }>(`/api/v1/skills/catalog/${catalogId}/files/content`, {
85+
params: { path },
86+
});
87+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import assert from 'node:assert/strict'
2+
import { readFileSync } from 'node:fs'
3+
import test from 'node:test'
4+
5+
const source = readFileSync(new URL('./SandboxSkillsPanel.vue', import.meta.url), 'utf8')
6+
const manageBlock = source.slice(
7+
source.indexOf('mode === \'list\' && focusSkillId'),
8+
source.indexOf('v-else-if="mode === \'list\'"'),
9+
)
10+
11+
test('focused skill management expands env vars and transcript without a mid-page save button', () => {
12+
assert.match(manageBlock, /skillHasDeclaredEnvs\(managedSkill\)/)
13+
assert.match(manageBlock, /onEnvFieldBlur/)
14+
assert.match(manageBlock, /SkillInstallTimeline/)
15+
assert.match(manageBlock, /settings\.skills\.manageUninstall/)
16+
assert.match(manageBlock, /skill-manage__progress/)
17+
assert.match(manageBlock, /v-if="!isBusy\(managedSkill\)"/)
18+
assert.doesNotMatch(manageBlock, /skill-manage__footer/)
19+
assert.doesNotMatch(manageBlock, /settings\.sandbox\.skillEnv\.save/)
20+
})

0 commit comments

Comments
 (0)