From 2ce02e4137a42df04d8705e0f88a82c5f6e0f1d2 Mon Sep 17 00:00:00 2001 From: BitFun Research Date: Sat, 18 Jul 2026 15:09:48 +0800 Subject: [PATCH] feat: add Legion orchestration UI with pattern selector and preset cards --- .../src/app/scenes/agents/AgentsScene.tsx | 61 ++- .../src/app/scenes/agents/agentVisibility.ts | 20 +- .../agents/components/CreateLegionPage.tsx | 177 ++++++++ .../scenes/agents/components/LegionCard.scss | 99 +++++ .../scenes/agents/components/LegionCard.tsx | 83 ++++ .../agents/data/orchestration-patterns.ts | 392 ++++++++++++++++++ .../app/scenes/agents/hooks/useAgentsList.ts | 40 +- .../api/service-api/LegionPresetAPI.ts | 45 ++ .../src/locales/en-US/scenes/agents.json | 31 ++ .../src/locales/zh-CN/scenes/agents.json | 31 ++ .../src/locales/zh-TW/scenes/agents.json | 31 ++ 11 files changed, 988 insertions(+), 22 deletions(-) create mode 100644 src/web-ui/src/app/scenes/agents/components/CreateLegionPage.tsx create mode 100644 src/web-ui/src/app/scenes/agents/components/LegionCard.scss create mode 100644 src/web-ui/src/app/scenes/agents/components/LegionCard.tsx create mode 100644 src/web-ui/src/app/scenes/agents/data/orchestration-patterns.ts create mode 100644 src/web-ui/src/infrastructure/api/service-api/LegionPresetAPI.ts diff --git a/src/web-ui/src/app/scenes/agents/AgentsScene.tsx b/src/web-ui/src/app/scenes/agents/AgentsScene.tsx index 936ff58ca..57a076325 100644 --- a/src/web-ui/src/app/scenes/agents/AgentsScene.tsx +++ b/src/web-ui/src/app/scenes/agents/AgentsScene.tsx @@ -39,7 +39,8 @@ import './AgentsView.scss'; import './AgentsScene.scss'; import { useGallerySceneAutoRefresh } from '@/app/hooks/useGallerySceneAutoRefresh'; import { - CORE_AGENT_IDS, + ACP_CORE_AGENT_PREFIX, + buildCoreAgentIds, isAgentInOverviewZone, isLocallyManageableSubagent, } from './agentVisibility'; @@ -288,26 +289,50 @@ const AgentsHomeView: React.FC = () => { }; }, []); - const coreAgentMeta = useMemo((): Record => ({ - agentic: { - role: t('coreAgentsZone.modes.agentic.role'), - ...CORE_AGENT_ACCENTS.agentic, - }, - Cowork: { - role: t('coreAgentsZone.modes.cowork.role'), - ...CORE_AGENT_ACCENTS.Cowork, - }, - ComputerUse: { - role: t('coreAgentsZone.modes.computerUse.role'), - ...CORE_AGENT_ACCENTS.ComputerUse, - }, - }), [t]); + const coreAgentMeta = useMemo((): Record => { + const meta: Record = { + agentic: { + role: t('coreAgentsZone.modes.agentic.role'), + ...CORE_AGENT_ACCENTS.agentic, + }, + Cowork: { + role: t('coreAgentsZone.modes.cowork.role'), + ...CORE_AGENT_ACCENTS.Cowork, + }, + ComputerUse: { + role: t('coreAgentsZone.modes.computerUse.role'), + ...CORE_AGENT_ACCENTS.ComputerUse, + }, + }; + // Append ACP external agent meta (use a neutral accent). + for (const agent of allAgents) { + if (agent.id.startsWith(ACP_CORE_AGENT_PREFIX) && !meta[agent.id]) { + meta[agent.id] = { + role: t('coreAgentsZone.modes.acpExternal.role', agent.name), + ...DEFAULT_CORE_AGENT_ACCENT, + }; + } + } + return meta; + }, [t, allAgents]); + + const acpEnabledIds = useMemo( + () => allAgents + .filter((a) => a.id.startsWith(ACP_CORE_AGENT_PREFIX)) + .map((a) => a.id.slice(ACP_CORE_AGENT_PREFIX.length)), + [allAgents], + ); - const coreAgents = useMemo(() => allAgents.filter((agent) => CORE_AGENT_IDS.has(agent.id)), [allAgents]); + const effectiveCoreAgentIds = useMemo(() => buildCoreAgentIds(acpEnabledIds), [acpEnabledIds]); + + const coreAgents = useMemo( + () => allAgents.filter((agent) => effectiveCoreAgentIds.has(agent.id)), + [allAgents, effectiveCoreAgentIds], + ); const visibleAgents = useMemo( - () => filteredAgents.filter((agent) => isAgentInOverviewZone(agent, hiddenAgentIds)), - [filteredAgents, hiddenAgentIds], + () => filteredAgents.filter((agent) => isAgentInOverviewZone(agent, hiddenAgentIds, effectiveCoreAgentIds)), + [filteredAgents, hiddenAgentIds, effectiveCoreAgentIds], ); const scrollToZone = useCallback((targetId: string) => { diff --git a/src/web-ui/src/app/scenes/agents/agentVisibility.ts b/src/web-ui/src/app/scenes/agents/agentVisibility.ts index e5fe4b460..bf77e5b70 100644 --- a/src/web-ui/src/app/scenes/agents/agentVisibility.ts +++ b/src/web-ui/src/app/scenes/agents/agentVisibility.ts @@ -18,15 +18,31 @@ export const HIDDEN_AGENT_IDS = new Set([ ...FALLBACK_REVIEW_HIDDEN_AGENT_IDS, ]); +/** Prefix used by ACP external agents (e.g. acp__codex, acp__Claude_Code). */ +export const ACP_CORE_AGENT_PREFIX = 'acp__'; + /** Core mode agents shown in the top zone only; excluded from overview zone list and counts. */ -export const CORE_AGENT_IDS = new Set(['agentic', 'Cowork', 'ComputerUse']); +const STATIC_CORE_AGENT_IDS = new Set(['agentic', 'Cowork', 'ComputerUse']); + +/** Build the effective core-agent id set including enabled ACP external agents. */ +export function buildCoreAgentIds(acpEnabledIds: string[]): Set { + const ids = new Set(STATIC_CORE_AGENT_IDS); + for (const acpId of acpEnabledIds) { + ids.add(`${ACP_CORE_AGENT_PREFIX}${acpId}`); + } + return ids; +} + +/** Legacy static set kept for backward-compat callers that don't inject ACP ids. */ +export const CORE_AGENT_IDS = STATIC_CORE_AGENT_IDS; /** Agents that appear in the bottom overview grid (same pool as filter chip counts). */ export function isAgentInOverviewZone( agent: { id: string }, hiddenAgentIds: ReadonlySet = HIDDEN_AGENT_IDS, + coreAgentIds: ReadonlySet = CORE_AGENT_IDS, ): boolean { - return !hiddenAgentIds.has(agent.id) && !CORE_AGENT_IDS.has(agent.id); + return !hiddenAgentIds.has(agent.id) && !coreAgentIds.has(agent.id); } /** External subagents are visible in the overview but managed by their source adapter. */ diff --git a/src/web-ui/src/app/scenes/agents/components/CreateLegionPage.tsx b/src/web-ui/src/app/scenes/agents/components/CreateLegionPage.tsx new file mode 100644 index 000000000..5e4b32c7c --- /dev/null +++ b/src/web-ui/src/app/scenes/agents/components/CreateLegionPage.tsx @@ -0,0 +1,177 @@ +import React, { useCallback, useState } from 'react'; +import { ArrowLeft, GitBranch, Network } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Button, IconButton } from '@/component-library'; +import { useNotification } from '@/shared/notification-system'; +import PATTERNS, { + type LegionPatternNode, + type LegionPatternEdge, +} from '../data/orchestration-patterns'; +import { LegionPresetAPI } from '@/infrastructure/api/service-api/LegionPresetAPI'; +import '../AgentsView.scss'; + +interface CreateLegionPageProps { + onBack: () => void; +} + +const CreateLegionPage: React.FC = ({ onBack }) => { + const { t } = useTranslation('scenes/agents'); + const { success: notifySuccess, error: notifyError } = useNotification(); + const [selectedPatternId, setSelectedPatternId] = useState(PATTERNS[0]?.id ?? ''); + const [saving, setSaving] = useState(false); + + const selectedPattern = PATTERNS.find((p) => p.id === selectedPatternId) ?? null; + + const handleSelectPattern = useCallback((id: string) => { + setSelectedPatternId(id); + }, []); + + const handleSave = useCallback(async () => { + if (!selectedPattern || saving) return; + setSaving(true); + try { + await LegionPresetAPI.createPreset({ + id: selectedPattern.id, + name: selectedPattern.name, + description: selectedPattern.description, + nodes: selectedPattern.nodes.map((n) => ({ + id: n.id, + agent: n.agent, + role: n.role, + prompt: n.prompt, + gate: n.gate, + })), + edges: selectedPattern.edges.map((e) => ({ + from: e.from, + to: e.to, + condition: e.condition, + })), + }); + notifySuccess(`Legion preset "${selectedPattern.name}" saved`); + onBack(); + } catch (err) { + notifyError(`Failed to save legion preset: ${err}`); + } finally { + setSaving(false); + } + }, [selectedPattern, saving, onBack, notifySuccess, notifyError]); + + const renderNodeList = (nodes: LegionPatternNode[]) => ( +
+ {nodes.map((node, i) => ( +
+ {i + 1} +
+ {node.role} + {node.agent} +
+ {node.gate ? {t('legionPattern.gate')} : null} +
+ ))} +
+ ); + + const renderEdgeList = (edges: LegionPatternEdge[], nodes: LegionPatternNode[]) => ( +
+ {edges.map((edge) => { + const fromNode = nodes.find((n) => n.id === edge.from); + const toNode = nodes.find((n) => n.id === edge.to); + return ( +
${edge.to}`} className="legion-edge-item"> + {fromNode?.role ?? edge.from} + + {toNode?.role ?? edge.to} + {edge.condition ? ( + [{edge.condition}] + ) : null} +
+ ); + })} +
+ ); + + return ( +
+
+ + + +

+ {selectedPattern ? selectedPattern.name : t('legionPattern.choosePattern')} +

+
+ + {/* Pattern selector */} +
+

{t('legionPattern.orchestrationPatterns')}

+
+ {PATTERNS.map((pattern) => ( +
handleSelectPattern(pattern.id)} + role="button" + tabIndex={0} + aria-pressed={pattern.id === selectedPatternId} + onKeyDown={(e) => e.key === 'Enter' && handleSelectPattern(pattern.id)} + data-testid="legion-pattern-option" + data-pattern-id={pattern.id} + > + + {pattern.name} +
+ ))} +
+
+ + {selectedPattern ? ( + <> + {/* Summary */} +
+

{t('legionPattern.overview')}

+

{selectedPattern.description}

+
+ {t('legionPattern.complexity', { level: selectedPattern.complexityLevel })} + {t('legionPattern.nodesCount', { count: selectedPattern.nodes.length })} + {t('legionPattern.edgesCount', { count: selectedPattern.edges.length })} +
+
+ + {/* Nodes */} +
+

+ {t('legionPattern.nodes', { count: selectedPattern.nodes.length })} +

+ {renderNodeList(selectedPattern.nodes)} +
+ + {/* Edges */} +
+

+ {t('legionPattern.edges', { count: selectedPattern.edges.length })} +

+ {selectedPattern.edges.length > 0 + ? renderEdgeList(selectedPattern.edges, selectedPattern.nodes) + :

{t('legionPattern.noEdges')}

} +
+ + {/* Actions */} +
+ + +
+ + ) : null} +
+ ); +}; + +export default CreateLegionPage; diff --git a/src/web-ui/src/app/scenes/agents/components/LegionCard.scss b/src/web-ui/src/app/scenes/agents/components/LegionCard.scss new file mode 100644 index 000000000..925e15e3e --- /dev/null +++ b/src/web-ui/src/app/scenes/agents/components/LegionCard.scss @@ -0,0 +1,99 @@ +.legion-card { + cursor: pointer; + display: flex; + flex-direction: column; + gap: 10px; + padding: 16px; + border-radius: 10px; + background: var(--element-bg-soft); + border: 1px solid var(--border-subtle); + transition: border-color 0.15s, box-shadow 0.15s; + + &:hover { + border-color: var(--border-accent); + box-shadow: 0 2px 8px rgb(0 0 0 / 0.06); + } + + &__header { + display: flex; + align-items: flex-start; + gap: 10px; + } + + &__icon-area { + flex-shrink: 0; + } + + &__icon { + width: 36px; + height: 36px; + border-radius: 8px; + background: var(--color-overlay-white-12); + color: var(--color-accent-500); + display: flex; + align-items: center; + justify-content: center; + } + + &__header-info { + flex: 1; + min-width: 0; + } + + &__title-row { + display: flex; + align-items: center; + gap: 6px; + } + + &__name { + font-size: 14px; + font-weight: 600; + color: var(--color-text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__badges { + display: flex; + gap: 4px; + flex-shrink: 0; + } + + &__body { + flex: 1; + } + + &__desc { + font-size: 12px; + line-height: 1.5; + color: var(--color-text-secondary); + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + &__footer { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 6px; + } + + &__meta { + display: flex; + gap: 10px; + } + + &__meta-item { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 11px; + color: var(--color-text-muted); + } +} diff --git a/src/web-ui/src/app/scenes/agents/components/LegionCard.tsx b/src/web-ui/src/app/scenes/agents/components/LegionCard.tsx new file mode 100644 index 000000000..3b8c53d9c --- /dev/null +++ b/src/web-ui/src/app/scenes/agents/components/LegionCard.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { GitBranch, Users, Network } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Badge } from '@/component-library'; +import type { LegionPattern } from '../data/orchestration-patterns'; +import './LegionCard.scss'; + +interface LegionCardProps { + pattern: LegionPattern; + index?: number; + onOpenDetails: (pattern: LegionPattern) => void; +} + +const LegionCard: React.FC = ({ + pattern, + index = 0, + onOpenDetails, +}) => { + const { t } = useTranslation('scenes/agents'); + const gateNodes = pattern.nodes.filter((n) => n.gate).length; + const openDetails = () => onOpenDetails(pattern); + + const complexityLabel = + t(`legionPattern.complexityLabel.l${pattern.complexityLevel}`, { + defaultValue: `L${pattern.complexityLevel}`, + }); + + return ( +
e.key === 'Enter' && openDetails()} + aria-label={pattern.name} + data-testid="legion-list-item" + data-legion-id={pattern.id} + > +
+
+
+ +
+
+
+
+ {pattern.name} +
+ + {complexityLabel} + +
+
+
+
+ +
+

{pattern.description}

+
+ +
+
+ + + {t('legionPattern.nodesCount', { count: pattern.nodes.length })} + + + + {t('legionPattern.edgesCount', { count: pattern.edges.length })} + + {gateNodes > 0 ? ( + + {gateNodes} {t('legionPattern.meta.gate')} + + ) : null} +
+
+
+ ); +}; + +export default LegionCard; diff --git a/src/web-ui/src/app/scenes/agents/data/orchestration-patterns.ts b/src/web-ui/src/app/scenes/agents/data/orchestration-patterns.ts new file mode 100644 index 000000000..ca7e440bc --- /dev/null +++ b/src/web-ui/src/app/scenes/agents/data/orchestration-patterns.ts @@ -0,0 +1,392 @@ +/** + * 18 built-in orchestration patterns for legion templates. + * Each pattern maps to the orchestration-patterns skill library. + */ +export interface LegionPatternNode { + id: string; + agent: string; + role: string; + prompt: string; + gate?: boolean; +} + +export interface LegionPatternEdge { + from: string; + to: string; + condition?: string; +} + +export interface LegionPattern { + id: string; + name: string; + description: string; + complexityLevel: number; + nodes: LegionPatternNode[]; + edges: LegionPatternEdge[]; +} + +const PATTERNS: LegionPattern[] = [ + { + id: 'sparc-dev', + name: 'SPARC Development', + description: '5-stage SPARC development pipeline: specification → pseudocode → architecture → refinement → completion', + complexityLevel: 4, + nodes: [ + { id: 'researcher', agent: 'Plan', role: 'Research Bee', prompt: 'Gather requirements, define acceptance criteria, identify constraints and edge cases.' }, + { id: 'decomposer', agent: 'Plan', role: 'Decompose Bee', prompt: 'Decompose into executable sub-tasks, annotate complexity, define dependencies.' }, + { id: 'architect', agent: 'agentic', role: 'Architect Bee', prompt: 'Design modules, define interfaces, resolve constraints.' }, + { id: 'implementer', agent: 'agentic', role: 'Implement Bee', prompt: 'Implement according to architecture and interface contracts.' }, + { id: 'tester', agent: 'agentic', role: 'Test Bee', prompt: 'Write and run automated tests. Coverage ≥ 80%, all ACs pass.' }, + { id: 'reviewer', agent: 'DeepReview', role: 'Review Bee', prompt: 'Code review and documentation generation.', gate: true }, + ], + edges: [ + { from: 'researcher', to: 'decomposer' }, + { from: 'decomposer', to: 'architect' }, + { from: 'architect', to: 'implementer' }, + { from: 'architect', to: 'tester' }, + { from: 'implementer', to: 'reviewer' }, + { from: 'tester', to: 'reviewer' }, + { from: 'reviewer', to: 'implementer', condition: 'fail' }, + { from: 'reviewer', to: 'tester', condition: 'fail' }, + ], + }, + { + id: 'cicd-pipeline', + name: 'CI/CD Pipeline', + description: 'Lint → Unit test → Build → Integration test → Security audit → Deploy → Verify', + complexityLevel: 5, + nodes: [ + { id: 'lint', agent: 'agentic', role: 'Lint Bee', prompt: 'Run linter, type checker, security scan. Gate: zero errors.' }, + { id: 'unit-test', agent: 'agentic', role: 'Unit Test Bee', prompt: 'Run unit tests across multiple environments. Gate: all pass, coverage ≥ 80%.' }, + { id: 'build', agent: 'agentic', role: 'Build Bee', prompt: 'Compile, package, upload artifact. Gate: build succeeds.' }, + { id: 'integration', agent: 'agentic', role: 'Integration Bee', prompt: 'Deploy to staging, run integration tests, smoke test.' }, + { id: 'security-audit', agent: 'agentic', role: 'Security Bee', prompt: 'Dependency vulnerability scan, container scan, compliance check.' }, + { id: 'deploy', agent: 'agentic', role: 'Deploy Bee', prompt: 'Rollout with health check. Gate: health passes.' }, + { id: 'verify', agent: 'DeepReview', role: 'Verify Bee', prompt: 'Smoke test production, monitor metrics, rollback if needed.', gate: true }, + ], + edges: [ + { from: 'lint', to: 'unit-test' }, + { from: 'unit-test', to: 'build' }, + { from: 'build', to: 'integration' }, + { from: 'integration', to: 'security-audit' }, + { from: 'security-audit', to: 'deploy' }, + { from: 'deploy', to: 'verify' }, + ], + }, + { + id: 'fan-out-converge', + name: 'Fan-out Converge', + description: 'Dispatch → Parallel research (N bees) → Synthesize → Final review', + complexityLevel: 5, + nodes: [ + { id: 'dispatch', agent: 'Team', role: 'Commander', prompt: 'Evaluate task, match pattern, build team, assign sub-goals.' }, + { id: 'researcher-1', agent: 'agentic', role: 'Research Bee A', prompt: 'Research scope A independently and report structured results.' }, + { id: 'researcher-2', agent: 'agentic', role: 'Research Bee B', prompt: 'Research scope B independently and report structured results.' }, + { id: 'researcher-3', agent: 'agentic', role: 'Research Bee C', prompt: 'Research scope C independently and report structured results.' }, + { id: 'synthesizer', agent: 'agentic', role: 'Synthesize Bee', prompt: 'Collect results, resolve conflicts, merge outputs, check consistency.' }, + { id: 'reviewer', agent: 'DeepReview', role: 'Review Bee', prompt: 'Review merged output, generate final report.', gate: true }, + ], + edges: [ + { from: 'dispatch', to: 'researcher-1' }, + { from: 'dispatch', to: 'researcher-2' }, + { from: 'dispatch', to: 'researcher-3' }, + { from: 'researcher-1', to: 'synthesizer' }, + { from: 'researcher-2', to: 'synthesizer' }, + { from: 'researcher-3', to: 'synthesizer' }, + { from: 'synthesizer', to: 'reviewer' }, + { from: 'reviewer', to: 'synthesizer', condition: 'fail' }, + ], + }, + { + id: 'triad-minimal', + name: 'Three-Bee Minimal', + description: 'Prompt Bee → Execute Bee → Review Bee. Atomic execution unit.', + complexityLevel: 2, + nodes: [ + { id: 'prompt-bee', agent: 'Plan', role: 'Prompt Bee', prompt: 'Analyze task, inject relevant skills and templates.' }, + { id: 'execute-bee', agent: 'agentic', role: 'Execute Bee', prompt: 'Execute the task using the provided methodology.' }, + { id: 'review-bee', agent: 'DeepReview', role: 'Review Bee', prompt: 'Audit behavior and output, gate pass/fail.', gate: true }, + ], + edges: [ + { from: 'prompt-bee', to: 'execute-bee' }, + { from: 'execute-bee', to: 'review-bee' }, + { from: 'review-bee', to: 'execute-bee', condition: 'fail' }, + { from: 'review-bee', to: 'prompt-bee', condition: 'fail' }, + ], + }, + { + id: 'state-machine', + name: 'State Machine', + description: 'Multi-state flow with conditional branches and escalation.', + complexityLevel: 6, + nodes: [ + { id: 'pending', agent: 'Plan', role: 'Assess Bee', prompt: 'Evaluate task complexity and route to appropriate state.' }, + { id: 'executing', agent: 'agentic', role: 'Execute Bee', prompt: 'Execute. On success → review. On failure (≤3) → retry. On failure (>3) → escalate.' }, + { id: 'reviewing', agent: 'DeepReview', role: 'Review Bee', prompt: 'Review. On pass → complete. On fix (≤3 rounds) → back to executing.' }, + { id: 'escalated', agent: 'Team', role: 'Escalation', prompt: 'Human-in-the-loop decision: confirm fix or abandon.' }, + { id: 'completed', agent: 'agentic', role: 'Doc Bee', prompt: 'Generate completion report.' }, + { id: 'failed', agent: 'agentic', role: 'Doc Bee', prompt: 'Generate failure report with root cause.' }, + ], + edges: [ + { from: 'pending', to: 'executing' }, + { from: 'executing', to: 'reviewing', condition: 'success' }, + { from: 'executing', to: 'failed', condition: 'exhausted' }, + { from: 'reviewing', to: 'completed', condition: 'pass' }, + { from: 'reviewing', to: 'executing', condition: 'fix' }, + { from: 'reviewing', to: 'escalated', condition: 'max_rounds' }, + { from: 'escalated', to: 'executing', condition: 'confirm' }, + { from: 'escalated', to: 'failed', condition: 'abandon' }, + ], + }, + { + id: 'deep-research', + name: 'Deep Research', + description: '6-phase research pipeline with parallel specialists, debate, and arbitration.', + complexityLevel: 6, + nodes: [ + { id: 'planner', agent: 'Plan', role: 'Planner', prompt: 'Query understanding, ambiguity detection, sub-question decomposition.' }, + { id: 'primary', agent: 'agentic', role: 'Primary Source', prompt: 'Primary source specialist research.' }, + { id: 'news', agent: 'agentic', role: 'News Specialist', prompt: 'News and timeline research.' }, + { id: 'expert', agent: 'agentic', role: 'Expert Opinion', prompt: 'Expert opinion research.' }, + { id: 'counter', agent: 'agentic', role: 'Counter Evidence', prompt: 'Counter-evidence research.' }, + { id: 'advocate', agent: 'agentic', role: 'Advocate', prompt: 'Defend findings in adversarial debate.' }, + { id: 'critic', agent: 'agentic', role: 'Critic', prompt: 'Challenge findings in adversarial debate.' }, + { id: 'fact-checker', agent: 'agentic', role: 'Fact Checker', prompt: 'Resolve conflicts into HARD_CONFLICT / GENUINE_UNCERTAINTY / UNVERIFIED.' }, + { id: 'arbitrator', agent: 'DeepReview', role: 'Arbitrator', prompt: 'Research Manager arbitration with verdict markers.', gate: true }, + { id: 'reporter', agent: 'agentic', role: 'Reporter', prompt: 'Generate final report with citation index.' }, + ], + edges: [ + { from: 'planner', to: 'primary' }, + { from: 'planner', to: 'news' }, + { from: 'planner', to: 'expert' }, + { from: 'planner', to: 'counter' }, + { from: 'primary', to: 'advocate' }, + { from: 'news', to: 'advocate' }, + { from: 'expert', to: 'advocate' }, + { from: 'counter', to: 'critic' }, + { from: 'advocate', to: 'fact-checker' }, + { from: 'critic', to: 'fact-checker' }, + { from: 'fact-checker', to: 'arbitrator' }, + { from: 'arbitrator', to: 'reporter', condition: 'pass' }, + { from: 'arbitrator', to: 'fact-checker', condition: 'contest' }, + ], + }, + { + id: 'react-loop', + name: 'ReAct Loop', + description: 'Thought → Action → Observation loop with stop condition.', + complexityLevel: 1, + nodes: [ + { id: 'react-agent', agent: 'agentic', role: 'ReAct Agent', prompt: 'Think → Act → Observe loop until stop condition or final answer.' }, + ], + edges: [], + }, + { + id: 'plan-exec-reflect', + name: 'Plan-Execute-Reflect', + description: 'Plan → Execute step by step → Draft → Reflect and critique → Refine or stop.', + complexityLevel: 3, + nodes: [ + { id: 'planner', agent: 'Plan', role: 'Planner', prompt: 'Create a structured plan with dependencies.' }, + { id: 'executor', agent: 'agentic', role: 'Executor', prompt: 'Execute the plan step by step.' }, + { id: 'reflector', agent: 'DeepReview', role: 'Reflector', prompt: 'Reflect on the draft, critique quality, decide refine or stop.', gate: true }, + ], + edges: [ + { from: 'planner', to: 'executor' }, + { from: 'executor', to: 'reflector' }, + { from: 'reflector', to: 'executor', condition: 'refine' }, + ], + }, + { + id: 'event-driven', + name: 'Event-Driven Response', + description: 'Detect → Classify → Triage → Resolve → Postmortem. For incidents and alerts.', + complexityLevel: 4, + nodes: [ + { id: 'detector', agent: 'agentic', role: 'Detector', prompt: 'Detect event source, classify severity (P0-P4), tag category.' }, + { id: 'triage', agent: 'agentic', role: 'Triage', prompt: 'Assess impact, identify root cause, propose fix.' }, + { id: 'resolver', agent: 'agentic', role: 'Resolver', prompt: 'Apply fix, verify resolution, restore service.' }, + { id: 'postmortem', agent: 'agentic', role: 'Postmortem', prompt: 'Document timeline, identify prevention, generate report.' }, + ], + edges: [ + { from: 'detector', to: 'triage' }, + { from: 'triage', to: 'resolver' }, + { from: 'resolver', to: 'postmortem' }, + ], + }, + { + id: 'coding-agent', + name: 'Coding Agent', + description: 'Repo inspection → Scoped plan → File edits → Tests & checks → Patch & summary.', + complexityLevel: 3, + nodes: [ + { id: 'inspector', agent: 'agentic', role: 'Inspector', prompt: 'Inspect repository structure, understand codebase.' }, + { id: 'planner', agent: 'Plan', role: 'Planner', prompt: 'Create scoped implementation plan.' }, + { id: 'editor', agent: 'agentic', role: 'Editor', prompt: 'Implement changes with minimal diff.' }, + { id: 'tester', agent: 'agentic', role: 'Tester', prompt: 'Run tests and checks.' }, + { id: 'reviewer', agent: 'DeepReview', role: 'Reviewer', prompt: 'Review diff, logs, summary.', gate: true }, + ], + edges: [ + { from: 'inspector', to: 'planner' }, + { from: 'planner', to: 'editor' }, + { from: 'editor', to: 'tester' }, + { from: 'tester', to: 'reviewer' }, + { from: 'reviewer', to: 'editor', condition: 'fail' }, + ], + }, + { + id: 'dag-data-pipeline', + name: 'DAG Data Pipeline', + description: 'Extract → Transform (parallel partitions) → Validate → Load → Report.', + complexityLevel: 4, + nodes: [ + { id: 'extract', agent: 'agentic', role: 'Extractor', prompt: 'Connect source, validate connection, pull incremental data.' }, + { id: 'transform-a', agent: 'agentic', role: 'Transform A', prompt: 'Clean and transform partition A.' }, + { id: 'transform-b', agent: 'agentic', role: 'Transform B', prompt: 'Clean and transform partition B.' }, + { id: 'validator', agent: 'agentic', role: 'Validator', prompt: 'Run quality rules, check anomalies, generate quality report.' }, + { id: 'loader', agent: 'agentic', role: 'Loader', prompt: 'Connect target, write data, verify row count.' }, + { id: 'reporter', agent: 'agentic', role: 'Reporter', prompt: 'Generate execution report, log metrics.' }, + ], + edges: [ + { from: 'extract', to: 'transform-a' }, + { from: 'extract', to: 'transform-b' }, + { from: 'transform-a', to: 'validator' }, + { from: 'transform-b', to: 'validator' }, + { from: 'validator', to: 'loader' }, + { from: 'loader', to: 'reporter' }, + ], + }, + { + id: 'pr-code-review', + name: 'PR Code Review', + description: 'PR created → Lint → Code review (max 3 rounds) → Merge → Deploy.', + complexityLevel: 3, + nodes: [ + { id: 'lint', agent: 'agentic', role: 'Lint Bee', prompt: 'Check diff size, run automated lint, verify PR template.' }, + { id: 'reviewer', agent: 'DeepReview', role: 'Review Bee', prompt: 'Review logic, check test coverage, verify no regression.' }, + { id: 'merger', agent: 'agentic', role: 'Merge Bee', prompt: 'Rebase, resolve conflicts, run CI again.' }, + { id: 'deployer', agent: 'agentic', role: 'Deploy Bee', prompt: 'Deploy with promotion staging → production.' }, + ], + edges: [ + { from: 'lint', to: 'reviewer' }, + { from: 'reviewer', to: 'merger', condition: 'approved' }, + { from: 'reviewer', to: 'lint', condition: 'changes_requested' }, + { from: 'merger', to: 'deployer' }, + ], + }, + { + id: 'deploy-orchestration', + name: 'Deploy Orchestration', + description: 'Configure → Schedule → Health check → Rolling update → Self-heal loop.', + complexityLevel: 5, + nodes: [ + { id: 'configure', agent: 'agentic', role: 'Config Bee', prompt: 'Define desired state, set resource limits, configure probes.' }, + { id: 'scheduler', agent: 'agentic', role: 'Schedule Bee', prompt: 'Match nodes, pull images, start containers.' }, + { id: 'health-check', agent: 'agentic', role: 'Health Bee', prompt: 'Readiness, liveness, startup probes.' }, + { id: 'updater', agent: 'agentic', role: 'Update Bee', prompt: 'Rolling update, verify each batch, zero downtime.' }, + { id: 'healer', agent: 'agentic', role: 'Healer Bee', prompt: 'Continuous pod/node health monitoring, auto-restart/scale/migrate.' }, + ], + edges: [ + { from: 'configure', to: 'scheduler' }, + { from: 'scheduler', to: 'health-check' }, + { from: 'health-check', to: 'updater' }, + { from: 'updater', to: 'healer' }, + ], + }, + { + id: 'six-layer-runtime', + name: 'Six-Layer Agent Runtime', + description: 'Intent dispatch → State & memory → Execution sandbox → Tool boundary → Control → Endpoint.', + complexityLevel: 7, + nodes: [ + { id: 'intent', agent: 'Team', role: 'Intent Layer', prompt: 'Receive task/event, dispatch to appropriate handler, spawn sub-agents.' }, + { id: 'state', agent: 'agentic', role: 'State Layer', prompt: 'Manage working memory, persist artifacts, create checkpoints.' }, + { id: 'exec', agent: 'agentic', role: 'Exec Layer', prompt: 'Execute in sandbox/container with appropriate environment.' }, + { id: 'tool', agent: 'agentic', role: 'Tool Layer', prompt: 'Bridge to MCP/A2A/ANP protocols, call external tools.' }, + { id: 'control', agent: 'DeepReview', role: 'Control Layer', prompt: 'Policy approval, behavior evaluation, guard enforcement.' }, + { id: 'endpoint', agent: 'agentic', role: 'Endpoint Layer', prompt: 'Deliver results to user interface or API consumer.' }, + ], + edges: [ + { from: 'intent', to: 'state' }, + { from: 'state', to: 'exec' }, + { from: 'exec', to: 'tool' }, + { from: 'tool', to: 'control' }, + { from: 'control', to: 'endpoint' }, + ], + }, + { + id: 'memory-retrieval', + name: 'Memory & Retrieval', + description: 'Working memory → Promote/discard → Episodic/Semantic memory → Retrieval → Notes → Task context.', + complexityLevel: 4, + nodes: [ + { id: 'working', agent: 'agentic', role: 'Working Memory', prompt: 'Current session state, lightweight, in-process.' }, + { id: 'episodic', agent: 'agentic', role: 'Episodic Store', prompt: 'Store bounded events with structured metadata + similarity search.' }, + { id: 'semantic', agent: 'agentic', role: 'Semantic Store', prompt: 'Persist cross-task facts, dedup, normalize relations.' }, + { id: 'retrieval', agent: 'agentic', role: 'Retrieval Layer', prompt: 'Hybrid search: keyword + dense retrieval + structured filters.' }, + { id: 'context', agent: 'agentic', role: 'Context Builder', prompt: 'Assemble notes and artifacts into task context for model call.' }, + ], + edges: [ + { from: 'working', to: 'episodic' }, + { from: 'working', to: 'semantic' }, + { from: 'episodic', to: 'retrieval' }, + { from: 'semantic', to: 'retrieval' }, + { from: 'retrieval', to: 'context' }, + ], + }, + { + id: 'customer-support', + name: 'Customer Support', + description: 'Triage → Policy grounding → Draft → Guardrails → Human review queue.', + complexityLevel: 3, + nodes: [ + { id: 'triage', agent: 'agentic', role: 'Triage', prompt: 'Classify case type, urgency, sentiment, requested outcome.' }, + { id: 'policy', agent: 'agentic', role: 'Policy Agent', prompt: 'Ground response in explicit policy documents.' }, + { id: 'drafter', agent: 'agentic', role: 'Drafter', prompt: 'Draft response. Never auto-send — final decision is human.' }, + { id: 'guard', agent: 'DeepReview', role: 'Guardrail', prompt: 'Reject refunds, legal commitments, high-risk actions.', gate: true }, + ], + edges: [ + { from: 'triage', to: 'policy' }, + { from: 'policy', to: 'drafter' }, + { from: 'drafter', to: 'guard' }, + { from: 'guard', to: 'drafter', condition: 'fail' }, + ], + }, + { + id: 'evaluation-observability', + name: 'Evaluation & Observability', + description: 'Offline eval → Online monitoring → Structured traces → Failure triage.', + complexityLevel: 5, + nodes: [ + { id: 'offline', agent: 'agentic', role: 'Offline Eval', prompt: 'Run benchmarks on known tasks, compare prompts/models/tools.' }, + { id: 'online', agent: 'agentic', role: 'Online Monitor', prompt: 'Collect production signals: success rate, latency, escalation rate.' }, + { id: 'tracer', agent: 'agentic', role: 'Tracer', prompt: 'Capture structured traces: tool inputs/outputs, state transitions.' }, + { id: 'triage', agent: 'DeepReview', role: 'Triage', prompt: 'Failure triage from traces: prompt / tool / model decisions.' }, + ], + edges: [ + { from: 'offline', to: 'triage' }, + { from: 'online', to: 'triage' }, + { from: 'tracer', to: 'triage' }, + ], + }, + { + id: 'workflow-agent-hybrid', + name: 'Workflow-Agent Hybrid', + description: 'Known path → workflow. Unknown path → agent. Hybrid embeds agent nodes in workflow or vice versa.', + complexityLevel: 5, + nodes: [ + { id: 'classifier', agent: 'Plan', role: 'Classifier', prompt: 'Evaluate: is the path known and rules stable (workflow) or unknown/variable (agent)?' }, + { id: 'workflow', agent: 'agentic', role: 'Workflow', prompt: 'Predefined ordered execution for deterministic business logic.' }, + { id: 'agent-node', agent: 'agentic', role: 'Agent Node', prompt: 'Autonomous decision-making for bounded exploration and judgment.' }, + { id: 'compliance', agent: 'DeepReview', role: 'Compliance', prompt: 'Wrap agent outputs in workflow controls: compliance, approval, irreversible ops.', gate: true }, + ], + edges: [ + { from: 'classifier', to: 'workflow' }, + { from: 'classifier', to: 'agent-node' }, + { from: 'agent-node', to: 'compliance' }, + { from: 'workflow', to: 'compliance' }, + ], + }, +]; + +export default PATTERNS; diff --git a/src/web-ui/src/app/scenes/agents/hooks/useAgentsList.ts b/src/web-ui/src/app/scenes/agents/hooks/useAgentsList.ts index f09846b3e..b0d882caa 100644 --- a/src/web-ui/src/app/scenes/agents/hooks/useAgentsList.ts +++ b/src/web-ui/src/app/scenes/agents/hooks/useAgentsList.ts @@ -16,10 +16,11 @@ import { useNotification } from '@/shared/notification-system'; import type { DynamicToolInfo } from '@/shared/types/agent-api'; import type { AgentWithCapabilities } from '../agentsStore'; import { enrichCapabilities } from '../utils'; -import { HIDDEN_AGENT_IDS, isAgentInOverviewZone } from '../agentVisibility'; +import { HIDDEN_AGENT_IDS, isAgentInOverviewZone, ACP_CORE_AGENT_PREFIX } from '../agentVisibility'; import { useCurrentWorkspace } from '@/infrastructure/contexts/WorkspaceContext'; import { loadDefaultReviewTeamDefinition } from '@/shared/services/reviewTeamService'; import { globalEventBus } from '@/infrastructure/event-bus'; +import { ACPClientAPI, type AcpClientInfo } from '@/infrastructure/api/service-api/ACPClientAPI'; export type FilterLevel = 'all' | 'builtin' | 'user' | 'project' | 'external'; export type FilterType = 'all' | 'mode' | 'subagent'; @@ -203,6 +204,14 @@ export function useAgentsList({ ]).catch((): Record => ({})), ]); + // Fetch enabled ACP external agents for core zone metadata enrichment. + let acpClients: AcpClientInfo[] = []; + try { + acpClients = (await ACPClientAPI.getClients()).filter((c) => c.enabled); + } catch { + // ACP not initialized — no external agents to show. + } + const profileMap = buildProfileMap(modes); const profileEntries = Object.values(profileMap); @@ -275,7 +284,34 @@ export function useAgentsList({ }); }); - setAllAgents([...modeAgents, ...subAgents]); + const modeAgentIds = new Set(modeAgents.map((a) => a.id)); + const acpAgents: AgentWithCapabilities[] = acpClients + .filter((client) => !modeAgentIds.has(`${ACP_CORE_AGENT_PREFIX}${client.id}`)) + .map((client): AgentWithCapabilities => { + const agentId = `${ACP_CORE_AGENT_PREFIX}${client.id}`; + const displayName = client.name || client.id; + const acpToolName = `${ACP_CORE_AGENT_PREFIX}${client.id}__prompt`; + const defaultTools = [acpToolName, 'Read', 'Grep', 'Glob', 'LS']; + return enrichCapabilities({ + key: `acp::${client.id}`, + id: agentId, + name: displayName, + description: client.command + ? `External ACP agent (${client.command}${client.args.length ? ' ' + client.args.join(' ') : ''})` + : `External ACP agent — ${client.status}`, + isReadonly: client.readonly, + isReview: false, + toolCount: defaultTools.length, + defaultTools, + defaultEnabled: client.enabled, + effectiveEnabled: client.enabled, + source: 'builtin' as AgentSource, + capabilities: [], + agentKind: 'subagent' as const, + }); + }); + + setAllAgents([...modeAgents, ...subAgents, ...acpAgents]); setAvailableTools(tools); setConfiguredModels(models); setModeProfiles(profileMap); diff --git a/src/web-ui/src/infrastructure/api/service-api/LegionPresetAPI.ts b/src/web-ui/src/infrastructure/api/service-api/LegionPresetAPI.ts new file mode 100644 index 000000000..dff346d9c --- /dev/null +++ b/src/web-ui/src/infrastructure/api/service-api/LegionPresetAPI.ts @@ -0,0 +1,45 @@ +import { api } from './ApiClient'; + +export interface LegionPresetNode { + id: string; + agent: string; + role: string; + prompt: string; + gate?: boolean; +} + +export interface LegionPresetEdge { + from: string; + to: string; + condition?: string; +} + +export interface LegionPreset { + id: string; + name: string; + description: string; + nodes: LegionPresetNode[]; + edges: LegionPresetEdge[]; +} + +export const LegionPresetAPI = { + async listPresets(): Promise { + return api.invoke('list_legion_presets'); + }, + + async getPreset(id: string): Promise { + return api.invoke('get_legion_preset', { id }); + }, + + async createPreset(preset: LegionPreset): Promise { + return api.invoke('create_legion_preset', { preset }); + }, + + async updatePreset(preset: LegionPreset): Promise { + return api.invoke('update_legion_preset', { preset }); + }, + + async deletePreset(id: string): Promise { + return api.invoke('delete_legion_preset', { id }); + }, +}; diff --git a/src/web-ui/src/locales/en-US/scenes/agents.json b/src/web-ui/src/locales/en-US/scenes/agents.json index ccce7c623..8a575bfd5 100644 --- a/src/web-ui/src/locales/en-US/scenes/agents.json +++ b/src/web-ui/src/locales/en-US/scenes/agents.json @@ -42,6 +42,9 @@ }, "computerUse": { "role": "Desktop automation agent" + }, + "acpExternal": { + "role": "ACP Agent" } } }, @@ -285,5 +288,33 @@ "Debug": "Debug mode: systematically diagnose and fix errors in code", "Claw": "Claw mode: extract and integrate information from external sources", "Team": "Team mode: coordinate multiple agents to collaboratively complete complex tasks" + }, + "legionPattern": { + "back": "Back", + "choosePattern": "Choose a Pattern", + "orchestrationPatterns": "Orchestration Patterns", + "overview": "Overview", + "complexity": "Complexity: L{{level}}", + "nodesCount": "{{count}} nodes", + "edgesCount": "{{count}} edges", + "noEdges": "No edges (self-contained agent)", + "usePattern": "Use Pattern", + "gate": "GATE", + "nodes": "Nodes ({{count}})", + "edges": "Edges ({{count}})", + "meta": { + "nodes": "nodes", + "edges": "edges", + "gate": "gate" + }, + "complexityLabel": { + "l1": "L1", + "l2l3": "L2-L3", + "l3": "L3", + "l4": "L4", + "l5l6": "L5-L6", + "l6": "L6", + "l7": "L7" + } } } diff --git a/src/web-ui/src/locales/zh-CN/scenes/agents.json b/src/web-ui/src/locales/zh-CN/scenes/agents.json index 0aa2eacff..01a0e18f3 100644 --- a/src/web-ui/src/locales/zh-CN/scenes/agents.json +++ b/src/web-ui/src/locales/zh-CN/scenes/agents.json @@ -42,6 +42,9 @@ }, "computerUse": { "role": "电脑操作智能体" + }, + "acpExternal": { + "role": "ACP智能体" } } }, @@ -285,5 +288,33 @@ "Debug": "调试模式:系统性地诊断和修复代码中的错误", "Claw": "抓取模式:从外部源提取和整合信息", "Team": "团队模式:协调多个智能体协同完成复杂任务" + }, + "legionPattern": { + "back": "返回", + "choosePattern": "选择模式", + "orchestrationPatterns": "编排模式", + "overview": "概览", + "complexity": "复杂度:L{{level}}", + "nodesCount": "{{count}} 个节点", + "edgesCount": "{{count}} 条边", + "noEdges": "无边关系(独立智能体)", + "usePattern": "使用此模式", + "gate": "关卡", + "nodes": "节点({{count}})", + "edges": "边({{count}})", + "meta": { + "nodes": "节点", + "edges": "边", + "gate": "关卡" + }, + "complexityLabel": { + "l1": "L1", + "l2l3": "L2-L3", + "l3": "L3", + "l4": "L4", + "l5l6": "L5-L6", + "l6": "L6", + "l7": "L7" + } } } diff --git a/src/web-ui/src/locales/zh-TW/scenes/agents.json b/src/web-ui/src/locales/zh-TW/scenes/agents.json index 4657b5901..921b4ec8c 100644 --- a/src/web-ui/src/locales/zh-TW/scenes/agents.json +++ b/src/web-ui/src/locales/zh-TW/scenes/agents.json @@ -42,6 +42,9 @@ }, "computerUse": { "role": "電腦操作智能體" + }, + "acpExternal": { + "role": "ACP智能體" } } }, @@ -285,5 +288,33 @@ "Debug": "除錯模式:系統性地診斷和修復程式碼中的錯誤", "Claw": "抓取模式:從外部來源提取和整合資訊", "Team": "團隊模式:協調多個智慧體協同完成複雜任務" + }, + "legionPattern": { + "back": "返回", + "choosePattern": "選擇模式", + "orchestrationPatterns": "編排模式", + "overview": "概覽", + "complexity": "複雜度:L{{level}}", + "nodesCount": "{{count}} 個節點", + "edgesCount": "{{count}} 條邊", + "noEdges": "無邊關係(獨立智慧體)", + "usePattern": "使用此模式", + "gate": "關卡", + "nodes": "節點({{count}})", + "edges": "邊({{count}})", + "meta": { + "nodes": "節點", + "edges": "邊", + "gate": "關卡" + }, + "complexityLabel": { + "l1": "L1", + "l2l3": "L2-L3", + "l3": "L3", + "l4": "L4", + "l5l6": "L5-L6", + "l6": "L6", + "l7": "L7" + } } }