diff --git a/src/pages/agent-office/components/LogTab.jsx b/src/pages/agent-office/components/LogTab.jsx
index 447c180..91c8b4e 100644
--- a/src/pages/agent-office/components/LogTab.jsx
+++ b/src/pages/agent-office/components/LogTab.jsx
@@ -15,7 +15,7 @@ export default function LogTab({ agentId, refreshTrigger }) {
useEffect(() => {
let cancelled = false;
getAgentLogs(agentId, 50).then(data => {
- if (!cancelled) setLogs(data || []);
+ if (!cancelled) setLogs(Array.isArray(data) ? data : (data?.logs || []));
});
return () => { cancelled = true; };
}, [agentId, refreshTrigger]);
diff --git a/src/pages/agent-office/components/TaskTab.jsx b/src/pages/agent-office/components/TaskTab.jsx
index 3c9412f..967920f 100644
--- a/src/pages/agent-office/components/TaskTab.jsx
+++ b/src/pages/agent-office/components/TaskTab.jsx
@@ -27,7 +27,7 @@ export default function TaskTab({ agentId, refreshTrigger }) {
useEffect(() => {
let cancelled = false;
getAgentTasks(agentId, 20).then(data => {
- if (!cancelled) setTasks(data || []);
+ if (!cancelled) setTasks(Array.isArray(data) ? data : (data?.tasks || []));
});
return () => { cancelled = true; };
}, [agentId, refreshTrigger]);
diff --git a/src/pages/agent-office/components/TaskTab.test.jsx b/src/pages/agent-office/components/TaskTab.test.jsx
new file mode 100644
index 0000000..7d93092
--- /dev/null
+++ b/src/pages/agent-office/components/TaskTab.test.jsx
@@ -0,0 +1,36 @@
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, waitFor } from '@testing-library/react';
+import TaskTab from './TaskTab.jsx';
+
+const mockGetAgentTasks = vi.fn();
+vi.mock('../../../api', () => ({
+ getAgentTasks: (...args) => mockGetAgentTasks(...args),
+}));
+
+describe('TaskTab response shape handling', () => {
+ it('백엔드가 {tasks: [...]} 객체로 wrapping해서 응답해도 .map 깨지지 않음', async () => {
+ mockGetAgentTasks.mockResolvedValueOnce({
+ tasks: [
+ { id: 't1', task_type: 'compose', status: 'succeeded', created_at: '2026-05-18T08:00:00Z' },
+ { id: 't2', task_type: 'fetch_news', status: 'failed', created_at: '2026-05-18T08:05:00Z' },
+ ],
+ });
+ render();
+ await waitFor(() => expect(screen.getByText('compose')).toBeInTheDocument());
+ expect(screen.getByText('fetch_news')).toBeInTheDocument();
+ });
+
+ it('백엔드가 bare array를 반환해도 동작 (backward compat)', async () => {
+ mockGetAgentTasks.mockResolvedValueOnce([
+ { id: 't9', task_type: 'compose', status: 'succeeded', created_at: '2026-05-18T08:00:00Z' },
+ ]);
+ render();
+ await waitFor(() => expect(screen.getByText('compose')).toBeInTheDocument());
+ });
+
+ it('응답이 falsy/empty이면 No tasks yet 표시', async () => {
+ mockGetAgentTasks.mockResolvedValueOnce({ tasks: [] });
+ render();
+ await waitFor(() => expect(screen.getByText('No tasks yet')).toBeInTheDocument());
+ });
+});