feat(intelligence): background agent command center UI (#3373) (#3499)

This commit is contained in:
oxoxDev
2026-06-09 17:01:54 +05:30
committed by GitHub
parent 357447bb12
commit d72f300c17
19 changed files with 1066 additions and 1 deletions
@@ -0,0 +1,117 @@
import { render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { agentWorkApi, type AgentWorkResponse } from '../../services/api/agentWorkApi';
import IntelligenceAgentWorkTab from './IntelligenceAgentWorkTab';
vi.mock('../../services/api/agentWorkApi', () => ({ agentWorkApi: { list: vi.fn() } }));
// i18n → echo the key so assertions can target stable strings.
vi.mock('../../lib/i18n/I18nContext', () => ({ useT: () => ({ t: (k: string) => k }) }));
// Navigation + store: the tab only dispatches + navigates on click; stub them.
vi.mock('react-router-dom', () => ({ useNavigate: () => vi.fn() }));
vi.mock('../../store/hooks', () => ({ useAppDispatch: () => vi.fn() }));
vi.mock('../../store/threadSlice', () => ({
loadThreadMessages: vi.fn(),
loadThreads: vi.fn(),
setSelectedThread: vi.fn(),
}));
const mockList = vi.mocked(agentWorkApi.list);
function emptyResponse(): AgentWorkResponse {
return {
total: 0,
groups: (['needs_input', 'working', 'completed', 'failed', 'stopped'] as const).map(bucket => ({
bucket,
count: 0,
rows: [],
})),
};
}
function workingResponse(): AgentWorkResponse {
return {
total: 1,
groups: [
{ bucket: 'needs_input', count: 0, rows: [] },
{
bucket: 'working',
count: 1,
rows: [
{
runId: 'run-1',
kind: 'subagent',
agentId: 'agent-a',
displayName: 'Researcher',
bucket: 'working',
status: 'running',
workerThreadId: 'thread-w',
startedAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:01:00Z',
elapsedMs: 60000,
inputTokens: 1200,
outputTokens: 300,
costUsd: 0.05,
toolCount: 3,
},
],
},
{ bucket: 'completed', count: 0, rows: [] },
{ bucket: 'failed', count: 0, rows: [] },
{ bucket: 'stopped', count: 0, rows: [] },
],
};
}
describe('IntelligenceAgentWorkTab', () => {
beforeEach(() => {
// Reset the queue + implementation so a prior test's resolve/reject can't
// leak via the mount setTimeout into the next render (clearMocks only wipes
// call history, not queued *Once values / persistent implementations).
mockList.mockReset();
});
it('fetches agent work on mount', async () => {
mockList.mockResolvedValue(emptyResponse());
render(<IntelligenceAgentWorkTab />);
await waitFor(() => expect(mockList).toHaveBeenCalledTimes(1));
});
it('shows the loading state before the RPC resolves', () => {
mockList.mockReturnValue(new Promise(() => {}));
render(<IntelligenceAgentWorkTab />);
expect(screen.getByText('intelligence.agentWork.loading')).toBeInTheDocument();
});
it('shows the error box when the RPC rejects', async () => {
mockList.mockRejectedValue(new Error('boom'));
render(<IntelligenceAgentWorkTab />);
await waitFor(() =>
expect(screen.getByText(/intelligence\.agentWork\.failedToLoad/)).toBeInTheDocument()
);
expect(screen.getByText(/boom/)).toBeInTheDocument();
});
it('shows the empty state when total is 0', async () => {
mockList.mockResolvedValue(emptyResponse());
render(<IntelligenceAgentWorkTab />);
await waitFor(() =>
expect(screen.getByText('intelligence.agentWork.empty')).toBeInTheDocument()
);
});
it('renders a grouped working row with its display name and bucket label', async () => {
mockList.mockResolvedValue(workingResponse());
render(<IntelligenceAgentWorkTab />);
await waitFor(() => expect(screen.getByText('Researcher')).toBeInTheDocument());
expect(screen.getByText('intelligence.agentWork.bucket.working')).toBeInTheDocument();
// 1200 + 300 input/output tokens → "1.5K"
expect(screen.getByText('1.5K')).toBeInTheDocument();
// $0.05 cost formatted
expect(screen.getByText('$0.05')).toBeInTheDocument();
// worker-thread jump button present
expect(screen.getByText('intelligence.agentWork.openWorker')).toBeInTheDocument();
});
});
@@ -0,0 +1,310 @@
/**
* IntelligenceAgentWorkTab — the Background Agent Command Center.
*
* Reads `openhuman.agent_work_list` (via {@link agentWorkApi}) once on mount
* and renders every tracked background agent run, grouped into five lifecycle
* buckets in a fixed display order: needs_input → working → completed →
* failed → stopped. The handler always returns all five groups, so the order
* here is authoritative and never needs sorting.
*
* This tab is read-only and holds no Redux slice — it owns its own
* {data, loading, error} state, mirroring {@link IntelligenceTasksTab}'s
* mount pattern (mountedRef + a 0ms `setTimeout` so the first paint shows the
* loading state before the RPC resolves). Each row offers jumps to the parent
* / worker thread via the same navigate('/chat', { openThreadId }) path the
* Tasks tab uses for "View session".
*/
import debug from 'debug';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useT } from '../../lib/i18n/I18nContext';
import {
agentWorkApi,
type AgentWorkBucket,
type AgentWorkResponse,
type AgentWorkRow,
} from '../../services/api/agentWorkApi';
import { useAppDispatch } from '../../store/hooks';
import { loadThreadMessages, loadThreads, setSelectedThread } from '../../store/threadSlice';
const log = debug('intelligence:agent-work');
/** Fixed display order of buckets — matches the handler's group order. */
const BUCKET_ORDER: AgentWorkBucket[] = [
'needs_input',
'working',
'completed',
'failed',
'stopped',
];
/** Per-bucket accent classes (semantic palette from tailwind.config.js). */
const BUCKET_ACCENT: Record<AgentWorkBucket, string> = {
needs_input:
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-300',
working:
'border-ocean-200 bg-ocean-50 text-ocean-700 dark:border-ocean-500/30 dark:bg-ocean-500/10 dark:text-ocean-300',
completed:
'border-sage-200 bg-sage-50 text-sage-700 dark:border-sage-500/30 dark:bg-sage-500/10 dark:text-sage-300',
failed:
'border-coral-200 bg-coral-50 text-coral-700 dark:border-coral-500/30 dark:bg-coral-500/10 dark:text-coral-300',
stopped:
'border-stone-200 bg-stone-50 text-stone-600 dark:border-neutral-700 dark:bg-neutral-800/60 dark:text-neutral-300',
};
/** i18n key for each bucket's localized label. */
const BUCKET_LABEL_KEY: Record<AgentWorkBucket, string> = {
needs_input: 'intelligence.agentWork.bucket.needsInput',
working: 'intelligence.agentWork.bucket.working',
completed: 'intelligence.agentWork.bucket.completed',
failed: 'intelligence.agentWork.bucket.failed',
stopped: 'intelligence.agentWork.bucket.stopped',
};
/** i18n key for each granular run status (mirrors Rust `AgentRunStatus`). */
const STATUS_LABEL_KEY: Record<string, string> = {
pending: 'intelligence.agentWork.status.pending',
running: 'intelligence.agentWork.status.running',
awaiting_user: 'intelligence.agentWork.status.awaitingUser',
paused: 'intelligence.agentWork.status.paused',
completed: 'intelligence.agentWork.status.completed',
failed: 'intelligence.agentWork.status.failed',
cancelled: 'intelligence.agentWork.status.cancelled',
interrupted: 'intelligence.agentWork.status.interrupted',
};
/** i18n key for each run kind (mirrors Rust `AgentRunKind`). */
const KIND_LABEL_KEY: Record<string, string> = {
subagent: 'intelligence.agentWork.kind.subagent',
worker_thread: 'intelligence.agentWork.kind.workerThread',
background_agent: 'intelligence.agentWork.kind.backgroundAgent',
team_member: 'intelligence.agentWork.kind.teamMember',
workflow_child: 'intelligence.agentWork.kind.workflowChild',
};
/** Format an elapsed millisecond span as a compact "1h 23m" / "45s" string. */
export function formatElapsed(ms: number | undefined): string {
if (ms === undefined || !Number.isFinite(ms) || ms < 0) return '—';
const totalSeconds = Math.floor(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
if (hours > 0) return `${hours}h ${minutes}m`;
if (minutes > 0) return `${minutes}m ${seconds}s`;
return `${seconds}s`;
}
/** Human-readable token count: 1234 → "1.2K", 2_500_000 → "2.5M". */
export function formatTokens(value: number): string {
if (!Number.isFinite(value) || value < 0) return '0';
if (value < 1000) return String(value);
if (value < 1_000_000) return `${(value / 1000).toFixed(1)}K`;
return `${(value / 1_000_000).toFixed(1)}M`;
}
/** Format a USD cost with two decimals, e.g. 0.0123 → "$0.01". */
export function formatCost(value: number): string {
if (!Number.isFinite(value) || value < 0) return '$0.00';
return `$${value.toFixed(2)}`;
}
export default function IntelligenceAgentWorkTab() {
const { t } = useT();
const dispatch = useAppDispatch();
const navigate = useNavigate();
const [data, setData] = useState<AgentWorkResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const mountedRef = useRef(true);
const fetchWork = useCallback(async () => {
log('fetchWork: entry');
setError(null);
try {
const response = await agentWorkApi.list();
if (mountedRef.current) {
setData(response);
log('fetchWork: done total=%d', response.total);
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
log('fetchWork: error %s', msg);
if (mountedRef.current) setError(msg);
} finally {
if (mountedRef.current) setLoading(false);
}
}, []);
useEffect(() => {
mountedRef.current = true;
const handle = window.setTimeout(() => {
void fetchWork();
}, 0);
return () => {
window.clearTimeout(handle);
mountedRef.current = false;
};
}, [fetchWork]);
// Open a thread in /chat — mirrors IntelligenceTasksTab's "View session"
// path so the chat surface lands on the requested thread instead of just
// the Conversations list. Navigation only; the thread is not marked active.
const openThread = useCallback(
(threadId: string) => {
log('openThread threadId=%s', threadId);
dispatch(setSelectedThread(threadId));
void dispatch(loadThreads());
void dispatch(loadThreadMessages(threadId));
navigate('/chat', { state: { openThreadId: threadId } });
},
[dispatch, navigate]
);
if (loading) {
return (
<div className="flex items-center justify-center py-10 text-stone-400 dark:text-neutral-500">
<div className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-ocean-500 border-t-transparent" />
<span className="text-sm">{t('intelligence.agentWork.loading')}</span>
</div>
);
}
if (error) {
return (
<div className="rounded-xl border border-coral-200 bg-coral-50 px-4 py-3 text-sm text-coral-700 dark:border-coral-500/30 dark:bg-coral-500/10 dark:text-coral-300">
{t('intelligence.agentWork.failedToLoad')}: {error}
</div>
);
}
if (!data || data.total === 0) {
return (
<div className="space-y-4">
<p className="text-xs text-stone-400 dark:text-neutral-500">
{t('intelligence.agentWork.subtitle')}
</p>
<div className="rounded-xl border border-dashed border-stone-200 py-10 text-center text-sm text-stone-400 dark:border-neutral-800 dark:text-neutral-500">
{t('intelligence.agentWork.empty')}
</div>
</div>
);
}
const groupByBucket = new Map(data.groups.map(group => [group.bucket, group]));
return (
<div className="space-y-6">
<p className="text-xs text-stone-400 dark:text-neutral-500">
{t('intelligence.agentWork.subtitle')}
</p>
{BUCKET_ORDER.map(bucket => {
const group = groupByBucket.get(bucket);
const rows = group?.rows ?? [];
if (rows.length === 0) return null;
return (
<section key={bucket} className="space-y-2">
<div className="flex items-center gap-2">
<span
className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium ${BUCKET_ACCENT[bucket]}`}>
{bucket === 'working' && (
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-ocean-500" />
)}
{t(BUCKET_LABEL_KEY[bucket])}
</span>
<span className="text-xs text-stone-400 dark:text-neutral-500">
{group?.count ?? rows.length}
</span>
</div>
<ul className="divide-y divide-stone-100 overflow-hidden rounded-xl border border-stone-200 bg-white dark:divide-neutral-800 dark:border-neutral-800 dark:bg-neutral-900">
{rows.map(row => (
<AgentWorkRowItem key={row.runId} row={row} onOpenThread={openThread} />
))}
</ul>
</section>
);
})}
</div>
);
}
interface AgentWorkRowItemProps {
row: AgentWorkRow;
onOpenThread: (threadId: string) => void;
}
function AgentWorkRowItem({ row, onOpenThread }: AgentWorkRowItemProps) {
const { t } = useT();
const name = row.displayName || row.agentId || row.runId;
const totalTokens = row.inputTokens + row.outputTokens;
// Localize the backend enum values, falling back to the raw value for any
// status/kind the UI doesn't yet have a key for.
const statusLabel = STATUS_LABEL_KEY[row.status] ? t(STATUS_LABEL_KEY[row.status]) : row.status;
const kindLabel = KIND_LABEL_KEY[row.kind] ? t(KIND_LABEL_KEY[row.kind]) : row.kind;
return (
<li className="p-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 space-y-1">
<div className="flex flex-wrap items-center gap-1.5">
<span
title={t('intelligence.agentWork.column.agent')}
className="truncate text-sm font-medium text-stone-800 dark:text-neutral-100">
{name}
</span>
<span
title={t('intelligence.agentWork.column.status')}
className="rounded-md border border-stone-200 px-1.5 py-0.5 text-[10px] font-medium text-stone-500 dark:border-neutral-700 dark:text-neutral-400">
{statusLabel}
</span>
<span className="text-[10px] uppercase tracking-wide text-stone-400 dark:text-neutral-500">
{kindLabel}
</span>
</div>
{row.summary && (
<p className="line-clamp-2 break-words text-xs leading-snug text-stone-500 dark:text-neutral-400">
{row.summary}
</p>
)}
{row.error && (
<p className="line-clamp-2 break-words text-xs leading-snug text-coral-600 dark:text-coral-300">
{row.error}
</p>
)}
</div>
<div className="flex flex-none items-center gap-3 text-xs text-stone-500 dark:text-neutral-400">
<span title={t('intelligence.agentWork.column.elapsed')}>
{formatElapsed(row.elapsedMs)}
</span>
<span title={t('intelligence.agentWork.column.cost')}>{formatCost(row.costUsd)}</span>
<span title={t('intelligence.agentWork.column.tokens')}>{formatTokens(totalTokens)}</span>
</div>
</div>
{(row.parentThreadId || row.workerThreadId) && (
<div className="mt-2 flex flex-wrap items-center gap-2">
{row.parentThreadId && (
<button
type="button"
onClick={() => onOpenThread(row.parentThreadId as string)}
className="rounded-md border border-stone-200 px-2 py-1 text-[11px] font-medium text-ocean-600 hover:bg-stone-50 dark:border-neutral-800 dark:text-ocean-300 dark:hover:bg-neutral-800">
{t('intelligence.agentWork.openThread')}
</button>
)}
{row.workerThreadId && (
<button
type="button"
onClick={() => onOpenThread(row.workerThreadId as string)}
className="rounded-md border border-stone-200 px-2 py-1 text-[11px] font-medium text-ocean-600 hover:bg-stone-50 dark:border-neutral-800 dark:text-ocean-300 dark:hover:bg-neutral-800">
{t('intelligence.agentWork.openWorker')}
</button>
)}
</div>
)}
</li>
);
}
+32
View File
@@ -306,6 +306,9 @@ const messages: TranslationMap = {
'memory.tab.tasksDescription':
'أنشئ المهام وتتبعها — مهامك الخاصة بالإضافة إلى اللوحات التي يبنيها وكلاؤك عبر المحادثات.',
'memory.tab.subconscious': 'اللاوعي',
'memory.tab.agentWork': 'عمل الوكيل',
'memory.tab.agentWorkDescription':
'مركز قيادة لكل تشغيل وكيل في الخلفية — مُجمّع حسب ما يحتاج إلى مُدخلاتك، وما يعمل الآن، وما اكتمل.',
'memory.tab.agents': 'المكتبة',
'memory.tab.agentsDescription':
'تصفّح وشغّل الوكلاء المتاحين — لكل منهم أدواته وقدراته ومجال تركيزه.',
@@ -2766,6 +2769,35 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- الرابط: {url}',
'intelligence.workTask.closingInstruction':
'ابدأ بإعادة صياغة خطة التنفيذ الملموسة بإيجاز، ثم نفّذها. أبقِ التقدّم مرئيًا في هذا المحادثة وحدّث لوحة المهام عند تغيّر حالة العمل.',
'intelligence.agentWork.subtitle': 'كل تشغيل وكيل في الخلفية، مُجمّع حسب حالة دورة الحياة.',
'intelligence.agentWork.loading': 'جارٍ تحميل عمل الوكيل…',
'intelligence.agentWork.failedToLoad': 'تعذّر تحميل عمل الوكيل',
'intelligence.agentWork.empty': 'لا توجد عمليات تشغيل وكيل في الخلفية بعد.',
'intelligence.agentWork.bucket.needsInput': 'يحتاج إلى مُدخلات',
'intelligence.agentWork.bucket.working': 'قيد العمل',
'intelligence.agentWork.bucket.completed': 'مكتمل',
'intelligence.agentWork.bucket.failed': 'فشل',
'intelligence.agentWork.bucket.stopped': 'متوقف',
'intelligence.agentWork.column.agent': 'الوكيل',
'intelligence.agentWork.column.status': 'الحالة',
'intelligence.agentWork.column.elapsed': 'الوقت المنقضي',
'intelligence.agentWork.column.cost': 'التكلفة',
'intelligence.agentWork.column.tokens': 'الرموز',
'intelligence.agentWork.status.pending': 'قيد الانتظار',
'intelligence.agentWork.status.running': 'قيد العمل',
'intelligence.agentWork.status.awaitingUser': 'يحتاج إلى مُدخلات',
'intelligence.agentWork.status.paused': 'متوقف مؤقتًا',
'intelligence.agentWork.status.completed': 'مكتمل',
'intelligence.agentWork.status.failed': 'فشل',
'intelligence.agentWork.status.cancelled': 'ملغى',
'intelligence.agentWork.status.interrupted': 'مقاطَع',
'intelligence.agentWork.kind.subagent': 'وكيل فرعي',
'intelligence.agentWork.kind.workerThread': 'محادثة عامل',
'intelligence.agentWork.kind.backgroundAgent': 'وكيل في الخلفية',
'intelligence.agentWork.kind.teamMember': 'عضو الفريق',
'intelligence.agentWork.kind.workflowChild': 'عنصر فرعي لسير العمل',
'intelligence.agentWork.openThread': 'فتح المحادثة',
'intelligence.agentWork.openWorker': 'فتح العامل',
'intelligence.refine.objectiveDefault': 'حوّل المهمة المصدر إلى مهمة وكيل جاهزة للتنفيذ: {title}',
'intelligence.refine.sourceLine': 'المصدر: {url}',
'intelligence.refine.sourceIntake': 'المصدر: استقبال مصدر المهام',
+33
View File
@@ -310,6 +310,9 @@ const messages: TranslationMap = {
'memory.tab.tasksDescription':
'টাস্ক তৈরি করুন এবং ট্র্যাক করুন — আপনার নিজের কাজের তালিকা এবং এজেন্টরা কথোপকথন জুড়ে যে বোর্ডগুলি তৈরি করে।',
'memory.tab.subconscious': 'সাবকনশাস',
'memory.tab.agentWork': 'এজেন্ট কাজ',
'memory.tab.agentWorkDescription':
'প্রতিটি ব্যাকগ্রাউন্ড এজেন্ট রানের জন্য একটি কমান্ড সেন্টার — আপনার ইনপুট প্রয়োজন এমন, চলমান এবং সম্পন্ন অনুযায়ী গোষ্ঠীবদ্ধ।',
'memory.tab.agents': 'লাইব্রেরি',
'memory.tab.agentsDescription':
'আপনার উপলব্ধ এজেন্টগুলি ব্রাউজ করুন এবং চালান — প্রতিটির নিজস্ব টুল, ক্ষমতা এবং ফোকাস এলাকা রয়েছে।',
@@ -2822,6 +2825,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- ইউআরএল: {url}',
'intelligence.workTask.closingInstruction':
'প্রথমে সুনির্দিষ্ট বাস্তবায়ন পরিকল্পনাটি সংক্ষেপে পুনরায় বলুন, তারপর তা সম্পাদন করুন। এই থ্রেডে অগ্রগতি দৃশ্যমান রাখুন এবং কাজের অবস্থা পরিবর্তিত হলে টাস্ক বোর্ড আপডেট করুন।',
'intelligence.agentWork.subtitle':
'প্রতিটি ব্যাকগ্রাউন্ড এজেন্ট রান, জীবনচক্রের অবস্থা অনুযায়ী গোষ্ঠীবদ্ধ।',
'intelligence.agentWork.loading': 'এজেন্ট কাজ লোড হচ্ছে…',
'intelligence.agentWork.failedToLoad': 'এজেন্ট কাজ লোড করা যায়নি',
'intelligence.agentWork.empty': 'এখনও কোনো ব্যাকগ্রাউন্ড এজেন্ট রান নেই।',
'intelligence.agentWork.bucket.needsInput': 'ইনপুট প্রয়োজন',
'intelligence.agentWork.bucket.working': 'কাজ চলছে',
'intelligence.agentWork.bucket.completed': 'সম্পন্ন',
'intelligence.agentWork.bucket.failed': 'ব্যর্থ',
'intelligence.agentWork.bucket.stopped': 'বন্ধ',
'intelligence.agentWork.column.agent': 'এজেন্ট',
'intelligence.agentWork.column.status': 'স্ট্যাটাস',
'intelligence.agentWork.column.elapsed': 'অতিবাহিত সময়',
'intelligence.agentWork.column.cost': 'ব্যয়',
'intelligence.agentWork.column.tokens': 'টোকেন',
'intelligence.agentWork.status.pending': 'মুলতুবি',
'intelligence.agentWork.status.running': 'চলছে',
'intelligence.agentWork.status.awaitingUser': 'ইনপুট প্রয়োজন',
'intelligence.agentWork.status.paused': 'বিরতিতে',
'intelligence.agentWork.status.completed': 'সম্পন্ন',
'intelligence.agentWork.status.failed': 'ব্যর্থ',
'intelligence.agentWork.status.cancelled': 'বাতিল',
'intelligence.agentWork.status.interrupted': 'বিঘ্নিত',
'intelligence.agentWork.kind.subagent': 'সাব-এজেন্ট',
'intelligence.agentWork.kind.workerThread': 'ওয়ার্কার থ্রেড',
'intelligence.agentWork.kind.backgroundAgent': 'ব্যাকগ্রাউন্ড এজেন্ট',
'intelligence.agentWork.kind.teamMember': 'টিম সদস্য',
'intelligence.agentWork.kind.workflowChild': 'ওয়ার্কফ্লো চাইল্ড',
'intelligence.agentWork.openThread': 'থ্রেড খুলুন',
'intelligence.agentWork.openWorker': 'ওয়ার্কার খুলুন',
'intelligence.refine.objectiveDefault':
'উৎস টাস্কটিকে বাস্তবায়নের জন্য প্রস্তুত একটি এজেন্ট টাস্কে রূপান্তর করুন: {title}',
'intelligence.refine.sourceLine': 'উৎস: {url}',
+33
View File
@@ -319,6 +319,9 @@ const messages: TranslationMap = {
'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.agentWork': 'Agentenarbeit',
'memory.tab.agentWorkDescription':
'Eine Kommandozentrale für jeden Hintergrund-Agentenlauf — gruppiert danach, was deine Eingabe braucht, was läuft und was abgeschlossen ist.',
'memory.tab.agents': 'Bibliothek',
'memory.tab.agentsDescription':
'Verfügbare Agenten durchsuchen und starten — jeder mit eigenen Werkzeugen, Fähigkeiten und Schwerpunkten.',
@@ -2892,6 +2895,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- Adresse: {url}',
'intelligence.workTask.closingInstruction':
'Beginne damit, den konkreten Umsetzungsplan kurz zu wiederholen, und führe ihn dann aus. Halte den Fortschritt in diesem Thread sichtbar und aktualisiere das Aufgabenboard, wenn sich der Arbeitsstand ändert.',
'intelligence.agentWork.subtitle':
'Jeder Hintergrund-Agentenlauf, gruppiert nach Lebenszyklusstatus.',
'intelligence.agentWork.loading': 'Agentenarbeit wird geladen…',
'intelligence.agentWork.failedToLoad': 'Agentenarbeit konnte nicht geladen werden',
'intelligence.agentWork.empty': 'Noch keine Hintergrund-Agentenläufe.',
'intelligence.agentWork.bucket.needsInput': 'Eingabe nötig',
'intelligence.agentWork.bucket.working': 'In Arbeit',
'intelligence.agentWork.bucket.completed': 'Abgeschlossen',
'intelligence.agentWork.bucket.failed': 'Fehlgeschlagen',
'intelligence.agentWork.bucket.stopped': 'Angehalten',
'intelligence.agentWork.column.agent': 'Agent',
'intelligence.agentWork.column.status': 'Status',
'intelligence.agentWork.column.elapsed': 'Verstrichen',
'intelligence.agentWork.column.cost': 'Kosten',
'intelligence.agentWork.column.tokens': 'Tokens',
'intelligence.agentWork.status.pending': 'Ausstehend',
'intelligence.agentWork.status.running': 'Läuft',
'intelligence.agentWork.status.awaitingUser': 'Eingabe nötig',
'intelligence.agentWork.status.paused': 'Pausiert',
'intelligence.agentWork.status.completed': 'Abgeschlossen',
'intelligence.agentWork.status.failed': 'Fehlgeschlagen',
'intelligence.agentWork.status.cancelled': 'Abgebrochen',
'intelligence.agentWork.status.interrupted': 'Unterbrochen',
'intelligence.agentWork.kind.subagent': 'Subagent',
'intelligence.agentWork.kind.workerThread': 'Worker-Thread',
'intelligence.agentWork.kind.backgroundAgent': 'Hintergrundagent',
'intelligence.agentWork.kind.teamMember': 'Teammitglied',
'intelligence.agentWork.kind.workflowChild': 'Workflow-Unterelement',
'intelligence.agentWork.openThread': 'Thread öffnen',
'intelligence.agentWork.openWorker': 'Worker öffnen',
'intelligence.refine.objectiveDefault':
'Verwandle die Quellaufgabe in eine umsetzungsbereite Agentenaufgabe: {title}',
'intelligence.refine.sourceLine': 'Quelle: {url}',
+32
View File
@@ -335,6 +335,9 @@ const en: TranslationMap = {
'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.agentWork': 'Agent Work',
'memory.tab.agentWorkDescription':
'A command center for every background agent run — grouped by what needs your input, what is working, and what has finished.',
'memory.tab.agents': 'Library',
'memory.tab.agentsDescription':
'Browse and run your available agents — each with its own tools, capabilities, and focus area.',
@@ -3339,6 +3342,35 @@ const en: TranslationMap = {
'intelligence.workTask.urlLine': '- URL: {url}',
'intelligence.workTask.closingInstruction':
'Start by restating the concrete implementation plan briefly, then execute it. Keep progress visible in this thread and update the task board when the work state changes.',
'intelligence.agentWork.subtitle': 'Every background agent run, grouped by lifecycle state.',
'intelligence.agentWork.loading': 'Loading agent work…',
'intelligence.agentWork.failedToLoad': 'Failed to load agent work',
'intelligence.agentWork.empty': 'No background agent runs yet.',
'intelligence.agentWork.bucket.needsInput': 'Needs input',
'intelligence.agentWork.bucket.working': 'Working',
'intelligence.agentWork.bucket.completed': 'Completed',
'intelligence.agentWork.bucket.failed': 'Failed',
'intelligence.agentWork.bucket.stopped': 'Stopped',
'intelligence.agentWork.column.agent': 'Agent',
'intelligence.agentWork.column.status': 'Status',
'intelligence.agentWork.column.elapsed': 'Elapsed',
'intelligence.agentWork.column.cost': 'Cost',
'intelligence.agentWork.column.tokens': 'Tokens',
'intelligence.agentWork.status.pending': 'Pending',
'intelligence.agentWork.status.running': 'Running',
'intelligence.agentWork.status.awaitingUser': 'Awaiting input',
'intelligence.agentWork.status.paused': 'Paused',
'intelligence.agentWork.status.completed': 'Completed',
'intelligence.agentWork.status.failed': 'Failed',
'intelligence.agentWork.status.cancelled': 'Cancelled',
'intelligence.agentWork.status.interrupted': 'Interrupted',
'intelligence.agentWork.kind.subagent': 'Subagent',
'intelligence.agentWork.kind.workerThread': 'Worker thread',
'intelligence.agentWork.kind.backgroundAgent': 'Background agent',
'intelligence.agentWork.kind.teamMember': 'Team member',
'intelligence.agentWork.kind.workflowChild': 'Workflow child',
'intelligence.agentWork.openThread': 'Open thread',
'intelligence.agentWork.openWorker': 'Open worker',
'intelligence.refine.objectiveDefault':
'Turn the source task into an implementation-ready agent task: {title}',
'intelligence.refine.sourceLine': 'Source: {url}',
+33
View File
@@ -319,6 +319,9 @@ const messages: TranslationMap = {
'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.agentWork': 'Trabajo del agente',
'memory.tab.agentWorkDescription':
'Un centro de mando para cada ejecución de agente en segundo plano, agrupado según lo que necesita tu intervención, lo que está en curso y lo que ha terminado.',
'memory.tab.agents': 'Biblioteca',
'memory.tab.agentsDescription':
'Explora y ejecuta los agentes disponibles — cada uno con sus propias herramientas, capacidades y área de enfoque.',
@@ -2876,6 +2879,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- Dirección: {url}',
'intelligence.workTask.closingInstruction':
'Empieza reformulando brevemente el plan de implementación concreto y luego ejecútalo. Mantén el progreso visible en este hilo y actualiza el tablero de tareas cuando cambie el estado del trabajo.',
'intelligence.agentWork.subtitle':
'Cada ejecución de agente en segundo plano, agrupada por estado del ciclo de vida.',
'intelligence.agentWork.loading': 'Cargando trabajo del agente…',
'intelligence.agentWork.failedToLoad': 'No se pudo cargar el trabajo del agente',
'intelligence.agentWork.empty': 'Aún no hay ejecuciones de agente en segundo plano.',
'intelligence.agentWork.bucket.needsInput': 'Requiere intervención',
'intelligence.agentWork.bucket.working': 'En curso',
'intelligence.agentWork.bucket.completed': 'Completado',
'intelligence.agentWork.bucket.failed': 'Fallido',
'intelligence.agentWork.bucket.stopped': 'Detenido',
'intelligence.agentWork.column.agent': 'Agente',
'intelligence.agentWork.column.status': 'Estado',
'intelligence.agentWork.column.elapsed': 'Transcurrido',
'intelligence.agentWork.column.cost': 'Costo',
'intelligence.agentWork.column.tokens': 'Fichas',
'intelligence.agentWork.status.pending': 'Pendiente',
'intelligence.agentWork.status.running': 'En ejecución',
'intelligence.agentWork.status.awaitingUser': 'Requiere intervención',
'intelligence.agentWork.status.paused': 'Pausado',
'intelligence.agentWork.status.completed': 'Completado',
'intelligence.agentWork.status.failed': 'Fallido',
'intelligence.agentWork.status.cancelled': 'Cancelado',
'intelligence.agentWork.status.interrupted': 'Interrumpido',
'intelligence.agentWork.kind.subagent': 'Subagente',
'intelligence.agentWork.kind.workerThread': 'Hilo de worker',
'intelligence.agentWork.kind.backgroundAgent': 'Agente en segundo plano',
'intelligence.agentWork.kind.teamMember': 'Miembro del equipo',
'intelligence.agentWork.kind.workflowChild': 'Hijo de flujo de trabajo',
'intelligence.agentWork.openThread': 'Abrir hilo',
'intelligence.agentWork.openWorker': 'Abrir trabajador',
'intelligence.refine.objectiveDefault':
'Convierte la tarea de origen en una tarea de agente lista para implementar: {title}',
'intelligence.refine.sourceLine': 'Origen: {url}',
+33
View File
@@ -318,6 +318,9 @@ const messages: TranslationMap = {
'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.agentWork': "Travail de l'agent",
'memory.tab.agentWorkDescription':
"Un centre de commande pour chaque exécution d'agent en arrière-plan, regroupée selon ce qui nécessite votre intervention, ce qui est en cours et ce qui est terminé.",
'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.',
@@ -2890,6 +2893,36 @@ const messages: TranslationMap = {
'intelligence.refine.objectiveDefault':
"Transformez la tâche source en une tâche d'agent prête à être implémentée : {title}",
'intelligence.refine.sourceLine': 'Source : {url}',
'intelligence.agentWork.subtitle':
"Chaque exécution d'agent en arrière-plan, regroupée par état du cycle de vie.",
'intelligence.agentWork.loading': "Chargement du travail de l'agent…",
'intelligence.agentWork.failedToLoad': "Impossible de charger le travail de l'agent",
'intelligence.agentWork.empty': "Aucune exécution d'agent en arrière-plan pour l'instant.",
'intelligence.agentWork.bucket.needsInput': 'Intervention requise',
'intelligence.agentWork.bucket.working': 'En cours',
'intelligence.agentWork.bucket.completed': 'Terminé',
'intelligence.agentWork.bucket.failed': 'Échoué',
'intelligence.agentWork.bucket.stopped': 'Arrêté',
'intelligence.agentWork.column.agent': 'Agent',
'intelligence.agentWork.column.status': 'Statut',
'intelligence.agentWork.column.elapsed': 'Écoulé',
'intelligence.agentWork.column.cost': 'Coût',
'intelligence.agentWork.column.tokens': 'Jetons',
'intelligence.agentWork.status.pending': 'En attente',
'intelligence.agentWork.status.running': 'En cours',
'intelligence.agentWork.status.awaitingUser': 'Intervention requise',
'intelligence.agentWork.status.paused': 'En pause',
'intelligence.agentWork.status.completed': 'Terminé',
'intelligence.agentWork.status.failed': 'Échoué',
'intelligence.agentWork.status.cancelled': 'Annulé',
'intelligence.agentWork.status.interrupted': 'Interrompu',
'intelligence.agentWork.kind.subagent': 'Sous-agent',
'intelligence.agentWork.kind.workerThread': 'Fil worker',
'intelligence.agentWork.kind.backgroundAgent': 'Agent en arrière-plan',
'intelligence.agentWork.kind.teamMember': 'Membre de l’équipe',
'intelligence.agentWork.kind.workflowChild': 'Élément enfant de workflow',
'intelligence.agentWork.openThread': 'Ouvrir le fil',
'intelligence.agentWork.openWorker': 'Ouvrir le worker',
'intelligence.refine.sourceIntake': 'Source : réception des sources de tâches',
'intelligence.refine.repositoryLine': 'Dépôt : {repo}',
'intelligence.refine.externalTaskLine': 'Tâche externe : {externalId}',
+32
View File
@@ -309,6 +309,9 @@ const messages: TranslationMap = {
'memory.tab.tasksDescription':
'कार्य बनाएं और ट्रैक करें — आपके अपने टू-डू और साथ ही वे बोर्ड जो आपके एजेंट वार्तालापों में बनाते हैं।',
'memory.tab.subconscious': 'सबकॉन्शस',
'memory.tab.agentWork': 'एजेंट कार्य',
'memory.tab.agentWorkDescription':
'हर पृष्ठभूमि एजेंट रन के लिए एक कमांड सेंटर — इस आधार पर समूहित कि किसे आपकी जानकारी चाहिए, क्या चल रहा है और क्या पूरा हो चुका है।',
'memory.tab.agents': 'लाइब्रेरी',
'memory.tab.agentsDescription':
'अपने उपलब्ध एजेंट ब्राउज़ करें और चलाएं — प्रत्येक के पास अपने टूल, क्षमताएं और फोकस क्षेत्र हैं।',
@@ -2827,6 +2830,35 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- यूआरएल: {url}',
'intelligence.workTask.closingInstruction':
'पहले ठोस कार्यान्वयन योजना को संक्षेप में दोहराएँ, फिर उसे निष्पादित करें। इस थ्रेड में प्रगति दिखती रहे और कार्य की स्थिति बदलने पर कार्य बोर्ड अपडेट करें।',
'intelligence.agentWork.subtitle': 'हर पृष्ठभूमि एजेंट रन, जीवनचक्र स्थिति के अनुसार समूहित।',
'intelligence.agentWork.loading': 'एजेंट कार्य लोड हो रहा है…',
'intelligence.agentWork.failedToLoad': 'एजेंट कार्य लोड नहीं हो सका',
'intelligence.agentWork.empty': 'अभी तक कोई पृष्ठभूमि एजेंट रन नहीं।',
'intelligence.agentWork.bucket.needsInput': 'इनपुट चाहिए',
'intelligence.agentWork.bucket.working': 'काम जारी',
'intelligence.agentWork.bucket.completed': 'पूरा हुआ',
'intelligence.agentWork.bucket.failed': 'विफल',
'intelligence.agentWork.bucket.stopped': 'रुका हुआ',
'intelligence.agentWork.column.agent': 'एजेंट',
'intelligence.agentWork.column.status': 'स्टेटस',
'intelligence.agentWork.column.elapsed': 'बीता समय',
'intelligence.agentWork.column.cost': 'लागत',
'intelligence.agentWork.column.tokens': 'टोकन',
'intelligence.agentWork.status.pending': 'लंबित',
'intelligence.agentWork.status.running': 'चल रहा है',
'intelligence.agentWork.status.awaitingUser': 'इनपुट चाहिए',
'intelligence.agentWork.status.paused': 'रुका हुआ',
'intelligence.agentWork.status.completed': 'पूरा हुआ',
'intelligence.agentWork.status.failed': 'विफल',
'intelligence.agentWork.status.cancelled': 'रद्द हुआ',
'intelligence.agentWork.status.interrupted': 'बाधित',
'intelligence.agentWork.kind.subagent': 'सब-एजेंट',
'intelligence.agentWork.kind.workerThread': 'वर्कर थ्रेड',
'intelligence.agentWork.kind.backgroundAgent': 'बैकग्राउंड एजेंट',
'intelligence.agentWork.kind.teamMember': 'टीम सदस्य',
'intelligence.agentWork.kind.workflowChild': 'वर्कफ़्लो चाइल्ड',
'intelligence.agentWork.openThread': 'थ्रेड खोलें',
'intelligence.agentWork.openWorker': 'वर्कर खोलें',
'intelligence.refine.objectiveDefault':
'स्रोत कार्य को कार्यान्वयन के लिए तैयार एजेंट कार्य में बदलें: {title}',
'intelligence.refine.sourceLine': 'स्रोत: {url}',
+33
View File
@@ -311,6 +311,9 @@ const messages: TranslationMap = {
'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.agentWork': 'Kerja agen',
'memory.tab.agentWorkDescription':
'Pusat kendali untuk setiap proses agen latar belakang — dikelompokkan berdasarkan apa yang butuh masukan Anda, apa yang sedang berjalan, dan apa yang sudah selesai.',
'memory.tab.agents': 'Perpustakaan',
'memory.tab.agentsDescription':
'Telusuri dan jalankan agen yang tersedia — masing-masing dengan alat, kemampuan, dan area fokusnya sendiri.',
@@ -2829,6 +2832,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- Tautan: {url}',
'intelligence.workTask.closingInstruction':
'Mulailah dengan menyatakan kembali rencana implementasi konkret secara singkat, lalu jalankan. Jaga agar kemajuan tetap terlihat di utas ini dan perbarui papan tugas ketika status pekerjaan berubah.',
'intelligence.agentWork.subtitle':
'Setiap proses agen latar belakang, dikelompokkan menurut status siklus hidup.',
'intelligence.agentWork.loading': 'Memuat kerja agen…',
'intelligence.agentWork.failedToLoad': 'Gagal memuat kerja agen',
'intelligence.agentWork.empty': 'Belum ada proses agen latar belakang.',
'intelligence.agentWork.bucket.needsInput': 'Perlu masukan',
'intelligence.agentWork.bucket.working': 'Berjalan',
'intelligence.agentWork.bucket.completed': 'Selesai',
'intelligence.agentWork.bucket.failed': 'Gagal',
'intelligence.agentWork.bucket.stopped': 'Berhenti',
'intelligence.agentWork.column.agent': 'Agen',
'intelligence.agentWork.column.status': 'Status',
'intelligence.agentWork.column.elapsed': 'Berlalu',
'intelligence.agentWork.column.cost': 'Biaya',
'intelligence.agentWork.column.tokens': 'Token',
'intelligence.agentWork.status.pending': 'Tertunda',
'intelligence.agentWork.status.running': 'Berjalan',
'intelligence.agentWork.status.awaitingUser': 'Perlu masukan',
'intelligence.agentWork.status.paused': 'Dijeda',
'intelligence.agentWork.status.completed': 'Selesai',
'intelligence.agentWork.status.failed': 'Gagal',
'intelligence.agentWork.status.cancelled': 'Dibatalkan',
'intelligence.agentWork.status.interrupted': 'Terputus',
'intelligence.agentWork.kind.subagent': 'Subagen',
'intelligence.agentWork.kind.workerThread': 'Thread worker',
'intelligence.agentWork.kind.backgroundAgent': 'Agen latar belakang',
'intelligence.agentWork.kind.teamMember': 'Anggota tim',
'intelligence.agentWork.kind.workflowChild': 'Anak alur kerja',
'intelligence.agentWork.openThread': 'Buka utas',
'intelligence.agentWork.openWorker': 'Buka pekerja',
'intelligence.refine.objectiveDefault':
'Ubah tugas sumber menjadi tugas agen yang siap diimplementasikan: {title}',
'intelligence.refine.sourceLine': 'Sumber: {url}',
+33
View File
@@ -316,6 +316,9 @@ const messages: TranslationMap = {
'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.agentWork': "Lavoro dell'agente",
'memory.tab.agentWorkDescription':
'Un centro di comando per ogni esecuzione di agente in background, raggruppata in base a ciò che richiede il tuo intervento, ciò che è in corso e ciò che è terminato.',
'memory.tab.agents': 'Libreria',
'memory.tab.agentsDescription':
'Sfoglia e avvia gli agenti disponibili — ognuno con i propri strumenti, capacità e area di specializzazione.',
@@ -2870,6 +2873,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- Indirizzo: {url}',
'intelligence.workTask.closingInstruction':
'Inizia riformulando brevemente il piano di implementazione concreto, poi eseguilo. Mantieni i progressi visibili in questo thread e aggiorna la bacheca delle attività quando cambia lo stato del lavoro.',
'intelligence.agentWork.subtitle':
'Ogni esecuzione di agente in background, raggruppata per stato del ciclo di vita.',
'intelligence.agentWork.loading': "Caricamento del lavoro dell'agente…",
'intelligence.agentWork.failedToLoad': "Impossibile caricare il lavoro dell'agente",
'intelligence.agentWork.empty': 'Ancora nessuna esecuzione di agente in background.',
'intelligence.agentWork.bucket.needsInput': 'Richiede intervento',
'intelligence.agentWork.bucket.working': 'In corso',
'intelligence.agentWork.bucket.completed': 'Completato',
'intelligence.agentWork.bucket.failed': 'Fallito',
'intelligence.agentWork.bucket.stopped': 'Fermato',
'intelligence.agentWork.column.agent': 'Agente',
'intelligence.agentWork.column.status': 'Stato',
'intelligence.agentWork.column.elapsed': 'Trascorso',
'intelligence.agentWork.column.cost': 'Costo',
'intelligence.agentWork.column.tokens': 'Gettoni',
'intelligence.agentWork.status.pending': 'In sospeso',
'intelligence.agentWork.status.running': 'In esecuzione',
'intelligence.agentWork.status.awaitingUser': 'Richiede intervento',
'intelligence.agentWork.status.paused': 'In pausa',
'intelligence.agentWork.status.completed': 'Completato',
'intelligence.agentWork.status.failed': 'Fallito',
'intelligence.agentWork.status.cancelled': 'Annullato',
'intelligence.agentWork.status.interrupted': 'Interrotto',
'intelligence.agentWork.kind.subagent': 'Sottoagente',
'intelligence.agentWork.kind.workerThread': 'Thread worker',
'intelligence.agentWork.kind.backgroundAgent': 'Agente in background',
'intelligence.agentWork.kind.teamMember': 'Membro del team',
'intelligence.agentWork.kind.workflowChild': 'Figlio del flusso di lavoro',
'intelligence.agentWork.openThread': 'Apri thread',
'intelligence.agentWork.openWorker': 'Apri worker',
'intelligence.refine.objectiveDefault':
"Trasforma l'attività di origine in un'attività dell'agente pronta per l'implementazione: {title}",
'intelligence.refine.sourceLine': 'Origine: {url}',
+33
View File
@@ -309,6 +309,9 @@ const messages: TranslationMap = {
'memory.tab.tasksDescription':
'작업을 만들고 추적하세요 — 본인의 할 일 목록과 에이전트가 대화를 통해 구성한 보드가 모두 포함됩니다.',
'memory.tab.subconscious': '잠재의식',
'memory.tab.agentWork': '에이전트 작업',
'memory.tab.agentWorkDescription':
'모든 백그라운드 에이전트 실행을 위한 명령 센터 — 입력이 필요한 것, 진행 중인 것, 완료된 것별로 그룹화됩니다.',
'memory.tab.agents': '라이브러리',
'memory.tab.agentsDescription':
'사용 가능한 에이전트를 탐색하고 실행하세요 — 각 에이전트는 고유한 도구, 기능 및 전문 분야를 갖추고 있습니다.',
@@ -2800,6 +2803,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- 링크: {url}',
'intelligence.workTask.closingInstruction':
'먼저 구체적인 구현 계획을 간략히 다시 설명한 다음 실행하세요. 이 스레드에서 진행 상황을 계속 보이게 하고 작업 상태가 바뀌면 작업 보드를 업데이트하세요.',
'intelligence.agentWork.subtitle':
'모든 백그라운드 에이전트 실행을 수명 주기 상태별로 그룹화합니다.',
'intelligence.agentWork.loading': '에이전트 작업 불러오는 중…',
'intelligence.agentWork.failedToLoad': '에이전트 작업을 불러오지 못했습니다',
'intelligence.agentWork.empty': '아직 백그라운드 에이전트 실행이 없습니다.',
'intelligence.agentWork.bucket.needsInput': '입력 필요',
'intelligence.agentWork.bucket.working': '작업 중',
'intelligence.agentWork.bucket.completed': '완료됨',
'intelligence.agentWork.bucket.failed': '실패',
'intelligence.agentWork.bucket.stopped': '중지됨',
'intelligence.agentWork.column.agent': '에이전트',
'intelligence.agentWork.column.status': '상태',
'intelligence.agentWork.column.elapsed': '경과 시간',
'intelligence.agentWork.column.cost': '비용',
'intelligence.agentWork.column.tokens': '토큰',
'intelligence.agentWork.status.pending': '대기 중',
'intelligence.agentWork.status.running': '실행 중',
'intelligence.agentWork.status.awaitingUser': '입력 필요',
'intelligence.agentWork.status.paused': '일시 중지됨',
'intelligence.agentWork.status.completed': '완료됨',
'intelligence.agentWork.status.failed': '실패',
'intelligence.agentWork.status.cancelled': '취소됨',
'intelligence.agentWork.status.interrupted': '중단됨',
'intelligence.agentWork.kind.subagent': '하위 에이전트',
'intelligence.agentWork.kind.workerThread': '워커 스레드',
'intelligence.agentWork.kind.backgroundAgent': '백그라운드 에이전트',
'intelligence.agentWork.kind.teamMember': '팀 구성원',
'intelligence.agentWork.kind.workflowChild': '워크플로 하위 항목',
'intelligence.agentWork.openThread': '스레드 열기',
'intelligence.agentWork.openWorker': '워커 열기',
'intelligence.refine.objectiveDefault':
'소스 작업을 구현 준비가 된 에이전트 작업으로 전환하세요: {title}',
'intelligence.refine.sourceLine': '소스: {url}',
+33
View File
@@ -314,6 +314,9 @@ const messages: TranslationMap = {
'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.agentWork': 'Praca agenta',
'memory.tab.agentWorkDescription':
'Centrum dowodzenia dla każdego przebiegu agenta w tle — pogrupowane według tego, co wymaga Twojej reakcji, co działa i co zostało ukończone.',
'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.',
@@ -2861,6 +2864,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- Adres: {url}',
'intelligence.workTask.closingInstruction':
'Zacznij od krótkiego powtórzenia konkretnego planu wdrożenia, a następnie go zrealizuj. Utrzymuj widoczność postępów w tym wątku i aktualizuj tablicę zadań, gdy zmienia się stan pracy.',
'intelligence.agentWork.subtitle':
'Każdy przebieg agenta w tle, pogrupowany według stanu cyklu życia.',
'intelligence.agentWork.loading': 'Ładowanie pracy agenta…',
'intelligence.agentWork.failedToLoad': 'Nie udało się załadować pracy agenta',
'intelligence.agentWork.empty': 'Brak przebiegów agenta w tle.',
'intelligence.agentWork.bucket.needsInput': 'Wymaga reakcji',
'intelligence.agentWork.bucket.working': 'W toku',
'intelligence.agentWork.bucket.completed': 'Ukończone',
'intelligence.agentWork.bucket.failed': 'Niepowodzenie',
'intelligence.agentWork.bucket.stopped': 'Zatrzymane',
'intelligence.agentWork.column.agent': 'Agent',
'intelligence.agentWork.column.status': 'Status',
'intelligence.agentWork.column.elapsed': 'Czas trwania',
'intelligence.agentWork.column.cost': 'Koszt',
'intelligence.agentWork.column.tokens': 'Tokeny',
'intelligence.agentWork.status.pending': 'Oczekujące',
'intelligence.agentWork.status.running': 'W toku',
'intelligence.agentWork.status.awaitingUser': 'Wymaga reakcji',
'intelligence.agentWork.status.paused': 'Wstrzymane',
'intelligence.agentWork.status.completed': 'Ukończone',
'intelligence.agentWork.status.failed': 'Niepowodzenie',
'intelligence.agentWork.status.cancelled': 'Anulowane',
'intelligence.agentWork.status.interrupted': 'Przerwane',
'intelligence.agentWork.kind.subagent': 'Podagent',
'intelligence.agentWork.kind.workerThread': 'Wątek workera',
'intelligence.agentWork.kind.backgroundAgent': 'Agent w tle',
'intelligence.agentWork.kind.teamMember': 'Członek zespołu',
'intelligence.agentWork.kind.workflowChild': 'Element podrzędny przepływu',
'intelligence.agentWork.openThread': 'Otwórz wątek',
'intelligence.agentWork.openWorker': 'Otwórz proces',
'intelligence.refine.objectiveDefault':
'Przekształć zadanie źródłowe w gotowe do wdrożenia zadanie agenta: {title}',
'intelligence.refine.sourceLine': 'Źródło: {url}',
+33
View File
@@ -318,6 +318,9 @@ const messages: TranslationMap = {
'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.agentWork': 'Trabalho do agente',
'memory.tab.agentWorkDescription':
'Um centro de comando para cada execução de agente em segundo plano, agrupada conforme o que precisa da sua intervenção, o que está em andamento e o que foi concluído.',
'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.',
@@ -2873,6 +2876,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- Endereço: {url}',
'intelligence.workTask.closingInstruction':
'Comece reformulando brevemente o plano de implementação concreto e depois execute-o. Mantenha o progresso visível neste tópico e atualize o quadro de tarefas quando o estado do trabalho mudar.',
'intelligence.agentWork.subtitle':
'Cada execução de agente em segundo plano, agrupada por estado do ciclo de vida.',
'intelligence.agentWork.loading': 'Carregando trabalho do agente…',
'intelligence.agentWork.failedToLoad': 'Não foi possível carregar o trabalho do agente',
'intelligence.agentWork.empty': 'Ainda não há execuções de agente em segundo plano.',
'intelligence.agentWork.bucket.needsInput': 'Requer intervenção',
'intelligence.agentWork.bucket.working': 'Em andamento',
'intelligence.agentWork.bucket.completed': 'Concluído',
'intelligence.agentWork.bucket.failed': 'Falhou',
'intelligence.agentWork.bucket.stopped': 'Parado',
'intelligence.agentWork.column.agent': 'Agente',
'intelligence.agentWork.column.status': 'Status',
'intelligence.agentWork.column.elapsed': 'Decorrido',
'intelligence.agentWork.column.cost': 'Custo',
'intelligence.agentWork.column.tokens': 'Tokens',
'intelligence.agentWork.status.pending': 'Pendente',
'intelligence.agentWork.status.running': 'Em andamento',
'intelligence.agentWork.status.awaitingUser': 'Requer intervenção',
'intelligence.agentWork.status.paused': 'Pausado',
'intelligence.agentWork.status.completed': 'Concluído',
'intelligence.agentWork.status.failed': 'Falhou',
'intelligence.agentWork.status.cancelled': 'Cancelado',
'intelligence.agentWork.status.interrupted': 'Interrompido',
'intelligence.agentWork.kind.subagent': 'Subagente',
'intelligence.agentWork.kind.workerThread': 'Thread de worker',
'intelligence.agentWork.kind.backgroundAgent': 'Agente em segundo plano',
'intelligence.agentWork.kind.teamMember': 'Membro da equipe',
'intelligence.agentWork.kind.workflowChild': 'Filho de fluxo de trabalho',
'intelligence.agentWork.openThread': 'Abrir tópico',
'intelligence.agentWork.openWorker': 'Abrir trabalhador',
'intelligence.refine.objectiveDefault':
'Transforme a tarefa de origem em uma tarefa de agente pronta para implementação: {title}',
'intelligence.refine.sourceLine': 'Origem: {url}',
+33
View File
@@ -311,6 +311,9 @@ const messages: TranslationMap = {
'memory.tab.tasksDescription':
'Создавайте и отслеживайте задачи — ваши личные дела и доски, которые агенты формируют в ходе разговоров.',
'memory.tab.subconscious': 'Подсознание',
'memory.tab.agentWork': 'Работа агента',
'memory.tab.agentWorkDescription':
'Центр управления для каждого фонового запуска агента — сгруппировано по тому, что требует вашего участия, что выполняется и что завершено.',
'memory.tab.agents': 'Библиотека',
'memory.tab.agentsDescription':
'Просматривайте и запускайте доступных агентов — у каждого свои инструменты, возможности и область специализации.',
@@ -2846,6 +2849,36 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- Ссылка: {url}',
'intelligence.workTask.closingInstruction':
'Начните с краткого повторения конкретного плана реализации, затем выполните его. Поддерживайте видимость прогресса в этой ветке и обновляйте доску задач при изменении состояния работы.',
'intelligence.agentWork.subtitle':
'Каждый фоновый запуск агента, сгруппированный по состоянию жизненного цикла.',
'intelligence.agentWork.loading': 'Загрузка работы агента…',
'intelligence.agentWork.failedToLoad': 'Не удалось загрузить работу агента',
'intelligence.agentWork.empty': 'Фоновых запусков агента пока нет.',
'intelligence.agentWork.bucket.needsInput': 'Нужны данные',
'intelligence.agentWork.bucket.working': 'В работе',
'intelligence.agentWork.bucket.completed': 'Завершено',
'intelligence.agentWork.bucket.failed': 'Ошибка',
'intelligence.agentWork.bucket.stopped': 'Остановлено',
'intelligence.agentWork.column.agent': 'Агент',
'intelligence.agentWork.column.status': 'Статус',
'intelligence.agentWork.column.elapsed': 'Прошло',
'intelligence.agentWork.column.cost': 'Расходы',
'intelligence.agentWork.column.tokens': 'Токены',
'intelligence.agentWork.status.pending': 'В ожидании',
'intelligence.agentWork.status.running': 'В работе',
'intelligence.agentWork.status.awaitingUser': 'Нужны данные',
'intelligence.agentWork.status.paused': 'Приостановлено',
'intelligence.agentWork.status.completed': 'Завершено',
'intelligence.agentWork.status.failed': 'Ошибка',
'intelligence.agentWork.status.cancelled': 'Отменено',
'intelligence.agentWork.status.interrupted': 'Прервано',
'intelligence.agentWork.kind.subagent': 'Субагент',
'intelligence.agentWork.kind.workerThread': 'Чат воркера',
'intelligence.agentWork.kind.backgroundAgent': 'Фоновый агент',
'intelligence.agentWork.kind.teamMember': 'Участник команды',
'intelligence.agentWork.kind.workflowChild': 'Дочерний элемент рабочего процесса',
'intelligence.agentWork.openThread': 'Открыть тред',
'intelligence.agentWork.openWorker': 'Открыть воркер',
'intelligence.refine.objectiveDefault':
'Превратите исходную задачу в готовую к реализации задачу агента: {title}',
'intelligence.refine.sourceLine': 'Источник: {url}',
+32
View File
@@ -296,6 +296,9 @@ const messages: TranslationMap = {
'memory.tab.tasksDescription':
'创建并跟踪任务——包括您自己的待办事项以及智能体在对话中创建的看板。',
'memory.tab.subconscious': '潜意识',
'memory.tab.agentWork': '智能体工作',
'memory.tab.agentWorkDescription':
'所有后台智能体运行的指挥中心——按需要你处理的、正在运行的和已完成的进行分组。',
'memory.tab.agents': '资料库',
'memory.tab.agentsDescription':
'浏览并运行可用的智能体——每个智能体都有自己的工具、能力和专注领域。',
@@ -2685,6 +2688,35 @@ const messages: TranslationMap = {
'intelligence.workTask.urlLine': '- 网址:{url}',
'intelligence.workTask.closingInstruction':
'先简要重述具体的实施计划,然后执行它。在此线程中保持进度可见,并在工作状态变化时更新任务看板。',
'intelligence.agentWork.subtitle': '所有后台智能体运行,按生命周期状态分组。',
'intelligence.agentWork.loading': '正在加载智能体工作…',
'intelligence.agentWork.failedToLoad': '无法加载智能体工作',
'intelligence.agentWork.empty': '暂无后台智能体运行。',
'intelligence.agentWork.bucket.needsInput': '需要输入',
'intelligence.agentWork.bucket.working': '进行中',
'intelligence.agentWork.bucket.completed': '已完成',
'intelligence.agentWork.bucket.failed': '失败',
'intelligence.agentWork.bucket.stopped': '已停止',
'intelligence.agentWork.column.agent': '智能体',
'intelligence.agentWork.column.status': '状态',
'intelligence.agentWork.column.elapsed': '已用时间',
'intelligence.agentWork.column.cost': '成本',
'intelligence.agentWork.column.tokens': '令牌',
'intelligence.agentWork.status.pending': '待定',
'intelligence.agentWork.status.running': '进行中',
'intelligence.agentWork.status.awaitingUser': '需要输入',
'intelligence.agentWork.status.paused': '已暂停',
'intelligence.agentWork.status.completed': '已完成',
'intelligence.agentWork.status.failed': '失败',
'intelligence.agentWork.status.cancelled': '已取消',
'intelligence.agentWork.status.interrupted': '已中断',
'intelligence.agentWork.kind.subagent': '子智能体',
'intelligence.agentWork.kind.workerThread': '工作线程',
'intelligence.agentWork.kind.backgroundAgent': '后台智能体',
'intelligence.agentWork.kind.teamMember': '团队成员',
'intelligence.agentWork.kind.workflowChild': '工作流子项',
'intelligence.agentWork.openThread': '打开会话',
'intelligence.agentWork.openWorker': '打开工作线程',
'intelligence.refine.objectiveDefault': '将来源任务转化为可立即实施的智能体任务:{title}',
'intelligence.refine.sourceLine': '来源:{url}',
'intelligence.refine.sourceIntake': '来源:任务来源接收',
+17 -1
View File
@@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router-dom';
import { ConfirmationModal } from '../components/intelligence/ConfirmationModal';
import IntelligenceAgentsTab from '../components/intelligence/IntelligenceAgentsTab';
import IntelligenceAgentWorkTab from '../components/intelligence/IntelligenceAgentWorkTab';
import IntelligenceSubconsciousTab from '../components/intelligence/IntelligenceSubconsciousTab';
import IntelligenceTasksTab from '../components/intelligence/IntelligenceTasksTab';
import MemorySection from '../components/intelligence/MemorySection';
@@ -22,12 +23,20 @@ import type {
} from '../types/intelligence';
import { IS_DEV } from '../utils/config';
type IntelligenceTab = 'memory' | 'subconscious' | 'tasks' | 'agents' | 'workflows' | 'council';
type IntelligenceTab =
| 'memory'
| 'subconscious'
| 'tasks'
| 'agent-work'
| 'agents'
| 'workflows'
| 'council';
const INTELLIGENCE_TABS: IntelligenceTab[] = [
'memory',
'subconscious',
'tasks',
'agent-work',
'agents',
'workflows',
'council',
@@ -128,6 +137,11 @@ export default function Intelligence() {
devOnly?: boolean;
}[] = [
{ id: 'tasks', label: t('memory.tab.tasks'), description: t('memory.tab.tasksDescription') },
{
id: 'agent-work',
label: t('memory.tab.agentWork'),
description: t('memory.tab.agentWorkDescription'),
},
{ id: 'memory', label: t('memory.tab.memory') },
{ id: 'subconscious', label: t('memory.tab.subconscious') },
{
@@ -215,6 +229,8 @@ export default function Intelligence() {
{activeTab === 'tasks' && <IntelligenceTasksTab />}
{activeTab === 'agent-work' && <IntelligenceAgentWorkTab />}
{activeTab === 'agents' && <IntelligenceAgentsTab />}
{activeTab === 'workflows' && <WorkflowsTab />}
+88
View File
@@ -0,0 +1,88 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { callCoreRpc } from '../coreRpcClient';
import { agentWorkApi, type AgentWorkResponse } from './agentWorkApi';
vi.mock('../coreRpcClient', () => ({ callCoreRpc: vi.fn() }));
const mockCall = vi.mocked(callCoreRpc);
function response(): AgentWorkResponse {
return {
total: 2,
groups: [
{ bucket: 'needs_input', count: 0, rows: [] },
{
bucket: 'working',
count: 1,
rows: [
{
runId: 'run-1',
kind: 'subagent',
agentId: 'agent-a',
displayName: 'Researcher',
bucket: 'working',
status: 'running',
workerThreadId: 'thread-w',
startedAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:01:00Z',
elapsedMs: 60000,
inputTokens: 100,
outputTokens: 50,
costUsd: 0.01,
toolCount: 2,
},
],
},
{ bucket: 'completed', count: 1, rows: [] },
{ bucket: 'failed', count: 0, rows: [] },
{ bucket: 'stopped', count: 0, rows: [] },
],
};
}
describe('agentWorkApi', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('list calls the agent_work_list RPC with no params when limit is omitted', async () => {
mockCall.mockResolvedValueOnce(response());
await agentWorkApi.list();
expect(mockCall).toHaveBeenCalledWith({ method: 'openhuman.agent_work_list', params: {} });
});
it('list forwards an explicit limit', async () => {
mockCall.mockResolvedValueOnce(response());
await agentWorkApi.list(25);
expect(mockCall).toHaveBeenCalledWith({
method: 'openhuman.agent_work_list',
params: { limit: 25 },
});
});
it('list rejects a non-positive or non-integer limit without calling core', async () => {
await expect(agentWorkApi.list(0)).rejects.toThrow('positive integer');
await expect(agentWorkApi.list(-5)).rejects.toThrow('positive integer');
await expect(agentWorkApi.list(1.5)).rejects.toThrow('positive integer');
expect(mockCall).not.toHaveBeenCalled();
});
it('list returns the grouped response unchanged (wire is already camelCase)', async () => {
mockCall.mockResolvedValueOnce(response());
const result = await agentWorkApi.list();
expect(result.total).toBe(2);
expect(result.groups).toHaveLength(5);
expect(result.groups.map(g => g.bucket)).toEqual([
'needs_input',
'working',
'completed',
'failed',
'stopped',
]);
const working = result.groups.find(g => g.bucket === 'working');
expect(working?.count).toBe(1);
expect(working?.rows[0].displayName).toBe('Researcher');
expect(working?.rows[0].workerThreadId).toBe('thread-w');
});
});
+76
View File
@@ -0,0 +1,76 @@
/**
* Frontend client for the Background Agent Command Center surface
* (`openhuman.agent_work_list`). The Rust handler aggregates every tracked
* background agent run into five lifecycle buckets and returns the rows
* pre-grouped so the UI renders them in a stable order.
*
* The wire payload is already camelCase (the Rust controller serializes with
* `#[serde(rename_all = "camelCase")]`), so this client only types the shape
* and forwards the optional `limit`. No snake/camel transform is needed.
*/
import debug from 'debug';
import { callCoreRpc } from '../coreRpcClient';
const log = debug('agentWorkApi');
/**
* Lifecycle bucket a run is sorted into. The handler always emits all five,
* in this display order, even when a bucket has zero rows.
*/
export type AgentWorkBucket = 'needs_input' | 'working' | 'completed' | 'failed' | 'stopped';
/** A single background agent run. Mirrors the Rust `AgentWorkRow`. */
export interface AgentWorkRow {
runId: string;
kind: string;
agentId?: string;
displayName?: string;
bucket: AgentWorkBucket;
status: string;
parentThreadId?: string;
workerThreadId?: string;
summary?: string;
error?: string;
startedAt: string;
updatedAt: string;
elapsedMs?: number;
inputTokens: number;
outputTokens: number;
costUsd: number;
toolCount: number;
}
/** A bucket of rows plus its precomputed count. Mirrors the Rust group. */
export interface AgentWorkGroup {
bucket: AgentWorkBucket;
count: number;
rows: AgentWorkRow[];
}
/** Full response from `openhuman.agent_work_list`. */
export interface AgentWorkResponse {
groups: AgentWorkGroup[];
total: number;
}
export const agentWorkApi = {
/**
* List all tracked background agent runs, grouped by lifecycle bucket.
*
* @param limit Optional cap on the number of rows returned (newest first,
* applied server-side). Omit to use the handler default.
*/
list: async (limit?: number): Promise<AgentWorkResponse> => {
if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {
throw new Error('agentWorkApi.list: limit must be a positive integer');
}
log('list limit=%o', limit);
const response = await callCoreRpc<AgentWorkResponse>({
method: 'openhuman.agent_work_list',
params: limit === undefined ? {} : { limit },
});
log('list received groups=%d total=%d', response.groups.length, response.total);
return response;
},
};