Add files via upload

This commit is contained in:
公明
2026-04-19 02:59:57 +08:00
committed by GitHub
parent 705e7601f6
commit ef3de9e950
10 changed files with 647 additions and 83 deletions
+34 -7
View File
@@ -158,6 +158,11 @@
"callNumber": "Call #{{n}}",
"iterationRound": "Iteration {{n}}",
"einoOrchestratorRound": "Orchestrator · round {{n}}",
"einoPlanExecuteRound": "Plan-Execute · round {{n}} · {{phase}}",
"planExecuteStreamPlanner": "Planning output",
"planExecuteStreamExecutor": "Execution output",
"planExecuteStreamReplanning": "Replanning output",
"planExecuteStreamPhase": "Phase output",
"einoSubAgentStep": "Sub-agent {{agent}} · step {{n}}",
"aiThinking": "AI thinking",
"planning": "Planning",
@@ -186,12 +191,22 @@
"executionFailed": "Execution failed",
"penetrationTestComplete": "Penetration test complete",
"yesterday": "Yesterday",
"agentModeSelectAria": "Choose single-agent or multi-agent",
"agentModeSelectAria": "Choose conversation execution mode",
"agentModePanelTitle": "Conversation mode",
"agentModeReactNative": "Native ReAct",
"agentModeReactNativeHint": "Classic single-agent ReAct with MCP tools",
"agentModeDeep": "Deep (DeepAgent)",
"agentModeDeepHint": "Eino DeepAgent with task delegation to sub-agents",
"agentModePlanExecuteLabel": "Plan-Execute",
"agentModePlanExecuteHint": "Plan → execute → replan (single executor with tools)",
"agentModeSupervisorLabel": "Supervisor",
"agentModeSupervisorHint": "Supervisor coordinates via transfer to sub-agents",
"agentModeSingle": "Single-agent",
"agentModeMulti": "Multi-agent",
"agentModeSingleHint": "Single-model ReAct loop for chat and tool use",
"agentModeMultiHint": "Eino DeepAgent with sub-agents for complex tasks"
"agentModeMultiHint": "Eino prebuilt orchestration (deep / plan_execute / supervisor) for complex tasks",
"agentModeOrchPlanExecute": "Plan-Exec",
"agentModeOrchSupervisor": "Supervisor"
},
"progress": {
"callingAI": "Calling AI model...",
@@ -203,7 +218,11 @@
"analyzingRequestShort": "Analyzing your request...",
"analyzingRequestPlanning": "Analyzing your request and planning test strategy...",
"startingEinoDeepAgent": "Starting Eino DeepAgent...",
"einoAgent": "Eino agent: {{name}}"
"startingEinoMultiAgent": "Starting Eino multi-agent...",
"einoAgent": "Eino agent: {{name}}",
"peAgentPlanner": "Planner",
"peAgentExecutor": "Executor",
"peAgentReplanning": "Replanner"
},
"timeline": {
"params": "Parameters:",
@@ -1236,7 +1255,7 @@
"fieldRole": "Type",
"roleSub": "Sub-agent",
"roleOrchestrator": "Orchestrator (Deep)",
"roleHint": "You can also use the fixed file name orchestrator.md. Only one orchestrator per directory. If the orchestrator body is empty, config orchestrator_instruction and Eino defaults apply.",
"roleHint": "Orchestrators are mode-specific: Deep → orchestrator.md (or one .md with kind: orchestrator); plan_execute → orchestrator-plan-execute.md; supervisor → orchestrator-supervisor.md. At most one of each. Empty body falls back to multi_agent.orchestrator_instruction / orchestrator_instruction_plan_execute / orchestrator_instruction_supervisor or built-in defaults (PE/SV do not reuse Deep orchestrator_instruction).",
"badgeOrchestrator": "Orchestrator",
"badgeSub": "Sub-agent",
"filenameInvalid": "File name must end with .md and use only letters, digits, ._-",
@@ -1282,8 +1301,16 @@
"fofaApiKeyHint": "Stored in server config (config.yaml) only.",
"maxIterations": "Max iterations",
"iterationsPlaceholder": "30",
"enableMultiAgent": "Enable Eino multi-agent (DeepAgent)",
"enableMultiAgentHint": "After enabling, the chat page can use multi-agent mode; sub-agents are configured in config.yaml under multi_agent.sub_agents.",
"enableMultiAgent": "Enable Eino multi-agent",
"enableMultiAgentHint": "After enabling, the chat page can use multi-agent mode; sub-agents are set in multi_agent.sub_agents or the agents/ directory. Orchestration is configured below.",
"multiAgentOrchestration": "Multi-agent orchestration",
"multiAgentOrchestrationHint": "deep = DeepAgent + task; plan_execute = plan / execute / replan (single executor tool loop); supervisor = supervisor + transfer. Takes effect after save & apply.",
"multiAgentOrchDeep": "deep — DeepAgent (task sub-agents)",
"multiAgentOrchPlanExecute": "plan_execute — plan / execute / replan",
"multiAgentOrchSupervisor": "supervisor — supervisor + transfer",
"multiAgentPeLoop": "plan_execute outer loop limit",
"multiAgentPeLoopPlaceholder": "0 uses Eino default (10)",
"multiAgentPeLoopHint": "Only for plan_execute; max execute↔replan rounds.",
"multiAgentDefaultMode": "Default mode on chat page",
"multiAgentModeSingle": "Single-agent (ReAct)",
"multiAgentModeMulti": "Multi-agent (Eino)",
@@ -1565,7 +1592,7 @@
"agentMode": "Agent mode",
"agentModeSingle": "Single-agent (ReAct)",
"agentModeMulti": "Multi-agent (Eino)",
"agentModeHint": "Single-agent is recommended by default; use multi-agent for complex tasks (requires system multi-agent enabled).",
"agentModeHint": "Same as chat: single-agent ReAct or Deep / Plan-Execute / Supervisor (Eino requires multi-agent enabled).",
"scheduleMode": "Schedule mode",
"scheduleModeManual": "Manual",
"scheduleModeCron": "Cron expression",
+34 -7
View File
@@ -158,6 +158,11 @@
"callNumber": "调用 #{{n}}",
"iterationRound": "第 {{n}} 轮迭代",
"einoOrchestratorRound": "主代理 · 第 {{n}} 轮",
"einoPlanExecuteRound": "Plan-Execute · 第 {{n}} 轮 · {{phase}}",
"planExecuteStreamPlanner": "规划输出",
"planExecuteStreamExecutor": "执行输出",
"planExecuteStreamReplanning": "重规划输出",
"planExecuteStreamPhase": "阶段输出",
"einoSubAgentStep": "子代理 {{agent}} · 第 {{n}} 步",
"aiThinking": "AI思考",
"planning": "规划中",
@@ -186,12 +191,22 @@
"executionFailed": "执行失败",
"penetrationTestComplete": "渗透测试完成",
"yesterday": "昨天",
"agentModeSelectAria": "选择单代理或多代理",
"agentModeSelectAria": "选择对话执行模式",
"agentModePanelTitle": "对话模式",
"agentModeReactNative": "原生 ReAct 模式",
"agentModeReactNativeHint": "经典单代理 ReAct 与 MCP 工具",
"agentModeDeep": "DeepDeepAgent",
"agentModeDeepHint": "Eino DeepAgenttask 调度子代理",
"agentModePlanExecuteLabel": "Plan-Execute",
"agentModePlanExecuteHint": "规划 → 执行 → 重规划(单执行器带工具)",
"agentModeSupervisorLabel": "Supervisor",
"agentModeSupervisorHint": "监督者协调,transfer 委派子代理",
"agentModeSingle": "单代理",
"agentModeMulti": "多代理",
"agentModeSingleHint": "单模型 ReAct 循环,适合常规对话与工具调用",
"agentModeMultiHint": "Eino DeepAgent 编排子代理,适合复杂任务"
"agentModeMultiHint": "Eino 预置编排(deep / plan_execute / supervisor,适合复杂任务",
"agentModeOrchPlanExecute": "Plan-Exec",
"agentModeOrchSupervisor": "Supervisor"
},
"progress": {
"callingAI": "正在调用AI模型...",
@@ -203,7 +218,11 @@
"analyzingRequestShort": "正在分析您的请求...",
"analyzingRequestPlanning": "开始分析请求并制定测试策略",
"startingEinoDeepAgent": "正在启动 Eino 多代理(DeepAgent...",
"einoAgent": "Eino 代理{{name}}"
"startingEinoMultiAgent": "正在启动 Eino 代理...",
"einoAgent": "Eino 代理:{{name}}",
"peAgentPlanner": "规划器",
"peAgentExecutor": "执行器",
"peAgentReplanning": "重规划"
},
"timeline": {
"params": "参数:",
@@ -1236,7 +1255,7 @@
"fieldRole": "类型",
"roleSub": "子代理",
"roleOrchestrator": "主代理(Deep 协调者)",
"roleHint": "主代理也可使用固定文件名 orchestrator.md;全目录仅允许一个主代理。主代理正文为空时沿用 config 中 orchestrator_instruction 与 Eino 默认。",
"roleHint": "主代理分模式:Deep 用 orchestrator.md(或 kind: orchestrator 的单个 .md);plan_execute 用 orchestrator-plan-execute.mdsupervisor 用 orchestrator-supervisor.md。每种至多一个。正文为空时分别回退 multi_agent.orchestrator_instruction / orchestrator_instruction_plan_execute / orchestrator_instruction_supervisor 或内置默认(PE/SV 不使用 Deep 的 orchestrator_instruction。",
"badgeOrchestrator": "主代理",
"badgeSub": "子代理",
"filenameInvalid": "文件名须为 .md,且仅含字母、数字、._-",
@@ -1282,8 +1301,16 @@
"fofaApiKeyHint": "仅保存在服务器配置中(`config.yaml`)。",
"maxIterations": "最大迭代次数",
"iterationsPlaceholder": "30",
"enableMultiAgent": "启用 Eino 多代理DeepAgent",
"enableMultiAgentHint": "开启后对话页可选「多代理」模式;子代理在 config.yaml 的 multi_agent.sub_agents 中配置。",
"enableMultiAgent": "启用 Eino 多代理",
"enableMultiAgentHint": "开启后对话页可选「多代理」模式;子代理在 multi_agent.sub_agents 或 agents 目录配置;编排方式见下方「预置编排」。",
"multiAgentOrchestration": "多代理预置编排",
"multiAgentOrchestrationHint": "deep=DeepAgent+taskplan_execute=规划/执行/重规划(单执行器工具链);supervisor=监督者+transfer。保存并应用后生效。",
"multiAgentOrchDeep": "deep — DeepAgenttask 子代理)",
"multiAgentOrchPlanExecute": "plan_execute — 规划 / 执行 / 重规划",
"multiAgentOrchSupervisor": "supervisor — 监督者 + transfer",
"multiAgentPeLoop": "plan_execute 外层循环上限",
"multiAgentPeLoopPlaceholder": "0 表示 Eino 默认 10",
"multiAgentPeLoopHint": "仅 plan_execute 有效;execute 与 replan 之间的最大轮次。",
"multiAgentDefaultMode": "对话页默认模式",
"multiAgentModeSingle": "单代理(ReAct",
"multiAgentModeMulti": "多代理(Eino",
@@ -1565,7 +1592,7 @@
"agentMode": "代理模式",
"agentModeSingle": "单代理(ReAct",
"agentModeMulti": "多代理(Eino",
"agentModeHint": "建议默认单代理;复杂任务可使用多代理(需系统已启用多代理)。",
"agentModeHint": "与对话页一致:单代理 ReAct 或 Deep / Plan-Execute / SupervisorEino 需已启用多代理)。",
"scheduleMode": "调度方式",
"scheduleModeManual": "手工执行",
"scheduleModeCron": "调度表达式(Cron",
+98 -14
View File
@@ -32,19 +32,77 @@ const CHAT_FILE_DEFAULT_PROMPT = '请根据上传的文件内容进行分析。'
let chatAttachments = [];
let chatAttachmentSeq = 0;
// 多代理(Eino):需后端 multi_agent.enabled,与单代理 /agent-loop 并存
// 对话模式:react = 原生 ReAct/agent-loop);deep / plan_execute / supervisor = Eino/api/multi-agent/stream,请求体 orchestration
const AGENT_MODE_STORAGE_KEY = 'cyberstrike-chat-agent-mode';
const CHAT_AGENT_MODE_REACT = 'react';
const CHAT_AGENT_EINO_MODES = ['deep', 'plan_execute', 'supervisor'];
let multiAgentAPIEnabled = false;
function normalizeOrchestrationClient(s) {
const v = String(s || '').trim().toLowerCase().replace(/-/g, '_');
if (v === 'plan_execute' || v === 'planexecute' || v === 'pe') return 'plan_execute';
if (v === 'supervisor' || v === 'super' || v === 'sv') return 'supervisor';
return 'deep';
}
function chatAgentModeIsEino(mode) {
return CHAT_AGENT_EINO_MODES.indexOf(mode) >= 0;
}
/** 将 localStorage / 历史值规范为 react | deep | plan_execute | supervisor */
function chatAgentModeNormalizeStored(stored, cfg) {
const pub = cfg && cfg.multi_agent ? cfg.multi_agent : null;
const defOrch = 'deep';
let s = stored;
if (s === 'single') s = CHAT_AGENT_MODE_REACT;
if (s === 'multi') s = defOrch;
if (s === CHAT_AGENT_MODE_REACT || chatAgentModeIsEino(s)) return s;
const defMulti = pub && pub.default_mode === 'multi';
return defMulti ? defOrch : CHAT_AGENT_MODE_REACT;
}
if (typeof window !== 'undefined') {
window.csaiChatAgentMode = {
EINO_MODES: CHAT_AGENT_EINO_MODES,
REACT: CHAT_AGENT_MODE_REACT,
isEino: chatAgentModeIsEino,
normalizeStored: chatAgentModeNormalizeStored,
normalizeOrchestration: normalizeOrchestrationClient
};
}
function getAgentModeLabelForValue(mode) {
if (typeof window.t === 'function') {
return mode === 'multi' ? window.t('chat.agentModeMulti') : window.t('chat.agentModeSingle');
switch (mode) {
case CHAT_AGENT_MODE_REACT:
return window.t('chat.agentModeReactNative');
case 'deep':
return window.t('chat.agentModeDeep');
case 'plan_execute':
return window.t('chat.agentModePlanExecuteLabel');
case 'supervisor':
return window.t('chat.agentModeSupervisorLabel');
default:
return mode;
}
}
switch (mode) {
case CHAT_AGENT_MODE_REACT: return '原生 ReAct';
case 'deep': return 'Deep';
case 'plan_execute': return 'Plan-Execute';
case 'supervisor': return 'Supervisor';
default: return mode;
}
return mode === 'multi' ? '多代理' : '单代理';
}
function getAgentModeIconForValue(mode) {
return mode === 'multi' ? '🧩' : '🤖';
switch (mode) {
case CHAT_AGENT_MODE_REACT: return '🤖';
case 'deep': return '🧩';
case 'plan_execute': return '📋';
case 'supervisor': return '🎯';
default: return '🤖';
}
}
function syncAgentModeFromValue(value) {
@@ -88,7 +146,8 @@ function toggleAgentModePanel() {
}
function selectAgentMode(mode) {
if (mode !== 'single' && mode !== 'multi') return;
const ok = mode === CHAT_AGENT_MODE_REACT || chatAgentModeIsEino(mode);
if (!ok) return;
try {
localStorage.setItem(AGENT_MODE_STORAGE_KEY, mode);
} catch (e) { /* ignore */ }
@@ -113,11 +172,11 @@ async function initChatAgentModeFromConfig() {
return;
}
wrap.style.display = '';
const def = (cfg.multi_agent && cfg.multi_agent.default_mode === 'multi') ? 'multi' : 'single';
let stored = localStorage.getItem(AGENT_MODE_STORAGE_KEY);
if (stored !== 'single' && stored !== 'multi') {
stored = def;
}
stored = chatAgentModeNormalizeStored(stored, cfg);
try {
localStorage.setItem(AGENT_MODE_STORAGE_KEY, stored);
} catch (e) { /* ignore */ }
sel.value = stored;
syncAgentModeFromValue(stored);
} catch (e) {
@@ -129,7 +188,7 @@ document.addEventListener('languagechange', function () {
const hid = document.getElementById('agent-mode-select');
if (!hid) return;
const v = hid.value;
if (v === 'single' || v === 'multi') {
if (v === CHAT_AGENT_MODE_REACT || chatAgentModeIsEino(v)) {
syncAgentModeFromValue(v);
}
});
@@ -322,8 +381,12 @@ async function sendMessage() {
try {
const modeSel = document.getElementById('agent-mode-select');
const useMulti = multiAgentAPIEnabled && modeSel && modeSel.value === 'multi';
const modeVal = modeSel ? modeSel.value : CHAT_AGENT_MODE_REACT;
const useMulti = multiAgentAPIEnabled && chatAgentModeIsEino(modeVal);
const streamPath = useMulti ? '/api/multi-agent/stream' : '/api/agent-loop/stream';
if (useMulti && modeVal) {
body.orchestration = modeVal;
}
const response = await apiFetch(streamPath, {
method: 'POST',
headers: {
@@ -1741,12 +1804,33 @@ function renderProcessDetails(messageId, processDetails) {
// 根据事件类型渲染不同的内容
let itemTitle = title;
if (eventType === 'iteration') {
itemTitle = agPx + (typeof window.t === 'function' ? window.t('chat.iterationRound', { n: data.iteration || 1 }) : '第 ' + (data.iteration || 1) + ' 轮迭代');
const n = data.iteration || 1;
if (data.orchestration === 'plan_execute' && data.einoScope === 'main') {
const phase = typeof window.translatePlanExecuteAgentName === 'function'
? window.translatePlanExecuteAgentName(data.einoAgent) : (data.einoAgent || '');
itemTitle = (typeof window.t === 'function'
? window.t('chat.einoPlanExecuteRound', { n: n, phase: phase })
: ('Plan-Execute · 第 ' + n + ' 轮 · ' + phase));
} else if (data.einoScope === 'main') {
itemTitle = agPx + (typeof window.t === 'function'
? window.t('chat.einoOrchestratorRound', { n: n })
: ('主代理 · 第 ' + n + ' 轮'));
} else if (data.einoScope === 'sub') {
const agent = data.einoAgent != null ? String(data.einoAgent).trim() : '';
itemTitle = agPx + (typeof window.t === 'function'
? window.t('chat.einoSubAgentStep', { n: n, agent: agent })
: ('子代理 · ' + agent + ' · 第 ' + n + ' 步'));
} else {
itemTitle = agPx + (typeof window.t === 'function' ? window.t('chat.iterationRound', { n: n }) : '第 ' + n + ' 轮迭代');
}
} else if (eventType === 'thinking') {
itemTitle = agPx + '🤔 ' + (typeof window.t === 'function' ? window.t('chat.aiThinking') : 'AI思考');
} else if (eventType === 'planning') {
// 与流式 monitor.js 中 response_start/response_delta 展示的「规划中」一致(落库聚合)
itemTitle = agPx + '📝 ' + (typeof window.t === 'function' ? window.t('chat.planning') : '规划中');
if (typeof window.einoMainStreamPlanningTitle === 'function') {
itemTitle = window.einoMainStreamPlanningTitle(data);
} else {
itemTitle = agPx + '📝 ' + (typeof window.t === 'function' ? window.t('chat.planning') : '规划中');
}
} else if (eventType === 'tool_calls_detected') {
itemTitle = agPx + '🔧 ' + (typeof window.t === 'function' ? window.t('chat.toolCallsDetected', { count: data.count || 0 }) : '检测到 ' + (data.count || 0) + ' 个工具调用');
} else if (eventType === 'tool_call') {
+161 -16
View File
@@ -25,7 +25,98 @@ function getTimeFormatOptions() {
}
// 将后端下发的进度文案转为当前语言的翻译(中英双向映射,切换语言后能跟上)
function translateProgressMessage(message) {
/** Plan-Execute:将 Eino 内部 agent 名本地化为进度条标题用语 */
function translatePlanExecuteAgentName(name) {
const n = String(name || '').trim().toLowerCase();
if (n === 'planner') return typeof window.t === 'function' ? window.t('progress.peAgentPlanner') : '规划器';
if (n === 'executor') return typeof window.t === 'function' ? window.t('progress.peAgentExecutor') : '执行器';
if (n === 'replanner' || n === 'execute_replan' || n === 'plan_execute_replan') {
return typeof window.t === 'function' ? window.t('progress.peAgentReplanning') : '重规划';
}
return String(name || '').trim();
}
/** 从 Plan-Execute 模型返回的单层 JSON 中取面向用户的字符串(replanner 常用 response)。 */
function pickPeJSONUserText(o) {
if (!o || typeof o !== 'object') {
return '';
}
const keys = ['response', 'answer', 'message', 'content', 'summary', 'output', 'text', 'result'];
for (let i = 0; i < keys.length; i++) {
const v = o[keys[i]];
if (typeof v === 'string') {
const s = v.trim();
if (s) {
return s;
}
}
}
return '';
}
/** 少数模型在 JSON 字符串里仍留下字面量 “\\n”;在已解出正文后再转成换行(不误伤 Windows 盘符时极少命中)。 */
function normalizePeInlineEscapes(s) {
if (!s || s.indexOf('\\n') < 0) {
return s;
}
return s.replace(/\\n/g, '\n').replace(/\\t/g, '\t');
}
/**
* Plan-Execute 时间线正文:planner/replanner 的 {"steps":[...]} 转为列表;{"response":"..."} 解包为纯文本;
* executor 同样解包。流式片段非法 JSON 时保持原文。
*/
function formatTimelineStreamBody(raw, meta) {
if (!raw || !meta || meta.orchestration !== 'plan_execute') {
return raw;
}
const agent = String(meta.einoAgent || '').trim().toLowerCase();
const t = String(raw).trim();
if (t.length < 2 || t.charAt(0) !== '{') {
return raw;
}
try {
const o = JSON.parse(t);
if (agent === 'executor') {
const u = pickPeJSONUserText(o);
return u ? normalizePeInlineEscapes(u) : raw;
}
if (agent === 'planner' || agent === 'replanner' || agent === 'execute_replan' || agent === 'plan_execute_replan') {
if (o && Array.isArray(o.steps) && o.steps.length) {
return o.steps.map(function (s, i) {
return (i + 1) + '. ' + String(s);
}).join('\n');
}
const u = pickPeJSONUserText(o);
if (u) {
return normalizePeInlineEscapes(u);
}
}
} catch (e) {
return raw;
}
return raw;
}
/** 时间线条目:Plan-Execute 主通道流式阶段标题(替代一律「规划中」) */
function einoMainStreamPlanningTitle(responseData) {
const orch = responseData && responseData.orchestration;
const agent = responseData && responseData.einoAgent != null ? String(responseData.einoAgent).trim() : '';
const prefix = timelineAgentBracketPrefix(responseData);
if (orch === 'plan_execute' && agent) {
const a = agent.toLowerCase();
let key = 'chat.planExecuteStreamPhase';
if (a === 'planner') key = 'chat.planExecuteStreamPlanner';
else if (a === 'executor') key = 'chat.planExecuteStreamExecutor';
else if (a === 'replanner' || a === 'execute_replan' || a === 'plan_execute_replan') key = 'chat.planExecuteStreamReplanning';
const label = typeof window.t === 'function' ? window.t(key) : '输出';
return prefix + '📝 ' + label;
}
const plan = typeof window.t === 'function' ? window.t('chat.planning') : '规划中';
return prefix + '📝 ' + plan;
}
function translateProgressMessage(message, data) {
if (!message || typeof message !== 'string') return message;
if (typeof window.t !== 'function') return message;
const trim = message.trim();
@@ -39,6 +130,7 @@ function translateProgressMessage(message) {
'正在分析您的请求...': 'progress.analyzingRequestShort',
'开始分析请求并制定测试策略': 'progress.analyzingRequestPlanning',
'正在启动 Eino DeepAgent...': 'progress.startingEinoDeepAgent',
'正在启动 Eino 多代理...': 'progress.startingEinoMultiAgent',
// 英文(与 en-US.json 一致,避免后端/缓存已是英文时无法随语言切换)
'Calling AI model...': 'progress.callingAI',
'Last iteration: generating summary and next steps...': 'progress.lastIterSummary',
@@ -47,13 +139,18 @@ function translateProgressMessage(message) {
'Max iterations reached, generating summary...': 'progress.maxIterSummary',
'Analyzing your request...': 'progress.analyzingRequestShort',
'Analyzing your request and planning test strategy...': 'progress.analyzingRequestPlanning',
'Starting Eino DeepAgent...': 'progress.startingEinoDeepAgent'
'Starting Eino DeepAgent...': 'progress.startingEinoDeepAgent',
'Starting Eino multi-agent...': 'progress.startingEinoMultiAgent'
};
if (map[trim]) return window.t(map[trim]);
const einoAgentRe = /^\[Eino\]\s*(.+)$/;
const einoM = trim.match(einoAgentRe);
if (einoM) {
return window.t('progress.einoAgent', { name: einoM[1] });
let disp = einoM[1];
if (data && data.orchestration === 'plan_execute') {
disp = translatePlanExecuteAgentName(disp);
}
return window.t('progress.einoAgent', { name: disp });
}
const callingToolPrefixCn = '正在调用工具: ';
const callingToolPrefixEn = 'Calling tool: ';
@@ -69,6 +166,9 @@ function translateProgressMessage(message) {
}
if (typeof window !== 'undefined') {
window.translateProgressMessage = translateProgressMessage;
window.translatePlanExecuteAgentName = translatePlanExecuteAgentName;
window.einoMainStreamPlanningTitle = einoMainStreamPlanningTitle;
window.formatTimelineStreamBody = formatTimelineStreamBody;
}
// 存储工具调用ID到DOM元素的映射,用于更新执行状态
@@ -826,7 +926,12 @@ function handleStreamEvent(event, progressElement, progressId,
const d = event.data || {};
const n = d.iteration != null ? d.iteration : 1;
let iterTitle;
if (d.einoScope === 'main') {
if (d.orchestration === 'plan_execute' && d.einoScope === 'main') {
const phase = translatePlanExecuteAgentName(d.einoAgent != null ? d.einoAgent : '');
iterTitle = typeof window.t === 'function'
? window.t('chat.einoPlanExecuteRound', { n: n, phase: phase })
: ('Plan-Execute · 第 ' + n + ' 轮 · ' + phase);
} else if (d.einoScope === 'main') {
iterTitle = typeof window.t === 'function'
? window.t('chat.einoOrchestratorRound', { n: n })
: ('主代理 · 第 ' + n + ' 轮');
@@ -1202,8 +1307,13 @@ function handleStreamEvent(event, progressElement, progressId,
const progressEl = document.getElementById(progressId);
if (progressEl) {
progressEl.dataset.progressRawMessage = event.message || '';
try {
progressEl.dataset.progressRawData = event.data ? JSON.stringify(event.data) : '';
} catch (e) {
progressEl.dataset.progressRawData = '';
}
}
const progressMsg = translateProgressMessage(event.message);
const progressMsg = translateProgressMessage(event.message, event.data);
progressTitle.textContent = '🔍 ' + progressMsg;
}
break;
@@ -1274,14 +1384,13 @@ function handleStreamEvent(event, progressElement, progressId,
// 多代理模式下,迭代过程中的输出只显示在时间线中,不创建助手消息气泡
// 创建时间线条目用于显示迭代过程中的输出
const agentPrefix = timelineAgentBracketPrefix(responseData);
const title = agentPrefix + '📝 ' + (typeof window.t === 'function' ? window.t('chat.planning') : '规划中');
const title = einoMainStreamPlanningTitle(responseData);
const itemId = addTimelineItem(timeline, 'thinking', {
title: title,
message: ' ',
data: responseData
});
responseStreamStateByProgressId.set(progressId, { itemId: itemId, buffer: '' });
responseStreamStateByProgressId.set(progressId, { itemId: itemId, buffer: '', streamMeta: responseData });
break;
}
@@ -1301,8 +1410,10 @@ function handleStreamEvent(event, progressElement, progressId,
// 更新时间线条目内容
let state = responseStreamStateByProgressId.get(progressId);
if (!state) {
state = { itemId: null, buffer: '' };
state = { itemId: null, buffer: '', streamMeta: responseData };
responseStreamStateByProgressId.set(progressId, state);
} else if (!state.streamMeta && responseData && (responseData.einoAgent || responseData.orchestration)) {
state.streamMeta = responseData;
}
const deltaContent = event.message || '';
@@ -1314,10 +1425,12 @@ function handleStreamEvent(event, progressElement, progressId,
if (item) {
const contentEl = item.querySelector('.timeline-item-content');
if (contentEl) {
const meta = state.streamMeta || responseData;
const body = formatTimelineStreamBody(state.buffer, meta);
if (typeof formatMarkdown === 'function') {
contentEl.innerHTML = formatMarkdown(state.buffer);
contentEl.innerHTML = formatMarkdown(body);
} else {
contentEl.textContent = state.buffer;
contentEl.textContent = body;
}
}
}
@@ -1593,6 +1706,9 @@ function addTimelineItem(timeline, type, options) {
if (options.data && options.data.einoAgent != null && String(options.data.einoAgent).trim() !== '') {
item.dataset.einoAgent = String(options.data.einoAgent).trim();
}
if (options.data && options.data.orchestration != null && String(options.data.orchestration).trim() !== '') {
item.dataset.orchestration = String(options.data.orchestration).trim();
}
// 使用传入的createdAt时间,如果没有则使用当前时间(向后兼容)
let eventTime;
@@ -1630,7 +1746,10 @@ function addTimelineItem(timeline, type, options) {
// 根据类型添加详细内容
if ((type === 'thinking' || type === 'planning') && options.message) {
content += `<div class="timeline-item-content">${formatMarkdown(options.message)}</div>`;
const streamBody = typeof formatTimelineStreamBody === 'function'
? formatTimelineStreamBody(options.message, options.data)
: options.message;
content += `<div class="timeline-item-content">${formatMarkdown(streamBody)}</div>`;
} else if (type === 'tool_call' && options.data) {
const data = options.data;
let args = data.argumentsObj;
@@ -2480,7 +2599,15 @@ function refreshProgressAndTimelineI18n() {
const raw = msgEl.dataset.progressRawMessage;
const titleEl = msgEl.querySelector('.progress-title');
if (titleEl && raw) {
titleEl.textContent = '\uD83D\uDD0D ' + translateProgressMessage(raw);
let pdata = null;
if (msgEl.dataset.progressRawData) {
try {
pdata = JSON.parse(msgEl.dataset.progressRawData);
} catch (e) {
pdata = null;
}
}
titleEl.textContent = '\uD83D\uDD0D ' + translateProgressMessage(raw, pdata);
}
});
// 转换后的详情区顶栏「渗透测试详情」:仅刷新不在 .progress-message 内的 progress 标题
@@ -2499,7 +2626,11 @@ function refreshProgressAndTimelineI18n() {
if (type === 'iteration' && item.dataset.iterationN) {
const n = parseInt(item.dataset.iterationN, 10) || 1;
const scope = item.dataset.einoScope;
if (scope === 'main') {
if (item.dataset.orchestration === 'plan_execute' && scope === 'main') {
const phase = typeof translatePlanExecuteAgentName === 'function'
? translatePlanExecuteAgentName(item.dataset.einoAgent) : (item.dataset.einoAgent || '');
titleSpan.textContent = _t('chat.einoPlanExecuteRound', { n: n, phase: phase });
} else if (scope === 'main') {
titleSpan.textContent = _t('chat.einoOrchestratorRound', { n: n });
} else if (scope === 'sub') {
const agent = item.dataset.einoAgent || '';
@@ -2508,9 +2639,23 @@ function refreshProgressAndTimelineI18n() {
titleSpan.textContent = ap + _t('chat.iterationRound', { n: n });
}
} else if (type === 'thinking') {
titleSpan.textContent = ap + '\uD83E\uDD14 ' + _t('chat.aiThinking');
if (item.dataset.orchestration === 'plan_execute' && item.dataset.einoAgent && typeof einoMainStreamPlanningTitle === 'function') {
titleSpan.textContent = einoMainStreamPlanningTitle({
orchestration: 'plan_execute',
einoAgent: item.dataset.einoAgent
});
} else {
titleSpan.textContent = ap + '\uD83E\uDD14 ' + _t('chat.aiThinking');
}
} else if (type === 'planning') {
titleSpan.textContent = ap + '\uD83D\uDCDD ' + _t('chat.planning');
if (item.dataset.orchestration === 'plan_execute' && item.dataset.einoAgent && typeof einoMainStreamPlanningTitle === 'function') {
titleSpan.textContent = einoMainStreamPlanningTitle({
orchestration: 'plan_execute',
einoAgent: item.dataset.einoAgent
});
} else {
titleSpan.textContent = ap + '\uD83D\uDCDD ' + _t('chat.planning');
}
} else if (type === 'tool_calls_detected' && item.dataset.toolCallsCount != null) {
const count = parseInt(item.dataset.toolCallsCount, 10) || 0;
titleSpan.textContent = ap + '\uD83D\uDD27 ' + _t('chat.toolCallsDetected', { count: count });
+24 -6
View File
@@ -129,6 +129,11 @@ async function loadConfig(loadTools = true) {
const ma = currentConfig.multi_agent || {};
const maEn = document.getElementById('multi-agent-enabled');
if (maEn) maEn.checked = ma.enabled === true;
const maPeLoop = document.getElementById('multi-agent-pe-loop');
if (maPeLoop) {
const v = ma.plan_execute_loop_max_iterations;
maPeLoop.value = (v !== undefined && v !== null && !Number.isNaN(Number(v))) ? String(Number(v)) : '0';
}
const maMode = document.getElementById('multi-agent-default-mode');
if (maMode) maMode.value = (ma.default_mode === 'multi') ? 'multi' : 'single';
const maRobot = document.getElementById('multi-agent-robot-use');
@@ -891,12 +896,18 @@ async function applySettings() {
agent: {
max_iterations: parseInt(document.getElementById('agent-max-iterations').value) || 30
},
multi_agent: {
enabled: document.getElementById('multi-agent-enabled')?.checked === true,
default_mode: document.getElementById('multi-agent-default-mode')?.value === 'multi' ? 'multi' : 'single',
robot_use_multi_agent: document.getElementById('multi-agent-robot-use')?.checked === true,
batch_use_multi_agent: false
},
multi_agent: (function () {
const peRaw = document.getElementById('multi-agent-pe-loop')?.value;
const peParsed = parseInt(peRaw, 10);
const peLoop = Number.isNaN(peParsed) ? 0 : Math.max(0, peParsed);
return {
enabled: document.getElementById('multi-agent-enabled')?.checked === true,
default_mode: document.getElementById('multi-agent-default-mode')?.value === 'multi' ? 'multi' : 'single',
robot_use_multi_agent: document.getElementById('multi-agent-robot-use')?.checked === true,
batch_use_multi_agent: false,
plan_execute_loop_max_iterations: peLoop
};
})(),
knowledge: knowledgeConfig,
robots: {
wecom: {
@@ -1024,6 +1035,13 @@ async function applySettings() {
? window.t('settings.apply.applySuccess')
: '配置已成功应用!';
alert(successMsg);
try {
if (typeof initChatAgentModeFromConfig === 'function') {
await initChatAgentModeFromConfig();
}
} catch (e) {
console.warn('initChatAgentModeFromConfig after settings', e);
}
closeSettings();
} catch (error) {
console.error('应用配置失败:', error);
+23 -7
View File
@@ -14,6 +14,16 @@ function _tPlain(key, opts) {
});
}
/** 批量队列 agentMode 展示文案(与对话模式命名一致) */
function batchQueueAgentModeLabel(mode) {
const m = String(mode || 'single').toLowerCase();
if (m === 'single') return _t('batchImportModal.agentModeSingle');
if (m === 'multi' || m === 'deep') return _t('chat.agentModeDeep');
if (m === 'plan_execute') return _t('chat.agentModePlanExecuteLabel');
if (m === 'supervisor') return _t('chat.agentModeSupervisorLabel');
return _t('batchImportModal.agentModeSingle');
}
/** Cron 队列在「本轮 completed」等状态下的展示文案(底层 status 不变,仅 UI 强调循环调度) */
function getBatchQueueStatusPresentation(queue) {
const map = {
@@ -929,7 +939,8 @@ async function createBatchQueue() {
// 获取角色(可选,空字符串表示默认角色)
const role = roleSelect ? roleSelect.value || '' : '';
const agentMode = agentModeSelect ? (agentModeSelect.value === 'multi' ? 'multi' : 'single') : 'single';
const rawMode = agentModeSelect ? agentModeSelect.value : 'single';
const agentMode = ['single', 'deep', 'plan_execute', 'supervisor'].indexOf(rawMode) >= 0 ? rawMode : 'single';
const scheduleMode = scheduleModeSelect ? (scheduleModeSelect.value === 'cron' ? 'cron' : 'manual') : 'manual';
const cronExpr = cronExprInput ? cronExprInput.value.trim() : '';
const executeNow = executeNowCheckbox ? !!executeNowCheckbox.checked : false;
@@ -1123,7 +1134,7 @@ function renderBatchQueues() {
const cardMod = isCronCycleIdle ? ' batch-queue-item--cron-wait' : '';
const progressFillMod = isCronCycleIdle ? ' batch-queue-progress-fill--cron-wait' : '';
const agentLabel = queue.agentMode === 'multi' ? _t('batchImportModal.agentModeMulti') : _t('batchImportModal.agentModeSingle');
const agentLabel = batchQueueAgentModeLabel(queue.agentMode);
let scheduleLabel = queue.scheduleMode === 'cron' ? _t('batchImportModal.scheduleModeCron') : _t('batchImportModal.scheduleModeManual');
if (queue.scheduleMode === 'cron' && queue.cronExpr) {
scheduleLabel += ` (${queue.cronExpr})`;
@@ -1356,7 +1367,7 @@ async function showBatchQueueDetail(queueId) {
} else {
roleLineVal = '\uD83D\uDD35 ' + escapeHtml(_t('batchQueueDetailModal.defaultRole'));
}
const agentModeText = queue.agentMode === 'multi' ? _t('batchImportModal.agentModeMulti') : _t('batchImportModal.agentModeSingle');
const agentModeText = batchQueueAgentModeLabel(queue.agentMode);
const scheduleModeText = queue.scheduleMode === 'cron' ? _t('batchImportModal.scheduleModeCron') : _t('batchImportModal.scheduleModeManual');
const scheduleDetail = escapeHtml(scheduleModeText) + (queue.scheduleMode === 'cron' && queue.cronExpr ? `${escapeHtml(queue.cronExpr)}` : '');
const showProgressNoteInModal = !!(pres.progressNote && !pres.callout);
@@ -2125,11 +2136,15 @@ function startInlineEditAgentMode() {
if (!queueId) return;
apiFetch(`/api/batch-tasks/${queueId}`).then(r => r.json()).then(detail => {
const queue = detail.queue;
const currentMode = queue.agentMode || 'single';
let currentMode = (queue.agentMode || 'single').toLowerCase();
if (currentMode === 'multi') currentMode = 'deep';
if (['single', 'deep', 'plan_execute', 'supervisor'].indexOf(currentMode) < 0) currentMode = 'single';
container.innerHTML = `<span class="bq-inline-edit-controls">
<select id="bq-edit-agentmode">
<option value="single" ${currentMode !== 'multi' ? 'selected' : ''}>${escapeHtml(_t('batchImportModal.agentModeSingle'))}</option>
<option value="multi" ${currentMode === 'multi' ? 'selected' : ''}>${escapeHtml(_t('batchImportModal.agentModeMulti'))}</option>
<option value="single" ${currentMode === 'single' ? 'selected' : ''}>${escapeHtml(_t('batchImportModal.agentModeSingle'))}</option>
<option value="deep" ${currentMode === 'deep' ? 'selected' : ''}>${escapeHtml(_t('chat.agentModeDeep'))}</option>
<option value="plan_execute" ${currentMode === 'plan_execute' ? 'selected' : ''}>${escapeHtml(_t('chat.agentModePlanExecuteLabel'))}</option>
<option value="supervisor" ${currentMode === 'supervisor' ? 'selected' : ''}>${escapeHtml(_t('chat.agentModeSupervisorLabel'))}</option>
</select>
</span>`;
const sel = document.getElementById('bq-edit-agentmode');
@@ -2150,7 +2165,8 @@ async function saveInlineAgentMode() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) { _bqInlineSaving = false; return; }
const sel = document.getElementById('bq-edit-agentmode');
const agentMode = sel ? sel.value : 'single';
const raw = sel ? sel.value : 'single';
const agentMode = ['single', 'deep', 'plan_execute', 'supervisor'].indexOf(raw) >= 0 ? raw : 'single';
try {
const detailResp = await apiFetch(`/api/batch-tasks/${queueId}`);
const detail = await detailResp.json();
+25 -12
View File
@@ -37,23 +37,33 @@ let webshellStreamingTypingId = 0;
let webshellProbeStatusById = {};
let webshellBatchProbeRunning = false;
/** 与主对话页一致:multi_agent.enabled 且本地模式为 multi 时使用 /api/multi-agent/stream */
function resolveWebshellAiStreamPath() {
/** 与主对话页一致:Eino 模式走 /api/multi-agent/streambody 带 orchestration */
function resolveWebshellAiStreamRequest() {
if (typeof apiFetch === 'undefined') {
return Promise.resolve('/api/agent-loop/stream');
return Promise.resolve({ path: '/api/agent-loop/stream', orchestration: null });
}
return apiFetch('/api/config').then(function (r) {
if (!r.ok) return '/api/agent-loop/stream';
if (!r.ok) return null;
return r.json();
}).then(function (cfg) {
if (!cfg || !cfg.multi_agent || !cfg.multi_agent.enabled) return '/api/agent-loop/stream';
var mode = localStorage.getItem('cyberstrike-chat-agent-mode');
if (mode !== 'single' && mode !== 'multi') {
mode = (cfg.multi_agent.default_mode === 'multi') ? 'multi' : 'single';
if (!cfg || !cfg.multi_agent || !cfg.multi_agent.enabled) {
return { path: '/api/agent-loop/stream', orchestration: null };
}
return mode === 'multi' ? '/api/multi-agent/stream' : '/api/agent-loop/stream';
var norm = null;
if (typeof window.csaiChatAgentMode === 'object' && typeof window.csaiChatAgentMode.normalizeStored === 'function') {
norm = window.csaiChatAgentMode.normalizeStored(localStorage.getItem('cyberstrike-chat-agent-mode'), cfg);
} else {
var mode = localStorage.getItem('cyberstrike-chat-agent-mode');
if (mode === 'single') mode = 'react';
if (mode === 'multi') mode = 'deep';
norm = mode || 'react';
}
if (typeof window.csaiChatAgentMode === 'object' && typeof window.csaiChatAgentMode.isEino === 'function' && window.csaiChatAgentMode.isEino(norm)) {
return { path: '/api/multi-agent/stream', orchestration: norm };
}
return { path: '/api/agent-loop/stream', orchestration: null };
}).catch(function () {
return '/api/agent-loop/stream';
return { path: '/api/agent-loop/stream', orchestration: null };
});
}
@@ -2428,8 +2438,11 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
var streamingTarget = ''; // 当前要打字显示的目标全文(用于打字机效果)
var streamingTypingId = 0; // 防重入,每次新 response 自增
resolveWebshellAiStreamPath().then(function (streamPath) {
return apiFetch(streamPath, {
resolveWebshellAiStreamRequest().then(function (info) {
if (info && info.orchestration) {
body.orchestration = info.orchestration;
}
return apiFetch(info.path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
+37 -14
View File
@@ -586,7 +586,7 @@
</div>
<div id="agent-mode-wrapper" class="agent-mode-wrapper" style="display: none;">
<div class="agent-mode-inner">
<button type="button" id="agent-mode-btn" class="role-selector-btn agent-mode-btn" onclick="toggleAgentModePanel()" data-i18n="chat.agentModeSelectAria" data-i18n-attr="aria-label,title" data-i18n-skip-text="true" aria-label="选择单代理或多代理" aria-haspopup="listbox" aria-expanded="false" title="选择单代理或多代理">
<button type="button" id="agent-mode-btn" class="role-selector-btn agent-mode-btn" onclick="toggleAgentModePanel()" data-i18n="chat.agentModeSelectAria" data-i18n-attr="aria-label,title" data-i18n-skip-text="true" aria-label="选择对话执行模式" aria-haspopup="listbox" aria-expanded="false" title="选择对话执行模式">
<span id="agent-mode-icon" class="role-selector-icon" aria-hidden="true">🤖</span>
<span id="agent-mode-text" class="role-selector-text">单代理</span>
<svg class="role-selector-arrow" width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
@@ -603,26 +603,42 @@
</button>
</div>
<div class="agent-mode-options">
<button type="button" class="role-selection-item-main agent-mode-option" data-value="single" role="option" onclick="selectAgentMode('single')">
<button type="button" class="role-selection-item-main agent-mode-option" data-value="react" role="option" onclick="selectAgentMode('react')">
<div class="role-selection-item-icon-main" aria-hidden="true">🤖</div>
<div class="role-selection-item-content-main">
<div class="role-selection-item-name-main" data-i18n="chat.agentModeSingle">单代理</div>
<div class="role-selection-item-description-main" data-i18n="chat.agentModeSingleHint">单模型 ReAct 循环,适合常规对话与工具调用</div>
<div class="role-selection-item-name-main" data-i18n="chat.agentModeReactNative">原生 ReAct 模式</div>
<div class="role-selection-item-description-main" data-i18n="chat.agentModeReactNativeHint">经典单代理 ReAct 与 MCP 工具(/api/agent-loop</div>
</div>
<div class="role-selection-checkmark-main agent-mode-check" data-agent-mode-check="single"></div>
<div class="role-selection-checkmark-main agent-mode-check" data-agent-mode-check="react"></div>
</button>
<button type="button" class="role-selection-item-main agent-mode-option" data-value="multi" role="option" onclick="selectAgentMode('multi')">
<button type="button" class="role-selection-item-main agent-mode-option" data-value="deep" role="option" onclick="selectAgentMode('deep')">
<div class="role-selection-item-icon-main" aria-hidden="true">🧩</div>
<div class="role-selection-item-content-main">
<div class="role-selection-item-name-main" data-i18n="chat.agentModeMulti">多代理</div>
<div class="role-selection-item-description-main" data-i18n="chat.agentModeMultiHint">Eino DeepAgent 编排子代理,适合复杂任务</div>
<div class="role-selection-item-name-main" data-i18n="chat.agentModeDeep">DeepDeepAgent</div>
<div class="role-selection-item-description-main" data-i18n="chat.agentModeDeepHint">Eino DeepAgenttask 调度子代理</div>
</div>
<div class="role-selection-checkmark-main agent-mode-check" data-agent-mode-check="multi"></div>
<div class="role-selection-checkmark-main agent-mode-check" data-agent-mode-check="deep"></div>
</button>
<button type="button" class="role-selection-item-main agent-mode-option" data-value="plan_execute" role="option" onclick="selectAgentMode('plan_execute')">
<div class="role-selection-item-icon-main" aria-hidden="true">📋</div>
<div class="role-selection-item-content-main">
<div class="role-selection-item-name-main" data-i18n="chat.agentModePlanExecuteLabel">Plan-Execute</div>
<div class="role-selection-item-description-main" data-i18n="chat.agentModePlanExecuteHint">规划 → 执行 → 重规划(单执行器工具链)</div>
</div>
<div class="role-selection-checkmark-main agent-mode-check" data-agent-mode-check="plan_execute"></div>
</button>
<button type="button" class="role-selection-item-main agent-mode-option" data-value="supervisor" role="option" onclick="selectAgentMode('supervisor')">
<div class="role-selection-item-icon-main" aria-hidden="true">🎯</div>
<div class="role-selection-item-content-main">
<div class="role-selection-item-name-main" data-i18n="chat.agentModeSupervisorLabel">Supervisor</div>
<div class="role-selection-item-description-main" data-i18n="chat.agentModeSupervisorHint">监督者协调,transfer 委派子代理</div>
</div>
<div class="role-selection-checkmark-main agent-mode-check" data-agent-mode-check="supervisor"></div>
</button>
</div>
</div>
</div>
<input type="hidden" id="agent-mode-select" value="single" autocomplete="off">
<input type="hidden" id="agent-mode-select" value="react" autocomplete="off">
</div>
<div class="chat-input-with-files">
<div id="chat-file-list" class="chat-file-list" aria-label="已选文件列表"></div>
@@ -1411,9 +1427,14 @@
<label class="checkbox-label">
<input type="checkbox" id="multi-agent-enabled" class="modern-checkbox" />
<span class="checkbox-custom"></span>
<span class="checkbox-text" data-i18n="settingsBasic.enableMultiAgent">启用 Eino 多代理DeepAgent</span>
<span class="checkbox-text" data-i18n="settingsBasic.enableMultiAgent">启用 Eino 多代理</span>
</label>
<small class="form-hint" data-i18n="settingsBasic.enableMultiAgentHint">开启后对话页可选「多代理」模式;子代理在 config.yaml 的 multi_agent.sub_agents 中配置。</small>
<small class="form-hint" data-i18n="settingsBasic.enableMultiAgentHint">开启后对话页可选「多代理」模式;子代理在 config.yaml 的 multi_agent.sub_agents 或 agents 目录中配置。</small>
</div>
<div class="form-group">
<label for="multi-agent-pe-loop" data-i18n="settingsBasic.multiAgentPeLoop">plan_execute 外层循环上限</label>
<input type="number" id="multi-agent-pe-loop" min="0" step="1" value="0" data-i18n="settingsBasic.multiAgentPeLoopPlaceholder" data-i18n-attr="placeholder" placeholder="0 表示 Eino 默认 10" />
<small class="form-hint" data-i18n="settingsBasic.multiAgentPeLoopHint">仅 orchestration=plan_execute 时有效;execute 与 replan 之间的最大轮次。</small>
</div>
<div class="form-group">
<label for="multi-agent-default-mode" data-i18n="settingsBasic.multiAgentDefaultMode">对话页默认模式</label>
@@ -2408,9 +2429,11 @@
<label for="batch-queue-agent-mode" data-i18n="batchImportModal.agentMode">代理模式</label>
<select id="batch-queue-agent-mode" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.875rem;">
<option value="single" data-i18n="batchImportModal.agentModeSingle">单代理(ReAct</option>
<option value="multi" data-i18n="batchImportModal.agentModeMulti">多代理(Eino</option>
<option value="deep" data-i18n="chat.agentModeDeep">DeepDeepAgent</option>
<option value="plan_execute" data-i18n="chat.agentModePlanExecuteLabel">Plan-Execute</option>
<option value="supervisor" data-i18n="chat.agentModeSupervisorLabel">Supervisor</option>
</select>
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.agentModeHint">建议默认单代理;复杂任务可使用多代理(需系统已启用多代理)。</div>
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.agentModeHint">与对话页一致:原生 ReAct 或三种 Eino 编排(需已启用多代理)。</div>
</div>
<div class="form-group">
<label for="batch-queue-schedule-mode" data-i18n="batchImportModal.scheduleMode">调度方式</label>