agent-Specialization/static/src/stores/subAgent.ts
JOJO ae70e4902e refactor(stop-button): 拆分停止按钮和后台任务控制
停止按钮现在只停主智能体,与后台任务无关。
- 后端 cancel_task 简化:移除两段式判断、force_stop参数、is_ma_mode分支
- 删除 _force_stop_multi_agent 方法
- 新增 /api/sub_agents/stop_all 接口(mode=terminate/soft_stop)
- 前端 stopTask 移除 forceStop 概念,只取消主智能体
- 取消期间不再二次点击终结子智能体

子智能体停止按钮:
- InputComposer state bar 加'x个子智能体运行中'按钮
- 独立浮层显示,不依赖 git 栏(参考 askuser 方案)
- 点击传统模式弹窗终结;多智能体弹窗软停止
- App.vue 新增 handleStopAllSubAgents 调 ui.confirmDialog

后台指令停止按钮:
- LeftPanel 后台指令标签右边加停止按钮
- 仅当 panelMode=backgroundCommands 且存在运行中后台指令时显示
- 调 backgroundCommandStore.stopAllCommands 无二次确认

showStopIcon 只看主对话 streaming 状态:
- 后台任务在跑但主对话空闲时显示发送按钮
- 多智能体 / 传统模式统一处理

子智能体 store 加 stopAllAgents(mode) action 调新 API

useLegacySocket task_stopped handler 简化:
- 移除 multi_agent_reloaded 重载
- 移除'再次按下停止按钮以结束后台任务'提示
- 保留 has_running_multi_agent/sub_agents 用于 taskInProgress 判定

清理调试日志:[TaskCancel][stop_debug], [STOP_DEBUG] 全部去掉
保留 ma_debug 框架用于多智能体调试
2026-07-14 18:34:56 +08:00

223 lines
6.9 KiB
TypeScript

import { defineStore } from 'pinia';
import { useUiStore } from './ui';
interface SubAgent {
task_id: string;
agent_id?: string | number;
status?: string;
summary?: string;
last_tool?: string;
conversation_id?: string;
notice_pending?: boolean;
display_name?: string;
current_context_tokens?: number;
}
interface SubAgentActivityEntry {
id?: string;
tool?: string;
status?: string;
args?: Record<string, any>;
ts?: number;
error?: string;
}
interface SubAgentState {
subAgents: SubAgent[];
pollTimer: ReturnType<typeof setInterval> | null;
activityTimer: ReturnType<typeof setInterval> | null;
activeAgent: SubAgent | null;
stoppingTaskIds: Record<string, boolean>;
activityEntries: SubAgentActivityEntry[];
activityLoading: boolean;
activityError: string | null;
}
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'timeout', 'terminated']);
export const useSubAgentStore = defineStore('subAgent', {
state: (): SubAgentState => ({
subAgents: [],
pollTimer: null,
activityTimer: null,
activeAgent: null,
stoppingTaskIds: {},
activityEntries: [],
activityLoading: false,
activityError: null
}),
actions: {
async fetchSubAgents() {
try {
const resp = await fetch('/api/sub_agents');
if (!resp.ok) {
throw new Error(await resp.text());
}
const data = await resp.json();
if (data.success) {
this.subAgents = Array.isArray(data.data) ? data.data : [];
const activeTaskId = this.activeAgent?.task_id;
if (activeTaskId) {
const latest = this.subAgents.find((item) => item.task_id === activeTaskId);
if (latest) {
this.activeAgent = { ...latest };
}
}
}
} catch (error) {
console.error('获取子智能体列表失败:', error);
}
},
startPolling() {
if (this.pollTimer) {
return;
}
const uiStore = useUiStore();
this.pollTimer = setInterval(() => {
if (uiStore.panelMode === 'subAgents') {
this.fetchSubAgents();
}
}, 5000);
},
stopPolling() {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
},
openSubAgent(agent: SubAgent) {
if (!agent || !agent.task_id) {
return;
}
this.activeAgent = agent;
this.activityEntries = [];
this.activityError = null;
this.fetchSubAgentActivity(agent.task_id);
this.startActivityPolling();
},
closeSubAgent() {
this.stopActivityPolling();
this.activeAgent = null;
this.activityEntries = [];
this.activityError = null;
this.activityLoading = false;
},
startActivityPolling() {
if (this.activityTimer) {
return;
}
this.activityTimer = setInterval(() => {
const taskId = this.activeAgent?.task_id;
if (taskId) {
this.fetchSubAgentActivity(taskId);
}
}, 2000);
},
stopActivityPolling() {
if (this.activityTimer) {
clearInterval(this.activityTimer);
this.activityTimer = null;
}
},
async fetchSubAgentActivity(taskId: string) {
if (!taskId) return;
this.activityLoading = true;
try {
const resp = await fetch(`/api/sub_agents/${taskId}/activity?limit=100000`);
if (!resp.ok) {
throw new Error(await resp.text());
}
const data = await resp.json();
if (data && data.success && data.data) {
const entries = Array.isArray(data.data.entries) ? data.data.entries : [];
this.activityEntries = entries;
if (this.activeAgent && this.activeAgent.task_id === taskId) {
this.activeAgent = {
...this.activeAgent,
status: data.data.status || this.activeAgent.status
};
}
const status = (data.data.status || '').toString();
if (TERMINAL_STATUSES.has(status)) {
this.stopActivityPolling();
}
}
} catch (error: any) {
this.activityError = error?.message || String(error);
console.error('获取子智能体活动失败:', error);
} finally {
this.activityLoading = false;
}
},
async terminateSubAgent(taskId: string) {
const normalizedId = (taskId || '').toString().trim();
if (!normalizedId) {
return { success: false, error: 'task_id 不能为空' };
}
this.stoppingTaskIds = {
...this.stoppingTaskIds,
[normalizedId]: true
};
try {
const resp = await fetch(`/api/sub_agents/${encodeURIComponent(normalizedId)}/terminate`, {
method: 'POST'
});
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.success) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await this.fetchSubAgents();
if (this.activeAgent?.task_id === normalizedId) {
await this.fetchSubAgentActivity(normalizedId);
this.activeAgent = {
...this.activeAgent,
status: 'terminated'
};
}
return { success: true, data: data?.data || null };
} catch (error: any) {
const message = error?.message || String(error);
return { success: false, error: message };
} finally {
const next = { ...this.stoppingTaskIds };
delete next[normalizedId];
this.stoppingTaskIds = next;
}
},
async stopAllAgents(mode: 'terminate' | 'soft_stop'): Promise<{ success: boolean; stoppedCount?: number; error?: string }> {
try {
const resp = await fetch('/api/sub_agents/stop_all', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode })
});
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.success) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await this.fetchSubAgents();
return { success: true, stoppedCount: data?.data?.stopped_count || 0 };
} catch (error: any) {
const message = error?.message || String(error);
return { success: false, error: message };
}
},
stripConversationPrefix(conversationId: string) {
if (!conversationId) return '';
return conversationId.startsWith('conv_') ? conversationId.slice(5) : conversationId;
},
getBaseUrl() {
const override = (window as any).SUB_AGENT_BASE_URL || (window as any).__SUB_AGENT_BASE_URL__;
if (override && typeof override === 'string') {
return override.replace(/\/$/, '');
}
const { protocol, hostname } = window.location;
if (hostname && hostname.includes('agent.')) {
const mappedHost = hostname.replace('agent.', 'subagent.');
return `${protocol}//${mappedHost}`;
}
return `${protocol}//${hostname}:8092`;
}
}
});