Migrate agent control reads and writes to REST

This commit is contained in:
2026-03-24 16:30:36 +08:00
parent 8d6c3c5647
commit da6d642aaa
5 changed files with 614 additions and 79 deletions

View File

@@ -1,5 +1,18 @@
import { useCallback } from 'react';
import { uploadAgentSkillZip } from '../services/runtimeApi';
import {
createAgentLocalSkill,
deleteAgentLocalSkill,
disableAgentSkill,
enableAgentSkill,
fetchAgentProfile,
fetchAgentSkillDetail,
fetchAgentSkills,
fetchAgentWorkspaceFile,
fetchCurrentRuntime,
updateAgentLocalSkill,
updateAgentWorkspaceFile,
uploadAgentSkillZip
} from '../services/runtimeApi';
import { useAgentStore } from '../store/agentStore';
/**
@@ -10,12 +23,16 @@ export function useAgentDataRequests(clientRef) {
const {
selectedSkillAgentId,
setSelectedSkillAgentId,
setAgentProfilesByAgent,
setIsAgentSkillsLoading,
setAgentSkillsFeedback,
setAgentSkillsSavingKey,
setSkillDetailLoadingKey,
setAgentSkillsByAgent,
setSkillDetailsByName,
localSkillDraftsByKey,
selectedWorkspaceFile,
setWorkspaceFilesByAgent,
setWorkspaceDraftContent,
workspaceDraftContent,
setWorkspaceFileFeedback,
@@ -23,27 +40,88 @@ export function useAgentDataRequests(clientRef) {
setIsWorkspaceFileLoading
} = useAgentStore();
const resolveWorkspaceId = useCallback(async () => {
const runtime = await fetchCurrentRuntime();
const workspaceId = runtime?.run_id;
if (!workspaceId) {
throw new Error('未检测到正在运行的任务');
}
return workspaceId;
}, []);
const requestAgentSkills = useCallback((agentId) => {
const normalized = typeof agentId === 'string' ? agentId.trim() : '';
if (!normalized || !clientRef.current) return false;
if (!normalized) return false;
setIsAgentSkillsLoading(true);
setAgentSkillsFeedback(null);
return clientRef.current.send({ type: 'get_agent_skills', agent_id: normalized });
}, [clientRef, setIsAgentSkillsLoading, setAgentSkillsFeedback]);
void resolveWorkspaceId()
.then((workspaceId) => fetchAgentSkills(workspaceId, normalized))
.then((payload) => {
setAgentSkillsByAgent((prev) => ({ ...prev, [normalized]: Array.isArray(payload?.skills) ? payload.skills : [] }));
setIsAgentSkillsLoading(false);
})
.catch(() => {
if (!clientRef.current) {
setIsAgentSkillsLoading(false);
return;
}
console.debug('REST agent skills request failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'get_agent_skills', agent_id: normalized });
if (!success) {
setIsAgentSkillsLoading(false);
}
});
return true;
}, [clientRef, resolveWorkspaceId, setAgentSkillsByAgent, setIsAgentSkillsLoading, setAgentSkillsFeedback]);
const requestAgentProfile = useCallback((agentId) => {
const normalized = typeof agentId === 'string' ? agentId.trim() : '';
if (!normalized || !clientRef.current) return false;
return clientRef.current.send({ type: 'get_agent_profile', agent_id: normalized });
}, [clientRef]);
if (!normalized) return false;
void resolveWorkspaceId()
.then((workspaceId) => fetchAgentProfile(workspaceId, normalized))
.then((payload) => {
setAgentProfilesByAgent((prev) => ({
...prev,
[normalized]: payload?.profile && typeof payload.profile === 'object' ? payload.profile : {}
}));
})
.catch(() => {
if (clientRef.current) {
console.debug('REST agent profile request failed, falling back to websocket compatibility path');
clientRef.current.send({ type: 'get_agent_profile', agent_id: normalized });
}
});
return true;
}, [clientRef, resolveWorkspaceId, setAgentProfilesByAgent]);
const requestSkillDetail = useCallback((skillName) => {
const normalized = typeof skillName === 'string' ? skillName.trim() : '';
if (!normalized || !clientRef.current) return false;
if (!normalized) return false;
const detailKey = `${selectedSkillAgentId}:${normalized}`;
setSkillDetailLoadingKey(detailKey);
return clientRef.current.send({ type: 'get_skill_detail', agent_id: selectedSkillAgentId, skill_name: normalized });
}, [clientRef, selectedSkillAgentId, setSkillDetailLoadingKey]);
void resolveWorkspaceId()
.then((workspaceId) => fetchAgentSkillDetail(workspaceId, selectedSkillAgentId, normalized))
.then((payload) => {
setSkillDetailsByName((prev) => ({ ...prev, [detailKey]: payload?.skill || null }));
useAgentStore.getState().setLocalSkillDraftsByKey((prev) => ({
...prev,
[detailKey]: typeof payload?.skill?.content === 'string' ? payload.skill.content : ''
}));
setSkillDetailLoadingKey(null);
})
.catch(() => {
if (!clientRef.current) {
setSkillDetailLoadingKey(null);
return;
}
console.debug('REST skill detail request failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'get_skill_detail', agent_id: selectedSkillAgentId, skill_name: normalized });
if (!success) {
setSkillDetailLoadingKey(null);
}
});
return true;
}, [clientRef, resolveWorkspaceId, selectedSkillAgentId, setSkillDetailLoadingKey, setSkillDetailsByName]);
const handleCreateLocalSkill = useCallback((skillName) => {
const normalized = typeof skillName === 'string' ? skillName.trim() : '';
@@ -51,18 +129,30 @@ export function useAgentDataRequests(clientRef) {
setAgentSkillsFeedback({ type: 'error', text: '技能名称不能为空' });
return;
}
if (!clientRef.current) {
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${normalized}:create`);
setAgentSkillsFeedback(null);
const success = clientRef.current.send({ type: 'create_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: normalized });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
}, [clientRef, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
void resolveWorkspaceId()
.then((workspaceId) => createAgentLocalSkill(workspaceId, selectedSkillAgentId, normalized))
.then(() => {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'success', text: `已创建本地技能 ${normalized}` });
requestAgentSkills(selectedSkillAgentId);
requestSkillDetail(normalized);
})
.catch(() => {
if (!clientRef.current) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
console.debug('REST local skill create failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'create_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: normalized });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
});
}, [clientRef, requestAgentSkills, requestSkillDetail, resolveWorkspaceId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
const handleLocalSkillDraftChange = useCallback((skillName, content) => {
const detailKey = `${selectedSkillAgentId}:${skillName}`;
@@ -70,64 +160,110 @@ export function useAgentDataRequests(clientRef) {
}, [selectedSkillAgentId]);
const handleLocalSkillSave = useCallback((skillName) => {
if (!clientRef.current) {
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
const detailKey = `${selectedSkillAgentId}:${skillName}`;
const content = localSkillDraftsByKey[detailKey];
if (typeof content !== 'string') return;
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:content`);
setAgentSkillsFeedback(null);
const success = clientRef.current.send({ type: 'update_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName, content });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
}, [clientRef, localSkillDraftsByKey, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
void resolveWorkspaceId()
.then((workspaceId) => updateAgentLocalSkill(workspaceId, selectedSkillAgentId, skillName, content))
.then(() => {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'success', text: `${selectedSkillAgentId} 的本地技能 ${skillName} 已保存` });
requestSkillDetail(skillName);
})
.catch(() => {
if (!clientRef.current) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
console.debug('REST local skill save failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'update_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName, content });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
});
}, [clientRef, localSkillDraftsByKey, requestSkillDetail, resolveWorkspaceId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
const handleLocalSkillDelete = useCallback((skillName) => {
if (!clientRef.current) {
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:delete`);
setAgentSkillsFeedback(null);
const success = clientRef.current.send({ type: 'delete_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
}, [clientRef, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
void resolveWorkspaceId()
.then((workspaceId) => deleteAgentLocalSkill(workspaceId, selectedSkillAgentId, skillName))
.then(() => {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'success', text: `${selectedSkillAgentId} 的本地技能 ${skillName} 已删除` });
requestAgentSkills(selectedSkillAgentId);
})
.catch(() => {
if (!clientRef.current) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
console.debug('REST local skill delete failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'delete_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
});
}, [clientRef, requestAgentSkills, resolveWorkspaceId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
const handleRemoveSharedSkill = useCallback((skillName) => {
if (!clientRef.current) {
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:remove`);
setAgentSkillsFeedback(null);
const success = clientRef.current.send({ type: 'remove_agent_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
}, [clientRef, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
void resolveWorkspaceId()
.then((workspaceId) => disableAgentSkill(workspaceId, selectedSkillAgentId, skillName))
.then(() => {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'success', text: `${selectedSkillAgentId} 已移除共享技能 ${skillName}` });
requestAgentSkills(selectedSkillAgentId);
})
.catch(() => {
if (!clientRef.current) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
console.debug('REST shared skill remove failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'remove_agent_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
});
}, [clientRef, requestAgentSkills, resolveWorkspaceId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
const handleAgentSkillToggle = useCallback((skillName, enabled) => {
if (!clientRef.current) {
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
const agentId = selectedSkillAgentId;
setAgentSkillsSavingKey(`${agentId}:${skillName}`);
setAgentSkillsFeedback(null);
const success = clientRef.current.send({ type: 'update_agent_skill', agent_id: agentId, skill_name: skillName, enabled });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
}, [clientRef, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
void resolveWorkspaceId()
.then((workspaceId) => enabled
? enableAgentSkill(workspaceId, agentId, skillName)
: disableAgentSkill(workspaceId, agentId, skillName))
.then(() => {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'success', text: `${agentId} ${enabled ? '已启用' : '已禁用'} ${skillName}` });
requestAgentSkills(agentId);
})
.catch(() => {
if (!clientRef.current) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
console.debug('REST skill toggle failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'update_agent_skill', agent_id: agentId, skill_name: skillName, enabled });
if (!success) {
setAgentSkillsSavingKey(null);
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
});
}, [clientRef, requestAgentSkills, resolveWorkspaceId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
const handleSkillAgentChange = useCallback((agentId) => {
setSelectedSkillAgentId(agentId);
@@ -139,11 +275,35 @@ export function useAgentDataRequests(clientRef) {
const requestWorkspaceFile = useCallback((agentId, filename) => {
const normalizedAgentId = typeof agentId === 'string' ? agentId.trim() : '';
const normalizedFilename = typeof filename === 'string' ? filename.trim() : '';
if (!normalizedAgentId || !normalizedFilename || !clientRef.current) return false;
if (!normalizedAgentId || !normalizedFilename) return false;
setIsWorkspaceFileLoading(true);
setWorkspaceFileFeedback(null);
return clientRef.current.send({ type: 'get_agent_workspace_file', agent_id: normalizedAgentId, filename: normalizedFilename });
}, [clientRef, setIsWorkspaceFileLoading, setWorkspaceFileFeedback]);
void resolveWorkspaceId()
.then((workspaceId) => fetchAgentWorkspaceFile(workspaceId, normalizedAgentId, normalizedFilename))
.then((payload) => {
setWorkspaceFilesByAgent((prev) => ({
...prev,
[normalizedAgentId]: {
...(prev[normalizedAgentId] || {}),
[normalizedFilename]: typeof payload?.content === 'string' ? payload.content : ''
}
}));
setWorkspaceDraftContent(typeof payload?.content === 'string' ? payload.content : '');
setIsWorkspaceFileLoading(false);
})
.catch(() => {
if (!clientRef.current) {
setIsWorkspaceFileLoading(false);
return;
}
console.debug('REST workspace file read failed, falling back to websocket compatibility path');
const success = clientRef.current.send({ type: 'get_agent_workspace_file', agent_id: normalizedAgentId, filename: normalizedFilename });
if (!success) {
setIsWorkspaceFileLoading(false);
}
});
return true;
}, [clientRef, resolveWorkspaceId, setIsWorkspaceFileLoading, setWorkspaceDraftContent, setWorkspaceFileFeedback, setWorkspaceFilesByAgent]);
const handleWorkspaceFileChange = useCallback((filename) => {
useAgentStore.getState().setSelectedWorkspaceFile(filename);
@@ -151,24 +311,41 @@ export function useAgentDataRequests(clientRef) {
}, [requestWorkspaceFile, selectedSkillAgentId]);
const handleWorkspaceFileSave = useCallback(() => {
if (!clientRef.current) {
setWorkspaceFileFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
const key = `${selectedSkillAgentId}:${selectedWorkspaceFile}`;
setWorkspaceFileSavingKey(key);
setWorkspaceFileFeedback(null);
const success = clientRef.current.send({
type: 'update_agent_workspace_file',
agent_id: selectedSkillAgentId,
filename: selectedWorkspaceFile,
content: workspaceDraftContent
});
if (!success) {
setWorkspaceFileSavingKey(null);
setWorkspaceFileFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
}, [clientRef, selectedSkillAgentId, selectedWorkspaceFile, setWorkspaceFileFeedback, setWorkspaceFileSavingKey, workspaceDraftContent]);
void resolveWorkspaceId()
.then((workspaceId) => updateAgentWorkspaceFile(workspaceId, selectedSkillAgentId, selectedWorkspaceFile, workspaceDraftContent))
.then((payload) => {
setWorkspaceFileSavingKey(null);
setWorkspaceFileFeedback({ type: 'success', text: `${selectedSkillAgentId}${selectedWorkspaceFile} 已保存` });
setWorkspaceFilesByAgent((prev) => ({
...prev,
[selectedSkillAgentId]: {
...(prev[selectedSkillAgentId] || {}),
[selectedWorkspaceFile]: typeof payload?.content === 'string' ? payload.content : workspaceDraftContent
}
}));
})
.catch(() => {
if (!clientRef.current) {
setWorkspaceFileSavingKey(null);
setWorkspaceFileFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
return;
}
console.debug('REST workspace file save failed, falling back to websocket compatibility path');
const success = clientRef.current.send({
type: 'update_agent_workspace_file',
agent_id: selectedSkillAgentId,
filename: selectedWorkspaceFile,
content: workspaceDraftContent
});
if (!success) {
setWorkspaceFileSavingKey(null);
setWorkspaceFileFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
}
});
}, [clientRef, resolveWorkspaceId, selectedSkillAgentId, selectedWorkspaceFile, setWorkspaceFileFeedback, setWorkspaceFileSavingKey, setWorkspaceFilesByAgent, workspaceDraftContent]);
const handleUploadExternalSkill = useCallback(async (file) => {
if (!(file instanceof File)) {

View File

@@ -129,6 +129,69 @@ export function fetchRuntimeLogs() {
return safeFetch(RUNTIME_API_BASE, '/logs');
}
export function fetchAgentProfile(workspaceId, agentId) {
return safeFetch(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/profile`);
}
export function fetchAgentSkills(workspaceId, agentId) {
return safeFetch(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/skills`);
}
export function fetchAgentSkillDetail(workspaceId, agentId, skillName) {
return safeFetch(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(skillName)}`);
}
export function fetchAgentWorkspaceFile(workspaceId, agentId, filename) {
return safeFetch(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/files/${encodeURIComponent(filename)}`);
}
export function createAgentLocalSkill(workspaceId, agentId, skillName) {
return safeRequest(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/skills/local`, {
method: 'POST',
body: JSON.stringify({ skill_name: skillName })
});
}
export function updateAgentLocalSkill(workspaceId, agentId, skillName, content) {
return safeRequest(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/skills/local/${encodeURIComponent(skillName)}`, {
method: 'PUT',
body: JSON.stringify({ content })
});
}
export function deleteAgentLocalSkill(workspaceId, agentId, skillName) {
return safeRequest(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/skills/local/${encodeURIComponent(skillName)}`, {
method: 'DELETE'
});
}
export function enableAgentSkill(workspaceId, agentId, skillName) {
return safeRequest(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(skillName)}/enable`, {
method: 'POST'
});
}
export function disableAgentSkill(workspaceId, agentId, skillName) {
return safeRequest(CONTROL_API_BASE, `/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(skillName)}/disable`, {
method: 'POST'
});
}
export function updateAgentWorkspaceFile(workspaceId, agentId, filename, content) {
return fetch(`${CONTROL_API_BASE}/workspaces/${encodeURIComponent(workspaceId)}/agents/${encodeURIComponent(agentId)}/files/${encodeURIComponent(filename)}`, {
method: 'PUT',
headers: {
'Content-Type': 'text/plain'
},
body: content
}).then(async (response) => {
if (!response.ok) {
throw new Error(await response.text());
}
return response.json();
});
}
export async function uploadAgentSkillZip({
agentId,
file,