feat: Add evaluation hooks, skill adaptation and team pipeline config
- Add EvaluationHook for post-execution agent evaluation - Add SkillAdaptationHook for dynamic skill adaptation - Add team/ directory with team coordination logic - Add TEAM_PIPELINE.yaml for smoke_fullstack pipeline config - Update RuntimeView, TraderView and RuntimeSettingsPanel UI - Add runtimeApi and websocket services - Add runtime_state.json to smoke_fullstack state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import JSZip from 'jszip';
|
||||
import { getModelIcon, getShortModelName } from '../utils/modelIcons';
|
||||
|
||||
export default function TraderView({
|
||||
@@ -34,10 +35,14 @@ export default function TraderView({
|
||||
onSkillToggle,
|
||||
onWorkspaceFileChange,
|
||||
onWorkspaceDraftChange,
|
||||
onWorkspaceFileSave
|
||||
onWorkspaceFileSave,
|
||||
onUploadExternalSkill
|
||||
}) {
|
||||
const [expandedSkillKey, setExpandedSkillKey] = useState(null);
|
||||
const [newLocalSkillName, setNewLocalSkillName] = useState('');
|
||||
const [externalSkillFile, setExternalSkillFile] = useState(null);
|
||||
const [isExternalSkillChecking, setIsExternalSkillChecking] = useState(false);
|
||||
const [externalSkillCheck, setExternalSkillCheck] = useState({ type: null, text: '' });
|
||||
const [isSkillPickerOpen, setIsSkillPickerOpen] = useState(false);
|
||||
|
||||
const selectedAgent = useMemo(
|
||||
@@ -59,6 +64,50 @@ export default function TraderView({
|
||||
const installedSkills = selectedAgentSkills.filter((item) => item.status !== 'available');
|
||||
const availableSkills = selectedAgentSkills.filter((item) => item.status === 'available');
|
||||
|
||||
const validateExternalSkillZip = async (file) => {
|
||||
if (!(file instanceof File)) {
|
||||
setExternalSkillCheck({ type: 'error', text: '请选择 zip 文件' });
|
||||
return false;
|
||||
}
|
||||
if (!file.name.toLowerCase().endsWith('.zip')) {
|
||||
setExternalSkillCheck({ type: 'error', text: '仅支持 .zip 文件' });
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsExternalSkillChecking(true);
|
||||
setExternalSkillCheck({ type: null, text: '' });
|
||||
try {
|
||||
const zip = await JSZip.loadAsync(file);
|
||||
const entries = Object.keys(zip.files);
|
||||
const skillFilePath = entries.find((entry) => {
|
||||
const item = zip.files[entry];
|
||||
return !item.dir && /(^|\/)SKILL\.md$/i.test(entry);
|
||||
});
|
||||
|
||||
if (!skillFilePath) {
|
||||
setExternalSkillCheck({
|
||||
type: 'error',
|
||||
text: '压缩包中未检测到 SKILL.md,请检查目录结构'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
setExternalSkillCheck({
|
||||
type: 'success',
|
||||
text: `预检通过,检测到: ${skillFilePath}`
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
setExternalSkillCheck({
|
||||
type: 'error',
|
||||
text: `无法解析 zip: ${error?.message || '未知错误'}`
|
||||
});
|
||||
return false;
|
||||
} finally {
|
||||
setIsExternalSkillChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
height: '100%',
|
||||
@@ -679,6 +728,85 @@ export default function TraderView({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
border: '1px solid #E5EAF1',
|
||||
borderRadius: 12,
|
||||
background: '#FCFDFE',
|
||||
padding: 14,
|
||||
display: 'grid',
|
||||
gap: 10
|
||||
}}>
|
||||
<div style={{ fontSize: 12, fontWeight: 800, color: '#111111' }}>上传外部技能包</div>
|
||||
<div style={{ fontSize: 11, color: '#6B7280' }}>
|
||||
支持上传 .zip(包内需包含一个技能目录及 SKILL.md)
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<input
|
||||
type="file"
|
||||
accept=".zip,application/zip"
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0] || null;
|
||||
setExternalSkillFile(file);
|
||||
if (!file) {
|
||||
setExternalSkillCheck({ type: null, text: '' });
|
||||
return;
|
||||
}
|
||||
await validateExternalSkillZip(file);
|
||||
}}
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 220,
|
||||
padding: '6px 8px',
|
||||
borderRadius: 8,
|
||||
border: '1px solid #D0D7DE',
|
||||
background: '#FFFFFF',
|
||||
color: '#111111',
|
||||
fontSize: 11
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (!onUploadExternalSkill || !externalSkillFile) {
|
||||
return;
|
||||
}
|
||||
const valid = await validateExternalSkillZip(externalSkillFile);
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
await onUploadExternalSkill(externalSkillFile);
|
||||
setExternalSkillFile(null);
|
||||
setExternalSkillCheck({ type: null, text: '' });
|
||||
}}
|
||||
disabled={!isConnected || !externalSkillFile || isExternalSkillChecking || externalSkillCheck.type === 'error'}
|
||||
style={{
|
||||
padding: '8px 12px',
|
||||
borderRadius: 8,
|
||||
border: '1px solid #1565C0',
|
||||
background: isConnected && externalSkillFile && !isExternalSkillChecking && externalSkillCheck.type !== 'error' ? '#EFF6FF' : '#E5E7EB',
|
||||
color: '#1565C0',
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
cursor: isConnected && externalSkillFile && !isExternalSkillChecking && externalSkillCheck.type !== 'error' ? 'pointer' : 'not-allowed',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
{isExternalSkillChecking ? '预检中...' : '上传并安装'}
|
||||
</button>
|
||||
</div>
|
||||
{externalSkillCheck.text ? (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: externalSkillCheck.type === 'success' ? '#00C853' : '#FF5252',
|
||||
fontFamily: '"Courier New", monospace'
|
||||
}}
|
||||
>
|
||||
{externalSkillCheck.text}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
border: '1px solid #E5EAF1',
|
||||
borderRadius: 12,
|
||||
|
||||
Reference in New Issue
Block a user