From 7fa3c215ea18afe1f77fdf26329ee8eb0c9307af Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:44:57 -0400 Subject: [PATCH] feat(ui): move agents library to Intelligence tab (#3438) --- .../intelligence/IntelligenceAgentsTab.tsx | 115 +++++++++++ .../intelligence/IntelligenceTasksTab.tsx | 62 ------ .../__tests__/IntelligenceAgentsTab.test.tsx | 179 ++++++++++++++++++ .../__tests__/IntelligenceTasksTab.test.tsx | 63 ------ app/src/lib/i18n/ar.ts | 5 +- app/src/lib/i18n/bn.ts | 5 +- app/src/lib/i18n/de.ts | 5 +- app/src/lib/i18n/en.ts | 5 +- app/src/lib/i18n/es.ts | 5 +- app/src/lib/i18n/fr.ts | 5 +- app/src/lib/i18n/hi.ts | 5 +- app/src/lib/i18n/id.ts | 5 +- app/src/lib/i18n/it.ts | 5 +- app/src/lib/i18n/ko.ts | 5 +- app/src/lib/i18n/pl.ts | 7 +- app/src/lib/i18n/pt.ts | 5 +- app/src/lib/i18n/ru.ts | 5 +- app/src/lib/i18n/zh-CN.ts | 5 +- app/src/pages/Intelligence.test.tsx | 2 +- app/src/pages/Intelligence.tsx | 9 +- 20 files changed, 359 insertions(+), 143 deletions(-) create mode 100644 app/src/components/intelligence/IntelligenceAgentsTab.tsx create mode 100644 app/src/components/intelligence/__tests__/IntelligenceAgentsTab.test.tsx diff --git a/app/src/components/intelligence/IntelligenceAgentsTab.tsx b/app/src/components/intelligence/IntelligenceAgentsTab.tsx new file mode 100644 index 000000000..8714f3deb --- /dev/null +++ b/app/src/components/intelligence/IntelligenceAgentsTab.tsx @@ -0,0 +1,115 @@ +import debug from 'debug'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { useT } from '../../lib/i18n/I18nContext'; +import type { AgentDefinitionDisplay } from '../../services/api/agentLibraryApi'; +import { threadApi } from '../../services/api/threadApi'; +import { chatSend } from '../../services/chatService'; +import { selectActiveAgentProfileId } from '../../store/agentProfileSlice'; +import { beginInferenceTurn, setToolTimelineForThread } from '../../store/chatRuntimeSlice'; +import { useAppDispatch, useAppSelector } from '../../store/hooks'; +import { + loadThreadMessages, + loadThreads, + setActiveThread, + setSelectedThread, +} from '../../store/threadSlice'; +import type { ThreadMessage } from '../../types/thread'; +import AgentsLibraryPanel from './AgentsLibraryPanel'; + +const log = debug('intelligence:agents-tab'); +const AGENT_TASK_THREAD_LABEL = 'tasks'; +const CHAT_MODEL_ID = 'reasoning-v1'; + +function agentTaskThreadTitle(title: string): string { + const trimmed = title.trim(); + const base = trimmed.length > 72 ? `${trimmed.slice(0, 69)}...` : trimmed; + return `Agent task: ${base || 'Untitled task'}`; +} + +function buildExplicitAgentPrompt(agent: AgentDefinitionDisplay, task: string): string { + return [ + `@agent:${agent.id}`, + '', + 'Run this task with the explicitly selected agent above. Treat the selected agent id as the user routing choice when resolving delegation ambiguity.', + '', + task.trim(), + ].join('\n'); +} + +export default function IntelligenceAgentsTab() { + const { t } = useT(); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const selectedAgentProfileId = useAppSelector(selectActiveAgentProfileId); + const uiLocale = useAppSelector(state => state.locale?.current ?? 'en'); + const mountedRef = useRef(true); + + useEffect(() => { + return () => { + mountedRef.current = false; + }; + }, []); + + const [runningAgentId, setRunningAgentId] = useState(null); + const [actionError, setActionError] = useState(null); + + const handleRunAgentTask = useCallback( + async (agent: AgentDefinitionDisplay, task: string) => { + if (runningAgentId) return; + setRunningAgentId(agent.id); + setActionError(null); + const now = new Date().toISOString(); + const launchPrompt = buildExplicitAgentPrompt(agent, task); + const titleBase = task.trim().slice(0, 64) || agent.display_name; + try { + const thread = await threadApi.createNewThread([AGENT_TASK_THREAD_LABEL, 'agent-library']); + await threadApi.updateTitle(thread.id, agentTaskThreadTitle(titleBase)); + const userMessage: ThreadMessage = { + id: `msg_${globalThis.crypto?.randomUUID ? globalThis.crypto.randomUUID() : `${Date.now()}`}`, + content: launchPrompt, + type: 'text', + extraMetadata: { source: 'agent-library', explicitAgentId: agent.id }, + sender: 'user', + createdAt: now, + }; + await threadApi.appendMessage(thread.id, userMessage); + + dispatch(setSelectedThread(thread.id)); + dispatch(setToolTimelineForThread({ threadId: thread.id, entries: [] })); + dispatch(beginInferenceTurn({ threadId: thread.id })); + dispatch(setActiveThread(thread.id)); + void dispatch(loadThreads()); + void dispatch(loadThreadMessages(thread.id)); + navigate('/chat'); + + await chatSend({ + threadId: thread.id, + message: launchPrompt, + model: CHAT_MODEL_ID, + profileId: selectedAgentProfileId, + locale: uiLocale, + }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + log('run explicit agent task failed agent=%s: %s', agent.id, msg); + if (mountedRef.current) setActionError(t('intelligence.agents.runFailed')); + } finally { + if (mountedRef.current) setRunningAgentId(null); + } + }, + [dispatch, navigate, runningAgentId, selectedAgentProfileId, t, uiLocale] + ); + + return ( +
+ {actionError && ( +
+ {actionError} +
+ )} + +
+ ); +} diff --git a/app/src/components/intelligence/IntelligenceTasksTab.tsx b/app/src/components/intelligence/IntelligenceTasksTab.tsx index 93ceaee89..4620c6dc6 100644 --- a/app/src/components/intelligence/IntelligenceTasksTab.tsx +++ b/app/src/components/intelligence/IntelligenceTasksTab.tsx @@ -29,7 +29,6 @@ import { useNavigate } from 'react-router-dom'; import { useT } from '../../lib/i18n/I18nContext'; import { TaskKanbanBoard } from '../../pages/conversations/components/TaskKanbanBoard'; import { isTaskThread } from '../../pages/conversations/utils/threadFilter'; -import type { AgentDefinitionDisplay } from '../../services/api/agentLibraryApi'; import { threadApi } from '../../services/api/threadApi'; import { TASK_SOURCES_THREAD_ID, @@ -48,7 +47,6 @@ import { } from '../../store/threadSlice'; import type { ThreadMessage } from '../../types/thread'; import type { TaskBoard, TaskBoardCard, TaskBoardCardStatus } from '../../types/turnState'; -import AgentsLibraryPanel from './AgentsLibraryPanel'; import { UserTaskComposer } from './UserTaskComposer'; const log = debug('intelligence:tasks'); @@ -117,16 +115,6 @@ function buildAgentTaskPrompt( return lines.join('\n'); } -function buildExplicitAgentPrompt(agent: AgentDefinitionDisplay, task: string): string { - return [ - `@agent:${agent.id}`, - '', - 'Run this task with the explicitly selected agent above. Treat the selected agent id as the user routing choice when resolving delegation ambiguity.', - '', - task.trim(), - ].join('\n'); -} - export default function IntelligenceTasksTab() { const { t } = useT(); const dispatch = useAppDispatch(); @@ -143,7 +131,6 @@ export default function IntelligenceTasksTab() { const [refiningCard, setRefiningCard] = useState(null); const [workingCardId, setWorkingCardId] = useState(null); const [mutatingCardId, setMutatingCardId] = useState(null); - const [runningAgentId, setRunningAgentId] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [actionError, setActionError] = useState(null); @@ -424,53 +411,6 @@ export default function IntelligenceTasksTab() { [dispatch, navigate, personalBoard, selectedAgentProfileId, t, uiLocale, workingCardId] ); - const handleRunAgentTask = useCallback( - async (agent: AgentDefinitionDisplay, task: string) => { - if (runningAgentId) return; - setRunningAgentId(agent.id); - setActionError(null); - const now = new Date().toISOString(); - const launchPrompt = buildExplicitAgentPrompt(agent, task); - const titleBase = task.trim().slice(0, 64) || agent.display_name; - try { - const thread = await threadApi.createNewThread([AGENT_TASK_THREAD_LABEL, 'agent-library']); - await threadApi.updateTitle(thread.id, agentTaskThreadTitle(titleBase)); - const userMessage: ThreadMessage = { - id: `msg_${globalThis.crypto?.randomUUID ? globalThis.crypto.randomUUID() : `${Date.now()}`}`, - content: launchPrompt, - type: 'text', - extraMetadata: { source: 'agent-library', explicitAgentId: agent.id }, - sender: 'user', - createdAt: now, - }; - await threadApi.appendMessage(thread.id, userMessage); - - dispatch(setSelectedThread(thread.id)); - dispatch(setToolTimelineForThread({ threadId: thread.id, entries: [] })); - dispatch(beginInferenceTurn({ threadId: thread.id })); - dispatch(setActiveThread(thread.id)); - void dispatch(loadThreads()); - void dispatch(loadThreadMessages(thread.id)); - navigate('/chat'); - - await chatSend({ - threadId: thread.id, - message: launchPrompt, - model: CHAT_MODEL_ID, - profileId: selectedAgentProfileId, - locale: uiLocale, - }); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - log('run explicit agent task failed agent=%s: %s', agent.id, msg); - if (mountedRef.current) setActionError(t('intelligence.agents.runFailed')); - } finally { - if (mountedRef.current) setRunningAgentId(null); - } - }, - [dispatch, navigate, runningAgentId, selectedAgentProfileId, t, uiLocale] - ); - const handleApproveSourcePlan = useCallback( async (sourceCard: TaskBoardCard, draft: RefinedTaskDraft) => { const now = new Date().toISOString(); @@ -621,8 +561,6 @@ export default function IntelligenceTasksTab() { )} - - {/* Personal board — always present so users can manage their own tasks. */}
diff --git a/app/src/components/intelligence/__tests__/IntelligenceAgentsTab.test.tsx b/app/src/components/intelligence/__tests__/IntelligenceAgentsTab.test.tsx new file mode 100644 index 000000000..d1176d422 --- /dev/null +++ b/app/src/components/intelligence/__tests__/IntelligenceAgentsTab.test.tsx @@ -0,0 +1,179 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; + +const hoisted = vi.hoisted(() => ({ + createNewThread: vi.fn(), + updateTitle: vi.fn(), + appendMessage: vi.fn(), + chatSend: vi.fn(), + navigate: vi.fn(), + dispatch: vi.fn(), + selectorResult: { + agentProfiles: { activeProfileId: 'agent-profile-1' }, + locale: { current: 'en' }, + }, +})); + +vi.mock('../../../services/api/threadApi', () => ({ + threadApi: { + createNewThread: hoisted.createNewThread, + updateTitle: hoisted.updateTitle, + appendMessage: hoisted.appendMessage, + }, +})); + +vi.mock('../../../services/chatService', () => ({ chatSend: hoisted.chatSend })); + +vi.mock('../../../store/hooks', () => ({ + useAppSelector: (selector: (state: typeof hoisted.selectorResult) => unknown) => + selector(hoisted.selectorResult), + useAppDispatch: () => hoisted.dispatch, +})); + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { ...actual, useNavigate: () => hoisted.navigate }; +}); + +vi.mock('../AgentsLibraryPanel', () => ({ + default: ({ + onRunAgentTask, + runningAgentId, + }: { + onRunAgentTask: ( + agent: { + id: string; + display_name: string; + tier: string; + model: { kind: string; value: string }; + direct_tool_count: number; + direct_tool_names: string[]; + uses_wildcard_tools: boolean; + subagent_ids: string[]; + includes_profile: boolean; + includes_memory_md: boolean; + includes_memory_context: boolean; + can_run_as_user_facing_worker: boolean; + write_capable: boolean; + source: string; + }, + task: string + ) => Promise; + runningAgentId?: string | null; + }) => ( +
+ {runningAgentId ?? 'idle'} + +
+ ), +})); + +async function importTab() { + const mod = await import('../IntelligenceAgentsTab'); + return mod.default; +} + +describe('IntelligenceAgentsTab', () => { + beforeEach(() => { + vi.resetModules(); + hoisted.createNewThread.mockReset(); + hoisted.updateTitle.mockReset(); + hoisted.appendMessage.mockReset(); + hoisted.chatSend.mockReset(); + hoisted.navigate.mockReset(); + hoisted.dispatch.mockReset(); + hoisted.selectorResult.agentProfiles.activeProfileId = 'agent-profile-1'; + hoisted.selectorResult.locale.current = 'en'; + hoisted.createNewThread.mockResolvedValue({ + id: 'thread-agent-task', + title: 'Agent task', + labels: ['tasks', 'agent-library'], + chatId: null, + isActive: true, + messageCount: 0, + lastMessageAt: '2026-01-01T00:00:00Z', + createdAt: '2026-01-01T00:00:00Z', + }); + hoisted.updateTitle.mockResolvedValue({ + id: 'thread-agent-task', + title: 'Agent task: Find the current API docs', + labels: ['tasks', 'agent-library'], + chatId: null, + isActive: true, + messageCount: 0, + lastMessageAt: '2026-01-01T00:00:00Z', + createdAt: '2026-01-01T00:00:00Z', + }); + hoisted.appendMessage.mockResolvedValue({ + id: 'msg-1', + content: '@agent:researcher', + type: 'text', + extraMetadata: {}, + sender: 'user', + createdAt: '2026-01-01T00:00:00Z', + }); + hoisted.chatSend.mockResolvedValue(undefined); + }); + + test('starts a labeled task thread from an explicit library agent selection', async () => { + const Tab = await importTab(); + render(); + + fireEvent.click(screen.getByRole('button', { name: /run researcher/i })); + + await waitFor(() => + expect(hoisted.createNewThread).toHaveBeenCalledWith(['tasks', 'agent-library']) + ); + expect(hoisted.updateTitle).toHaveBeenCalledWith( + 'thread-agent-task', + 'Agent task: Find the current API docs' + ); + expect(hoisted.appendMessage).toHaveBeenCalledWith( + 'thread-agent-task', + expect.objectContaining({ + content: expect.stringContaining('@agent:researcher'), + sender: 'user', + extraMetadata: expect.objectContaining({ + source: 'agent-library', + explicitAgentId: 'researcher', + }), + }) + ); + await waitFor(() => + expect(hoisted.chatSend).toHaveBeenCalledWith( + expect.objectContaining({ + threadId: 'thread-agent-task', + message: expect.stringContaining('Find the current API docs'), + model: 'reasoning-v1', + profileId: 'agent-profile-1', + locale: 'en', + }) + ) + ); + expect(hoisted.navigate).toHaveBeenCalledWith('/chat'); + }); +}); diff --git a/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx b/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx index 37deb8ed9..07f2de310 100644 --- a/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx +++ b/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx @@ -20,7 +20,6 @@ const hoisted = vi.hoisted(() => ({ updateTitle: vi.fn(), appendMessage: vi.fn(), chatSend: vi.fn(), - listAgentDefinitions: vi.fn(), todosList: vi.fn(), todosAdd: vi.fn(), todosEdit: vi.fn(), @@ -47,10 +46,6 @@ vi.mock('../../../services/api/threadApi', () => ({ vi.mock('../../../services/chatService', () => ({ chatSend: hoisted.chatSend })); -vi.mock('../../../services/api/agentLibraryApi', () => ({ - agentLibraryApi: { listDefinitions: hoisted.listAgentDefinitions }, -})); - vi.mock('../../../services/api/todosApi', () => ({ TASK_SOURCES_THREAD_ID: 'task-sources', USER_TASKS_THREAD_ID: 'user-tasks', @@ -186,7 +181,6 @@ describe('IntelligenceTasksTab', () => { hoisted.updateTitle.mockReset(); hoisted.appendMessage.mockReset(); hoisted.chatSend.mockReset(); - hoisted.listAgentDefinitions.mockReset(); hoisted.todosList.mockReset(); hoisted.todosAdd.mockReset(); hoisted.todosEdit.mockReset(); @@ -234,25 +228,6 @@ describe('IntelligenceTasksTab', () => { // by default — otherwise the awaited `.catch()` chain stalls the handler // before it reaches chatSend. hoisted.todosSetSessionThread.mockResolvedValue(makeBoard('user-tasks', [])); - hoisted.listAgentDefinitions.mockResolvedValue([ - { - id: 'researcher', - display_name: 'Researcher', - when_to_use: 'Use for focused research.', - tier: 'worker', - model: { kind: 'hint', value: 'reasoning' }, - direct_tool_count: 1, - direct_tool_names: ['web_search'], - uses_wildcard_tools: false, - subagent_ids: [], - includes_profile: false, - includes_memory_md: false, - includes_memory_context: false, - can_run_as_user_facing_worker: true, - write_capable: false, - source: 'builtin', - }, - ]); hoisted.todosList.mockImplementation((threadId: string) => Promise.resolve(makeBoard(threadId, [])) ); @@ -599,44 +574,6 @@ describe('IntelligenceTasksTab', () => { ); }); - test('starts a labeled task thread from an explicit library agent selection', async () => { - hoisted.todosUpdateStatus.mockResolvedValue(makeBoard('user-tasks', [])); - - vi.resetModules(); - const Tab = await importTab(); - renderTab(Tab); - await waitFor(() => { - expect(screen.getByText('Researcher')).toBeInTheDocument(); - }); - - fireEvent.change(screen.getByPlaceholderText(/task for this agent/i), { - target: { value: 'Find the current API docs' }, - }); - fireEvent.click(screen.getByRole('button', { name: /Run task/i })); - - await waitFor(() => - expect(hoisted.createNewThread).toHaveBeenCalledWith(['tasks', 'agent-library']) - ); - expect(hoisted.appendMessage).toHaveBeenCalledWith( - 'thread-agent-task', - expect.objectContaining({ - content: expect.stringContaining('@agent:researcher'), - extraMetadata: expect.objectContaining({ - source: 'agent-library', - explicitAgentId: 'researcher', - }), - }) - ); - expect(hoisted.chatSend).toHaveBeenCalledWith( - expect.objectContaining({ - threadId: 'thread-agent-task', - message: expect.stringContaining('Find the current API docs'), - model: 'reasoning-v1', - profileId: 'agent-profile-1', - }) - ); - }); - test('deletes a personal card via the todos RPC', async () => { hoisted.todosList.mockImplementation((threadId: string) => Promise.resolve( diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 2acdedf2e..42dcfadf1 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -280,10 +280,13 @@ const messages: TranslationMap = { 'memory.empty': 'لا توجد ذكريات بعد. تُنشأ الذكريات تلقائيًا أثناء تفاعلك.', 'memory.tab.memory': 'الذاكرة', 'memory.tab.memoryTree': 'شجرة الذاكرة', - 'memory.tab.tasks': 'مهام الوكيل', + 'memory.tab.tasks': 'المهام', 'memory.tab.tasksDescription': 'أنشئ المهام وتتبعها — مهامك الخاصة بالإضافة إلى اللوحات التي يبنيها وكلاؤك عبر المحادثات.', 'memory.tab.subconscious': 'اللاوعي', + 'memory.tab.agents': 'المكتبة', + 'memory.tab.agentsDescription': + 'تصفّح وشغّل الوكلاء المتاحين — لكل منهم أدواته وقدراته ومجال تركيزه.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 1c8dbc65c..85a5b1f61 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -283,10 +283,13 @@ const messages: TranslationMap = { 'এখনো কোনো মেমোরি নেই। আপনি যত ইন্টারঅ্যাক্ট করবেন, মেমোরি স্বয়ংক্রিয়ভাবে তৈরি হবে।', 'memory.tab.memory': 'মেমোরি', 'memory.tab.memoryTree': 'মেমোরি ট্রি', - 'memory.tab.tasks': 'এজেন্ট টাস্ক', + 'memory.tab.tasks': 'টাস্ক', 'memory.tab.tasksDescription': 'টাস্ক তৈরি করুন এবং ট্র্যাক করুন — আপনার নিজের কাজের তালিকা এবং এজেন্টরা কথোপকথন জুড়ে যে বোর্ডগুলি তৈরি করে।', 'memory.tab.subconscious': 'সাবকনশাস', + 'memory.tab.agents': 'লাইব্রেরি', + 'memory.tab.agentsDescription': + 'আপনার উপলব্ধ এজেন্টগুলি ব্রাউজ করুন এবং চালান — প্রতিটির নিজস্ব টুল, ক্ষমতা এবং ফোকাস এলাকা রয়েছে।', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 6641c12e5..c632e4b55 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -292,10 +292,13 @@ const messages: TranslationMap = { 'Noch keine Erinnerungen. Erinnerungen werden automatisch erstellt, während du interagierst.', 'memory.tab.memory': 'Erinnerung', 'memory.tab.memoryTree': 'Erinnerungsbaum', - 'memory.tab.tasks': 'Agent-Aufgaben', + 'memory.tab.tasks': 'Aufgaben', 'memory.tab.tasksDescription': 'Aufgaben erstellen und verfolgen – eigene To-dos sowie die Boards, die Agenten in Gesprächen anlegen.', 'memory.tab.subconscious': 'Unterbewusstsein', + 'memory.tab.agents': 'Bibliothek', + 'memory.tab.agentsDescription': + 'Verfügbare Agenten durchsuchen und starten — jeder mit eigenen Werkzeugen, Fähigkeiten und Schwerpunkten.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index 8e9999aa5..ad75b7648 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -308,10 +308,13 @@ const en: TranslationMap = { 'memory.empty': 'No memories yet. Memories are created automatically as you interact.', 'memory.tab.memory': 'Memory', 'memory.tab.memoryTree': 'Memory Tree', - 'memory.tab.tasks': 'Agent Tasks', + 'memory.tab.tasks': 'Tasks', 'memory.tab.tasksDescription': 'Create and track tasks — your own to-dos plus the boards your agents build across conversations.', 'memory.tab.subconscious': 'Subconscious', + 'memory.tab.agents': 'Library', + 'memory.tab.agentsDescription': + 'Browse and run your available agents — each with its own tools, capabilities, and focus area.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Reusable, runnable procedures — a goal plus the steps to reach it. Create one, install from a URL, or open a workflow to run it.', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index c2f1f2bc7..4cd3ea07a 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -292,10 +292,13 @@ const messages: TranslationMap = { 'memory.empty': 'Sin recuerdos aún. Los recuerdos se crean automáticamente mientras interactúas.', 'memory.tab.memory': 'Memoria', 'memory.tab.memoryTree': 'Árbol de memoria', - 'memory.tab.tasks': 'Tareas del agente', + 'memory.tab.tasks': 'Tareas', 'memory.tab.tasksDescription': 'Crea y realiza un seguimiento de tareas: tus propios pendientes más los tableros que tus agentes crean a lo largo de las conversaciones.', 'memory.tab.subconscious': 'Subconsciente', + 'memory.tab.agents': 'Biblioteca', + 'memory.tab.agentsDescription': + 'Explora y ejecuta los agentes disponibles — cada uno con sus propias herramientas, capacidades y área de enfoque.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index c54401200..0e8697f0d 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -291,10 +291,13 @@ const messages: TranslationMap = { "Aucun souvenir pour l'instant. Les souvenirs sont créés automatiquement au fil de tes interactions.", 'memory.tab.memory': 'Mémoire', 'memory.tab.memoryTree': 'Arbre de mémoire', - 'memory.tab.tasks': "Tâches de l'agent", + 'memory.tab.tasks': 'Tâches', 'memory.tab.tasksDescription': 'Créez et suivez des tâches — vos propres listes de choses à faire ainsi que les tableaux que vos agents construisent au fil des conversations.', 'memory.tab.subconscious': 'Subconscient', + 'memory.tab.agents': 'Bibliothèque', + 'memory.tab.agentsDescription': + 'Parcourez et exécutez vos agents disponibles — chacun avec ses propres outils, capacités et domaine de spécialisation.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index f33dd836a..1c74c3b93 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -282,10 +282,13 @@ const messages: TranslationMap = { 'memory.empty': 'अभी कोई मेमोरी नहीं है। बातचीत के दौरान मेमोरी अपने आप बनती है।', 'memory.tab.memory': 'मेमोरी', 'memory.tab.memoryTree': 'मेमोरी ट्री', - 'memory.tab.tasks': 'एजेंट कार्य', + 'memory.tab.tasks': 'कार्य', 'memory.tab.tasksDescription': 'कार्य बनाएं और ट्रैक करें — आपके अपने टू-डू और साथ ही वे बोर्ड जो आपके एजेंट वार्तालापों में बनाते हैं।', 'memory.tab.subconscious': 'सबकॉन्शस', + 'memory.tab.agents': 'लाइब्रेरी', + 'memory.tab.agentsDescription': + 'अपने उपलब्ध एजेंट ब्राउज़ करें और चलाएं — प्रत्येक के पास अपने टूल, क्षमताएं और फोकस क्षेत्र हैं।', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 85b4e6a89..e69d02c8d 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -284,10 +284,13 @@ const messages: TranslationMap = { 'memory.empty': 'Belum ada memori. Memori dibuat otomatis saat Anda berinteraksi.', 'memory.tab.memory': 'Memori', 'memory.tab.memoryTree': 'Pohon Memori', - 'memory.tab.tasks': 'Tugas Agen', + 'memory.tab.tasks': 'Tugas', 'memory.tab.tasksDescription': 'Buat dan lacak tugas — to-do Anda sendiri beserta papan yang dibangun agen Anda di berbagai percakapan.', 'memory.tab.subconscious': 'Bawah sadar', + 'memory.tab.agents': 'Perpustakaan', + 'memory.tab.agentsDescription': + 'Telusuri dan jalankan agen yang tersedia — masing-masing dengan alat, kemampuan, dan area fokusnya sendiri.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index 700854679..c3d96cc46 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -289,10 +289,13 @@ const messages: TranslationMap = { 'Nessuna memoria ancora. Le memorie vengono create automaticamente mentre interagisci.', 'memory.tab.memory': 'Memoria', 'memory.tab.memoryTree': 'Albero della memoria', - 'memory.tab.tasks': 'Attività agente', + 'memory.tab.tasks': 'Attività', 'memory.tab.tasksDescription': 'Crea e monitora le attività — i tuoi to-do personali e le board create dagli agenti nelle conversazioni.', 'memory.tab.subconscious': 'Subconscio', + 'memory.tab.agents': 'Libreria', + 'memory.tab.agentsDescription': + 'Sfoglia e avvia gli agenti disponibili — ognuno con i propri strumenti, capacità e area di specializzazione.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 1fb12b207..d74f20669 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -282,10 +282,13 @@ const messages: TranslationMap = { 'memory.empty': '아직 메모리가 없습니다. 메모리는 상호작용하면서 자동으로 생성됩니다.', 'memory.tab.memory': '메모리', 'memory.tab.memoryTree': '메모리 트리', - 'memory.tab.tasks': '에이전트 작업', + 'memory.tab.tasks': '작업', 'memory.tab.tasksDescription': '작업을 만들고 추적하세요 — 본인의 할 일 목록과 에이전트가 대화를 통해 구성한 보드가 모두 포함됩니다.', 'memory.tab.subconscious': '잠재의식', + 'memory.tab.agents': '라이브러리', + 'memory.tab.agentsDescription': + '사용 가능한 에이전트를 탐색하고 실행하세요 — 각 에이전트는 고유한 도구, 기능 및 전문 분야를 갖추고 있습니다.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index e749afb0b..c84da17f2 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -287,10 +287,13 @@ const messages: TranslationMap = { 'Brak wspomnień. Wspomnienia powstają automatycznie podczas korzystania z aplikacji.', 'memory.tab.memory': 'Pamięć', 'memory.tab.memoryTree': 'Drzewo pamięci', - 'memory.tab.tasks': 'Zadania agenta', + 'memory.tab.tasks': 'Zadania', 'memory.tab.tasksDescription': 'Twórz i śledź zadania — własne listy do zrobienia oraz tablice, które Twoi agenci budują w rozmowach.', 'memory.tab.subconscious': 'Podświadomość', + 'memory.tab.agents': 'Biblioteka', + 'memory.tab.agentsDescription': + 'Przeglądaj i uruchamiaj dostępnych agentów — każdy z własnymi narzędziami, możliwościami i obszarem specjalizacji.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', @@ -4716,7 +4719,7 @@ const messages: TranslationMap = { 'intelligence.agents.allTools': 'Wszystkie narzędzia', 'intelligence.agents.toolCountOne': '{count} narzędzie', 'intelligence.agents.toolCountOther': '{count} narzędzi', - 'intelligence.agents.subagentCountOne': '{count} subagent', + 'intelligence.agents.subagentCountOne': '{count} podagent', 'intelligence.agents.subagentCountOther': '{count} subagentów', 'intelligence.agents.startChat': 'Rozpocznij czat', 'intelligence.agents.startChatPrompt': diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index b07e798d3..ae6ac31f6 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -291,10 +291,13 @@ const messages: TranslationMap = { 'Nenhuma memória ainda. As memórias são criadas automaticamente conforme você interage.', 'memory.tab.memory': 'Memória', 'memory.tab.memoryTree': 'Árvore de memória', - 'memory.tab.tasks': 'Tarefas do Agente', + 'memory.tab.tasks': 'Tarefas', 'memory.tab.tasksDescription': 'Crie e acompanhe tarefas — seus próprios afazeres e os painéis que seus agentes constroem ao longo das conversas.', 'memory.tab.subconscious': 'Subconsciente', + 'memory.tab.agents': 'Biblioteca', + 'memory.tab.agentsDescription': + 'Navegue e execute os agentes disponíveis — cada um com suas próprias ferramentas, capacidades e área de foco.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index 176cc5a1b..1e7d122fc 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -284,10 +284,13 @@ const messages: TranslationMap = { 'memory.empty': 'Воспоминаний пока нет. Они создаются автоматически в процессе общения.', 'memory.tab.memory': 'Память', 'memory.tab.memoryTree': 'Дерево памяти', - 'memory.tab.tasks': 'Задачи агента', + 'memory.tab.tasks': 'Задачи', 'memory.tab.tasksDescription': 'Создавайте и отслеживайте задачи — ваши личные дела и доски, которые агенты формируют в ходе разговоров.', 'memory.tab.subconscious': 'Подсознание', + 'memory.tab.agents': 'Библиотека', + 'memory.tab.agentsDescription': + 'Просматривайте и запускайте доступных агентов — у каждого свои инструменты, возможности и область специализации.', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index cca0eba85..1af00615e 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -271,10 +271,13 @@ const messages: TranslationMap = { 'memory.empty': '暂无记忆。记忆将在你交互时自动创建。', 'memory.tab.memory': '记忆', 'memory.tab.memoryTree': '记忆树', - 'memory.tab.tasks': '智能体任务', + 'memory.tab.tasks': '任务', 'memory.tab.tasksDescription': '创建并跟踪任务——包括您自己的待办事项以及智能体在对话中创建的看板。', 'memory.tab.subconscious': '潜意识', + 'memory.tab.agents': '资料库', + 'memory.tab.agentsDescription': + '浏览并运行可用的智能体——每个智能体都有自己的工具、能力和专注领域。', 'memory.tab.workflows': 'Workflows', 'memory.tab.workflowsDescription': 'Lifecycle-bound rule sets that guide how the agent behaves during tasks.', diff --git a/app/src/pages/Intelligence.test.tsx b/app/src/pages/Intelligence.test.tsx index 120539cc1..69f3c30c7 100644 --- a/app/src/pages/Intelligence.test.tsx +++ b/app/src/pages/Intelligence.test.tsx @@ -7,7 +7,7 @@ import Intelligence from './Intelligence'; vi.mock('../lib/i18n/I18nContext', () => ({ useT: () => ({ t: (k: string) => k }) })); // IS_DEV gates the dev-only "council" tab; default to a non-dev build. -const isDev = { value: false }; +const isDev = vi.hoisted(() => ({ value: false })); vi.mock('../utils/config', async () => { const actual = await vi.importActual('../utils/config'); return { diff --git a/app/src/pages/Intelligence.tsx b/app/src/pages/Intelligence.tsx index 2683d156f..0d26ad3fb 100644 --- a/app/src/pages/Intelligence.tsx +++ b/app/src/pages/Intelligence.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { ConfirmationModal } from '../components/intelligence/ConfirmationModal'; +import IntelligenceAgentsTab from '../components/intelligence/IntelligenceAgentsTab'; import IntelligenceSubconsciousTab from '../components/intelligence/IntelligenceSubconsciousTab'; import IntelligenceTasksTab from '../components/intelligence/IntelligenceTasksTab'; import MemorySection from '../components/intelligence/MemorySection'; @@ -21,12 +22,13 @@ import type { } from '../types/intelligence'; import { IS_DEV } from '../utils/config'; -type IntelligenceTab = 'memory' | 'subconscious' | 'tasks' | 'workflows' | 'council'; +type IntelligenceTab = 'memory' | 'subconscious' | 'tasks' | 'agents' | 'workflows' | 'council'; const INTELLIGENCE_TABS: IntelligenceTab[] = [ 'memory', 'subconscious', 'tasks', + 'agents', 'workflows', 'council', ]; @@ -134,13 +136,14 @@ export default function Intelligence() { description: t('memory.tab.workflowsDescription'), }, { id: 'council', label: t('memory.tab.council'), devOnly: true }, + { id: 'agents', label: t('memory.tab.agents'), description: t('memory.tab.agentsDescription') }, ]; const tabs = allTabs.filter(tab => !tab.devOnly || IS_DEV); const activeTabDef = tabs.find(tab => tab.id === activeTab); return (
-
+
({ label: tab.label, value: tab.id }))} selected={activeTab} @@ -212,6 +215,8 @@ export default function Intelligence() { {activeTab === 'tasks' && } + {activeTab === 'agents' && } + {activeTab === 'workflows' && } {activeTab === 'council' && }