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:
@@ -5,7 +5,7 @@ import { AGENTS, INITIAL_TICKERS } from './config/constants';
|
||||
|
||||
// Services
|
||||
import { ReadOnlyClient } from './services/websocket';
|
||||
import { startRuntime } from './services/runtimeApi';
|
||||
import { startRuntime, uploadAgentSkillZip } from './services/runtimeApi';
|
||||
|
||||
// Hooks
|
||||
import { useFeedProcessor } from './hooks/useFeedProcessor';
|
||||
@@ -98,6 +98,8 @@ export default function LiveTradingApp() {
|
||||
const [ohlcHistoryByTicker, setOhlcHistoryByTicker] = useState({});
|
||||
const [explainEventsByTicker, setExplainEventsByTicker] = useState({});
|
||||
const [newsByTicker, setNewsByTicker] = useState({});
|
||||
const [insiderTradesByTicker, setInsiderTradesByTicker] = useState({});
|
||||
const [technicalIndicatorsByTicker, setTechnicalIndicatorsByTicker] = useState({});
|
||||
const [selectedExplainSymbol, setSelectedExplainSymbol] = useState('');
|
||||
const [historySourceByTicker, setHistorySourceByTicker] = useState({});
|
||||
|
||||
@@ -127,6 +129,11 @@ export default function LiveTradingApp() {
|
||||
const [initialCashDraft, setInitialCashDraft] = useState('100000');
|
||||
const [marginRequirementDraft, setMarginRequirementDraft] = useState('0');
|
||||
const [enableMemoryDraft, setEnableMemoryDraft] = useState(false);
|
||||
const [modeDraft, setModeDraft] = useState('live');
|
||||
const [pollIntervalDraft, setPollIntervalDraft] = useState('10');
|
||||
const [startDateDraft, setStartDateDraft] = useState('');
|
||||
const [endDateDraft, setEndDateDraft] = useState('');
|
||||
const [enableMockDraft, setEnableMockDraft] = useState(false);
|
||||
const [runtimeConfigFeedback, setRuntimeConfigFeedback] = useState(null);
|
||||
const [isRuntimeConfigSaving, setIsRuntimeConfigSaving] = useState(false);
|
||||
const [selectedSkillAgentId, setSelectedSkillAgentId] = useState(AGENTS[0]?.id || 'portfolio_manager');
|
||||
@@ -602,7 +609,11 @@ export default function LiveTradingApp() {
|
||||
initial_cash: initialCash,
|
||||
margin_requirement: marginRequirement,
|
||||
enable_memory: Boolean(enableMemoryDraft),
|
||||
mode: serverMode || 'live'
|
||||
mode: modeDraft || 'live',
|
||||
poll_interval: Number(pollIntervalDraft) || 10,
|
||||
start_date: startDateDraft || null,
|
||||
end_date: endDateDraft || null,
|
||||
enable_mock: Boolean(enableMockDraft)
|
||||
});
|
||||
|
||||
setIsRuntimeConfigSaving(false);
|
||||
@@ -630,9 +641,13 @@ export default function LiveTradingApp() {
|
||||
initialCashDraft,
|
||||
marginRequirementDraft,
|
||||
enableMemoryDraft,
|
||||
modeDraft,
|
||||
pollIntervalDraft,
|
||||
startDateDraft,
|
||||
endDateDraft,
|
||||
enableMockDraft,
|
||||
watchlistDraftSymbols,
|
||||
watchlistInputValue,
|
||||
serverMode,
|
||||
addSystemMessage
|
||||
]);
|
||||
|
||||
@@ -644,6 +659,11 @@ export default function LiveTradingApp() {
|
||||
setInitialCashDraft('100000');
|
||||
setMarginRequirementDraft('0');
|
||||
setEnableMemoryDraft(false);
|
||||
setModeDraft('live');
|
||||
setPollIntervalDraft('10');
|
||||
setStartDateDraft('');
|
||||
setEndDateDraft('');
|
||||
setEnableMockDraft(false);
|
||||
setRuntimeConfigFeedback(null);
|
||||
}, []);
|
||||
|
||||
@@ -862,6 +882,38 @@ export default function LiveTradingApp() {
|
||||
}
|
||||
}, [selectedSkillAgentId, selectedWorkspaceFile, workspaceDraftContent]);
|
||||
|
||||
const handleUploadExternalSkill = useCallback(async (file) => {
|
||||
if (!(file instanceof File)) {
|
||||
setAgentSkillsFeedback({ type: 'error', text: '请选择 zip 文件后再上传' });
|
||||
return;
|
||||
}
|
||||
if (!selectedSkillAgentId) {
|
||||
setAgentSkillsFeedback({ type: 'error', text: '未选择目标 Agent' });
|
||||
return;
|
||||
}
|
||||
setAgentSkillsSavingKey(`${selectedSkillAgentId}:__upload__`);
|
||||
setAgentSkillsFeedback(null);
|
||||
try {
|
||||
const result = await uploadAgentSkillZip({
|
||||
agentId: selectedSkillAgentId,
|
||||
file,
|
||||
activate: true
|
||||
});
|
||||
setAgentSkillsFeedback({
|
||||
type: 'success',
|
||||
text: `已上传并安装技能 ${result.skill_name || ''}`.trim()
|
||||
});
|
||||
requestAgentSkills(selectedSkillAgentId);
|
||||
} catch (error) {
|
||||
setAgentSkillsFeedback({
|
||||
type: 'error',
|
||||
text: `上传失败: ${error.message || '未知错误'}`
|
||||
});
|
||||
} finally {
|
||||
setAgentSkillsSavingKey(null);
|
||||
}
|
||||
}, [requestAgentSkills, selectedSkillAgentId]);
|
||||
|
||||
useEffect(() => {
|
||||
setWorkspaceDraftContent(selectedWorkspaceContent);
|
||||
}, [selectedWorkspaceContent]);
|
||||
@@ -967,6 +1019,31 @@ export default function LiveTradingApp() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const requestStockInsiderTrades = useCallback((symbol, startDate = null, endDate = null) => {
|
||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||
if (!normalized || !clientRef.current) {
|
||||
return false;
|
||||
}
|
||||
return clientRef.current.send({
|
||||
type: 'get_stock_insider_trades',
|
||||
ticker: normalized,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
limit: 50
|
||||
});
|
||||
}, []);
|
||||
|
||||
const requestStockTechnicalIndicators = useCallback((symbol) => {
|
||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||
if (!normalized || !clientRef.current) {
|
||||
return false;
|
||||
}
|
||||
return clientRef.current.send({
|
||||
type: 'get_stock_technical_indicators',
|
||||
ticker: normalized
|
||||
});
|
||||
}, []);
|
||||
|
||||
const requestStockRangeExplain = useCallback((symbol, startDate, endDate, articleIds = []) => {
|
||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||
if (!normalized || !startDate || !endDate || !clientRef.current) {
|
||||
@@ -1050,13 +1127,15 @@ export default function LiveTradingApp() {
|
||||
}, [isLiveEnabled, chartTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWatchlistPanelOpen || !isWatchlistDraftDirty) {
|
||||
// Only reset when watchlist panel is closed AND runtime settings is also closed
|
||||
// This prevents reset when user is editing in RuntimeSettingsPanel
|
||||
if ((!isWatchlistPanelOpen && !isRuntimeSettingsOpen) || !isWatchlistDraftDirty) {
|
||||
setWatchlistDraftSymbols(runtimeWatchlistSymbols);
|
||||
if (!isWatchlistPanelOpen) {
|
||||
if (!isWatchlistPanelOpen && !isRuntimeSettingsOpen) {
|
||||
setWatchlistInputValue('');
|
||||
}
|
||||
}
|
||||
}, [isWatchlistDraftDirty, isWatchlistPanelOpen, runtimeWatchlistSymbols]);
|
||||
}, [isWatchlistDraftDirty, isWatchlistPanelOpen, isRuntimeSettingsOpen, runtimeWatchlistSymbols]);
|
||||
|
||||
useEffect(() => {
|
||||
isWatchlistSavingRef.current = isWatchlistSaving;
|
||||
@@ -1084,6 +1163,8 @@ export default function LiveTradingApp() {
|
||||
requestStockNews,
|
||||
requestStockNewsCategories,
|
||||
requestStockNewsTimeline,
|
||||
requestStockInsiderTrades,
|
||||
requestStockTechnicalIndicators,
|
||||
requestStockStory,
|
||||
selectedExplainSymbol
|
||||
]);
|
||||
@@ -1682,6 +1763,32 @@ export default function LiveTradingApp() {
|
||||
}));
|
||||
},
|
||||
|
||||
stock_insider_trades_loaded: (e) => {
|
||||
const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
|
||||
if (!symbol) {
|
||||
return;
|
||||
}
|
||||
setInsiderTradesByTicker((prev) => ({
|
||||
...prev,
|
||||
[symbol]: {
|
||||
trades: Array.isArray(e.trades) ? e.trades : [],
|
||||
startDate: e.start_date || null,
|
||||
endDate: e.end_date || null
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
stock_technical_indicators_loaded: (e) => {
|
||||
const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
|
||||
if (!symbol) {
|
||||
return;
|
||||
}
|
||||
setTechnicalIndicatorsByTicker((prev) => ({
|
||||
...prev,
|
||||
[symbol]: e.indicators || null
|
||||
}));
|
||||
},
|
||||
|
||||
stock_range_explain_loaded: (e) => {
|
||||
const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
|
||||
if (!symbol) {
|
||||
@@ -2388,6 +2495,11 @@ export default function LiveTradingApp() {
|
||||
initialCash={initialCashDraft}
|
||||
marginRequirement={marginRequirementDraft}
|
||||
enableMemory={enableMemoryDraft}
|
||||
mode={modeDraft}
|
||||
pollInterval={pollIntervalDraft}
|
||||
startDate={startDateDraft}
|
||||
endDate={endDateDraft}
|
||||
enableMock={enableMockDraft}
|
||||
watchlistSymbols={watchlistDraftSymbols}
|
||||
watchlistInputValue={watchlistInputValue}
|
||||
watchlistSuggestions={watchlistSuggestions}
|
||||
@@ -2400,6 +2512,11 @@ export default function LiveTradingApp() {
|
||||
onInitialCashChange={setInitialCashDraft}
|
||||
onMarginRequirementChange={setMarginRequirementDraft}
|
||||
onEnableMemoryChange={setEnableMemoryDraft}
|
||||
onModeChange={setModeDraft}
|
||||
onPollIntervalChange={setPollIntervalDraft}
|
||||
onStartDateChange={setStartDateDraft}
|
||||
onEndDateChange={setEndDateDraft}
|
||||
onEnableMockChange={setEnableMockDraft}
|
||||
onWatchlistInputChange={handleWatchlistInputChange}
|
||||
onWatchlistInputKeyDown={handleWatchlistInputKeyDown}
|
||||
onWatchlistAdd={() => commitWatchlistInput(watchlistInputValue)}
|
||||
@@ -2539,6 +2656,7 @@ export default function LiveTradingApp() {
|
||||
onWorkspaceFileChange={handleWorkspaceFileChange}
|
||||
onWorkspaceDraftChange={setWorkspaceDraftContent}
|
||||
onWorkspaceFileSave={handleWorkspaceFileSave}
|
||||
onUploadExternalSkill={handleUploadExternalSkill}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
@@ -2573,9 +2691,13 @@ export default function LiveTradingApp() {
|
||||
selectedHistorySource={historySourceByTicker[selectedExplainSymbol] || null}
|
||||
explainEventsSnapshot={explainEventsByTicker[selectedExplainSymbol] || null}
|
||||
newsSnapshot={newsByTicker[selectedExplainSymbol] || null}
|
||||
insiderTradesSnapshot={insiderTradesByTicker[selectedExplainSymbol] || null}
|
||||
technicalIndicatorsSnapshot={technicalIndicatorsByTicker[selectedExplainSymbol] || null}
|
||||
onRequestRangeExplain={requestStockRangeExplain}
|
||||
onRequestNewsForDate={requestStockNewsForDate}
|
||||
onRequestStory={requestStockStory}
|
||||
onRequestInsiderTrades={requestStockInsiderTrades}
|
||||
onRequestTechnicalIndicators={requestStockTechnicalIndicators}
|
||||
currentDate={currentDate}
|
||||
onRequestSimilarDays={requestStockSimilarDays}
|
||||
onRequestStockEnrich={requestStockEnrich}
|
||||
|
||||
Reference in New Issue
Block a user