repoburg 1.3.188 β 1.3.196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-pane/.prettierignore +5 -0
- package/agent-pane/.prettierrc +8 -0
- package/agent-pane/backend/coverage/base.css +224 -0
- package/agent-pane/backend/coverage/block-navigation.js +87 -0
- package/agent-pane/backend/coverage/clover.xml +887 -0
- package/agent-pane/backend/coverage/coverage-final.json +10 -0
- package/agent-pane/backend/coverage/favicon.png +0 -0
- package/agent-pane/backend/coverage/index.html +176 -0
- package/agent-pane/backend/coverage/prettify.css +1 -0
- package/agent-pane/backend/coverage/prettify.js +2 -0
- package/agent-pane/backend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/backend/coverage/sorter.js +210 -0
- package/agent-pane/backend/coverage/src/codex/codex-provider.ts.html +1402 -0
- package/agent-pane/backend/coverage/src/codex/index.html +131 -0
- package/agent-pane/backend/coverage/src/codex/item-mapper.ts.html +331 -0
- package/agent-pane/backend/coverage/src/db/index.html +131 -0
- package/agent-pane/backend/coverage/src/db/index.ts.html +436 -0
- package/agent-pane/backend/coverage/src/db/schema.ts.html +175 -0
- package/agent-pane/backend/coverage/src/index.html +116 -0
- package/agent-pane/backend/coverage/src/index.ts.html +262 -0
- package/agent-pane/backend/coverage/src/routes/index.html +146 -0
- package/agent-pane/backend/coverage/src/routes/sessions.ts.html +991 -0
- package/agent-pane/backend/coverage/src/routes/turns.ts.html +256 -0
- package/agent-pane/backend/coverage/src/routes/workspaces.ts.html +406 -0
- package/agent-pane/backend/coverage/src/ws/index.html +116 -0
- package/agent-pane/backend/coverage/src/ws/index.ts.html +238 -0
- package/agent-pane/backend/data.db +0 -0
- package/agent-pane/backend/data.db-shm +0 -0
- package/agent-pane/backend/data.db-wal +0 -0
- package/agent-pane/backend/dist/index.js +70869 -0
- package/agent-pane/backend/package-lock.json +4529 -1013
- package/agent-pane/backend/package.json +10 -6
- package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +163 -0
- package/agent-pane/backend/src/claude/claude-message-mapper.ts +154 -0
- package/agent-pane/backend/src/claude/claude-provider.test.ts +65 -0
- package/agent-pane/backend/src/claude/claude-provider.ts +482 -0
- package/agent-pane/backend/src/claude/claude-types.ts +33 -0
- package/agent-pane/backend/src/codex/app-server-client.test.ts +183 -0
- package/agent-pane/backend/src/codex/app-server-client.ts +225 -0
- package/agent-pane/backend/src/codex/app-server-types.ts +259 -0
- package/agent-pane/backend/src/codex/codex-provider-runtime-config.test.ts +56 -0
- package/agent-pane/backend/src/codex/codex-provider.test.ts +60 -0
- package/agent-pane/backend/src/codex/codex-provider.ts +521 -265
- package/agent-pane/backend/src/codex/item-mapper.test.ts +221 -0
- package/agent-pane/backend/src/codex/item-mapper.ts +35 -8
- package/agent-pane/backend/src/codex/shared-app-server.ts +25 -0
- package/agent-pane/backend/src/db/index.test.ts +125 -0
- package/agent-pane/backend/src/db/index.ts +39 -8
- package/agent-pane/backend/src/db/schema.ts +28 -74
- package/agent-pane/backend/src/index.ts +29 -2
- package/agent-pane/backend/src/integration/__fixtures__/cancel-turn.json +29 -0
- package/agent-pane/backend/src/integration/__fixtures__/delta-streaming.json +40 -0
- package/agent-pane/backend/src/integration/__fixtures__/failed-turn.json +16 -0
- package/agent-pane/backend/src/integration/__fixtures__/mcp-tool-call.json +60 -0
- package/agent-pane/backend/src/integration/__fixtures__/missing-item-started.json +34 -0
- package/agent-pane/backend/src/integration/__fixtures__/multi-item-turn.json +85 -0
- package/agent-pane/backend/src/integration/__fixtures__/multi-turn.json +48 -0
- package/agent-pane/backend/src/integration/__fixtures__/simple-turn.json +35 -0
- package/agent-pane/backend/src/integration/delta-streaming.test.ts +130 -0
- package/agent-pane/backend/src/integration/helpers/app-server-mock.ts +272 -0
- package/agent-pane/backend/src/integration/helpers/test-app.ts +108 -0
- package/agent-pane/backend/src/integration/helpers/ws-capture.ts +64 -0
- package/agent-pane/backend/src/integration/item-lifecycle.test.ts +133 -0
- package/agent-pane/backend/src/integration/mcp-tool-call.test.ts +102 -0
- package/agent-pane/backend/src/integration/missing-item-started.test.ts +91 -0
- package/agent-pane/backend/src/integration/multi-turn.test.ts +121 -0
- package/agent-pane/backend/src/integration/session-operations.test.ts +168 -0
- package/agent-pane/backend/src/integration/title-generation.test.ts +109 -0
- package/agent-pane/backend/src/integration/turn-cancel.test.ts +111 -0
- package/agent-pane/backend/src/integration/turn-failure.test.ts +97 -0
- package/agent-pane/backend/src/integration/turn-lifecycle.test.ts +141 -0
- package/agent-pane/backend/src/providers/index.ts +14 -0
- package/agent-pane/backend/src/providers/registry.test.ts +92 -0
- package/agent-pane/backend/src/providers/registry.ts +43 -0
- package/agent-pane/backend/src/providers/runtime-provider.ts +25 -0
- package/agent-pane/backend/src/routes/files.ts +31 -0
- package/agent-pane/backend/src/routes/mcp.ts +33 -0
- package/agent-pane/backend/src/routes/models.test.ts +92 -0
- package/agent-pane/backend/src/routes/models.ts +16 -0
- package/agent-pane/backend/src/routes/plugins.ts +40 -0
- package/agent-pane/backend/src/routes/providers.test.ts +69 -0
- package/agent-pane/backend/src/routes/providers.ts +57 -0
- package/agent-pane/backend/src/routes/sessions.test.ts +424 -0
- package/agent-pane/backend/src/routes/sessions.ts +295 -42
- package/agent-pane/backend/src/routes/skills.test.ts +114 -0
- package/agent-pane/backend/src/routes/skills.ts +70 -0
- package/agent-pane/backend/src/routes/turns.test.ts +152 -0
- package/agent-pane/backend/src/routes/turns.ts +36 -16
- package/agent-pane/backend/src/routes/workspaces.test.ts +197 -0
- package/agent-pane/backend/src/routes/workspaces.ts +73 -55
- package/agent-pane/backend/src/ws/index.test.ts +107 -0
- package/agent-pane/backend/src/ws/index.ts +19 -11
- package/agent-pane/backend/tsconfig.json +4 -2
- package/agent-pane/backend/vitest.config.ts +20 -0
- package/agent-pane/eslint.config.js +33 -0
- package/agent-pane/frontend/coverage/base.css +224 -0
- package/agent-pane/frontend/coverage/block-navigation.js +87 -0
- package/agent-pane/frontend/coverage/clover.xml +2962 -0
- package/agent-pane/frontend/coverage/coverage-final.json +37 -0
- package/agent-pane/frontend/coverage/favicon.png +0 -0
- package/agent-pane/frontend/coverage/index.html +251 -0
- package/agent-pane/frontend/coverage/prettify.css +1 -0
- package/agent-pane/frontend/coverage/prettify.js +2 -0
- package/agent-pane/frontend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/frontend/coverage/sorter.js +210 -0
- package/agent-pane/frontend/coverage/src/App.tsx.html +217 -0
- package/agent-pane/frontend/coverage/src/AppInitializer.tsx.html +223 -0
- package/agent-pane/frontend/coverage/src/api/client.ts.html +187 -0
- package/agent-pane/frontend/coverage/src/api/index.html +161 -0
- package/agent-pane/frontend/coverage/src/api/sessions.ts.html +316 -0
- package/agent-pane/frontend/coverage/src/api/websocket.ts.html +544 -0
- package/agent-pane/frontend/coverage/src/api/workspaces.ts.html +199 -0
- package/agent-pane/frontend/coverage/src/components/CollapsibleToolCalls.tsx.html +226 -0
- package/agent-pane/frontend/coverage/src/components/CommandInputBar.tsx.html +382 -0
- package/agent-pane/frontend/coverage/src/components/CommandPalette.tsx.html +715 -0
- package/agent-pane/frontend/coverage/src/components/IdeWorkspace.tsx.html +160 -0
- package/agent-pane/frontend/coverage/src/components/ModelSelector.tsx.html +478 -0
- package/agent-pane/frontend/coverage/src/components/NewSessionDialog.tsx.html +472 -0
- package/agent-pane/frontend/coverage/src/components/RenderArea.tsx.html +574 -0
- package/agent-pane/frontend/coverage/src/components/RightPanel.tsx.html +205 -0
- package/agent-pane/frontend/coverage/src/components/RuntimeIcon.tsx.html +133 -0
- package/agent-pane/frontend/coverage/src/components/SessionTabs.tsx.html +217 -0
- package/agent-pane/frontend/coverage/src/components/StatusBar.tsx.html +271 -0
- package/agent-pane/frontend/coverage/src/components/StatusDot.tsx.html +109 -0
- package/agent-pane/frontend/coverage/src/components/Toast.tsx.html +313 -0
- package/agent-pane/frontend/coverage/src/components/TurnContent.tsx.html +1216 -0
- package/agent-pane/frontend/coverage/src/components/WorkspaceSidebar.tsx.html +1138 -0
- package/agent-pane/frontend/coverage/src/components/index.html +326 -0
- package/agent-pane/frontend/coverage/src/components/panels/ChangesPanel.tsx.html +247 -0
- package/agent-pane/frontend/coverage/src/components/panels/LogsPanel.tsx.html +160 -0
- package/agent-pane/frontend/coverage/src/components/panels/MetricsPanel.tsx.html +427 -0
- package/agent-pane/frontend/coverage/src/components/panels/TurnsPanel.tsx.html +397 -0
- package/agent-pane/frontend/coverage/src/components/panels/index.html +161 -0
- package/agent-pane/frontend/coverage/src/constants/index.html +116 -0
- package/agent-pane/frontend/coverage/src/constants/models.ts.html +406 -0
- package/agent-pane/frontend/coverage/src/hooks/index.html +146 -0
- package/agent-pane/frontend/coverage/src/hooks/useEscapeKey.ts.html +124 -0
- package/agent-pane/frontend/coverage/src/hooks/useOutsideClick.ts.html +142 -0
- package/agent-pane/frontend/coverage/src/hooks/useSessionStatus.ts.html +187 -0
- package/agent-pane/frontend/coverage/src/index.html +146 -0
- package/agent-pane/frontend/coverage/src/main.tsx.html +115 -0
- package/agent-pane/frontend/coverage/src/store/index.html +116 -0
- package/agent-pane/frontend/coverage/src/store/store.ts.html +2404 -0
- package/agent-pane/frontend/coverage/src/test/component-helpers.ts.html +190 -0
- package/agent-pane/frontend/coverage/src/test/fixtures.ts.html +367 -0
- package/agent-pane/frontend/coverage/src/test/index.html +131 -0
- package/agent-pane/frontend/coverage/src/types/index.html +116 -0
- package/agent-pane/frontend/coverage/src/types/index.ts.html +466 -0
- package/agent-pane/frontend/coverage/src/utils/changes.ts.html +202 -0
- package/agent-pane/frontend/coverage/src/utils/duration.ts.html +127 -0
- package/agent-pane/frontend/coverage/src/utils/index.html +131 -0
- package/agent-pane/frontend/dist/assets/index-Bg0PF2aV.css +1 -0
- package/agent-pane/frontend/dist/assets/index-BlybeX5e.js +281 -0
- package/agent-pane/frontend/dist/index.html +19 -0
- package/agent-pane/frontend/package-lock.json +2434 -213
- package/agent-pane/frontend/package.json +10 -3
- package/agent-pane/frontend/src/App.test.tsx +74 -0
- package/agent-pane/frontend/src/App.tsx +1 -1
- package/agent-pane/frontend/src/AppInitializer.test.tsx +142 -0
- package/agent-pane/frontend/src/AppInitializer.tsx +1 -1
- package/agent-pane/frontend/src/api/client.test.ts +126 -0
- package/agent-pane/frontend/src/api/client.ts +22 -29
- package/agent-pane/frontend/src/api/models.ts +7 -0
- package/agent-pane/frontend/src/api/providers.ts +12 -0
- package/agent-pane/frontend/src/api/sessions.test.ts +241 -0
- package/agent-pane/frontend/src/api/sessions.ts +43 -1
- package/agent-pane/frontend/src/api/skills.ts +12 -0
- package/agent-pane/frontend/src/api/websocket.test.ts +288 -0
- package/agent-pane/frontend/src/api/websocket.ts +27 -2
- package/agent-pane/frontend/src/api/workspaces.test.ts +112 -0
- package/agent-pane/frontend/src/api/workspaces.ts +17 -3
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +49 -0
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +2 -5
- package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +133 -0
- package/agent-pane/frontend/src/components/CommandInputBar.tsx +296 -17
- package/agent-pane/frontend/src/components/CommandPalette.test.tsx +230 -0
- package/agent-pane/frontend/src/components/CommandPalette.tsx +26 -23
- package/agent-pane/frontend/src/components/IdeWorkspace.tsx +1 -1
- package/agent-pane/frontend/src/components/ModelSelector.test.tsx +129 -0
- package/agent-pane/frontend/src/components/ModelSelector.tsx +191 -0
- package/agent-pane/frontend/src/components/NewSessionDialog.test.tsx +95 -0
- package/agent-pane/frontend/src/components/NewSessionDialog.tsx +58 -13
- package/agent-pane/frontend/src/components/RenderArea.test.tsx +160 -0
- package/agent-pane/frontend/src/components/RenderArea.tsx +18 -25
- package/agent-pane/frontend/src/components/RightPanel.test.tsx +43 -0
- package/agent-pane/frontend/src/components/RightPanel.tsx +1 -1
- package/agent-pane/frontend/src/components/RuntimeIcon.test.tsx +32 -0
- package/agent-pane/frontend/src/components/RuntimeIcon.tsx +1 -1
- package/agent-pane/frontend/src/components/SessionTabs.test.tsx +72 -0
- package/agent-pane/frontend/src/components/SessionTabs.tsx +12 -12
- package/agent-pane/frontend/src/components/StatusBar.test.tsx +56 -0
- package/agent-pane/frontend/src/components/StatusBar.tsx +25 -31
- package/agent-pane/frontend/src/components/StatusDot.test.tsx +25 -0
- package/agent-pane/frontend/src/components/StatusDot.tsx +2 -7
- package/agent-pane/frontend/src/components/Toast.test.tsx +79 -0
- package/agent-pane/frontend/src/components/Toast.tsx +2 -6
- package/agent-pane/frontend/src/components/TurnContent.test.tsx +287 -0
- package/agent-pane/frontend/src/components/TurnContent.tsx +107 -24
- package/agent-pane/frontend/src/components/WorkspaceSidebar.test.tsx +297 -0
- package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +33 -32
- package/agent-pane/frontend/src/components/input/OptionSubPopup.tsx +32 -0
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.test.tsx +63 -0
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.tsx +78 -0
- package/agent-pane/frontend/src/components/input/SlashPopup.test.tsx +96 -0
- package/agent-pane/frontend/src/components/input/SlashPopup.tsx +63 -0
- package/agent-pane/frontend/src/components/panels/ChangesPanel.test.tsx +61 -0
- package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +12 -39
- package/agent-pane/frontend/src/components/panels/LogsPanel.test.tsx +58 -0
- package/agent-pane/frontend/src/components/panels/LogsPanel.tsx +1 -1
- package/agent-pane/frontend/src/components/panels/MetricsPanel.test.tsx +71 -0
- package/agent-pane/frontend/src/components/panels/MetricsPanel.tsx +63 -31
- package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +83 -0
- package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +66 -7
- package/agent-pane/frontend/src/constants/models.test.ts +97 -0
- package/agent-pane/frontend/src/constants/models.ts +129 -0
- package/agent-pane/frontend/src/hooks/useEscapeKey.test.ts +42 -0
- package/agent-pane/frontend/src/hooks/useEscapeKey.ts +13 -0
- package/agent-pane/frontend/src/hooks/useOutsideClick.test.ts +92 -0
- package/agent-pane/frontend/src/hooks/useOutsideClick.ts +19 -0
- package/agent-pane/frontend/src/hooks/useProvider.ts +44 -0
- package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +114 -0
- package/agent-pane/frontend/src/hooks/useSessionStatus.ts +34 -0
- package/agent-pane/frontend/src/main.test.tsx +37 -0
- package/agent-pane/frontend/src/main.tsx +1 -1
- package/agent-pane/frontend/src/store/sessionSlice.ts +417 -0
- package/agent-pane/frontend/src/store/store.test.ts +1284 -0
- package/agent-pane/frontend/src/store/store.ts +16 -617
- package/agent-pane/frontend/src/store/uiSlice.ts +83 -0
- package/agent-pane/frontend/src/store/workspaceSlice.ts +77 -0
- package/agent-pane/frontend/src/store/wsSlice.ts +292 -0
- package/agent-pane/frontend/src/styles/app.css +113 -7
- package/agent-pane/frontend/src/test/component-helpers.ts +36 -0
- package/agent-pane/frontend/src/test/fixtures.ts +96 -0
- package/agent-pane/frontend/src/test-setup.ts +5 -0
- package/agent-pane/frontend/src/types/index.test.ts +128 -0
- package/agent-pane/frontend/src/types/index.ts +80 -104
- package/agent-pane/frontend/src/utils/changes.test.ts +81 -0
- package/agent-pane/frontend/src/utils/changes.ts +39 -0
- package/agent-pane/frontend/src/utils/duration.test.ts +71 -0
- package/agent-pane/frontend/src/utils/duration.ts +14 -0
- package/agent-pane/frontend/tsconfig.json +4 -1
- package/agent-pane/frontend/vite.config.ts +6 -0
- package/agent-pane/frontend/vitest.config.ts +29 -0
- package/agent-pane/package-lock.json +1527 -0
- package/agent-pane/package.json +19 -0
- package/agent-pane/shared/provider-types.ts +53 -0
- package/agent-pane/shared/types.ts +107 -0
- package/agentpane-branch-session.png +0 -0
- package/agentpane-expanded-all.png +0 -0
- package/agentpane-fixed-rendering.png +0 -0
- package/agentpane-focus-check.png +0 -0
- package/agentpane-initial.png +0 -0
- package/agentpane-mcp-detail.png +0 -0
- package/agentpane-no-focus-ring.png +0 -0
- package/agentpane-soft-focus.png +0 -0
- package/backend/dist/src/llm-provider/ollama-llm.provider.js +1 -0
- package/backend/dist/src/llm-provider/ollama-llm.provider.js.map +1 -1
- package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
- package/data.db +0 -0
- package/empty-state.png +0 -0
- package/frontend-home.png +0 -0
- package/model-selector-open.png +0 -0
- package/package.json +1 -1
- package/slash-popup-working.png +0 -0
- package/agent-pane/frontend/src/components/DiffViewer.tsx +0 -27
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
+
import Toast from './Toast.tsx';
|
|
4
|
+
import { useStore } from '../store/store.ts';
|
|
5
|
+
import { resetStore } from '../test/component-helpers.ts';
|
|
6
|
+
|
|
7
|
+
describe('Toast', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.useFakeTimers();
|
|
10
|
+
resetStore();
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
cleanup();
|
|
14
|
+
vi.useRealTimers();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns null when no toasts', () => {
|
|
18
|
+
const { container } = render(<Toast />);
|
|
19
|
+
expect(container.firstChild).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders toast container with toast items', () => {
|
|
23
|
+
useStore.setState({ toasts: [{ id: 't1', type: 'info', message: 'Hello' }] });
|
|
24
|
+
render(<Toast />);
|
|
25
|
+
expect(screen.getByText('Hello')).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByText('Hello').closest('.toast')).toHaveClass('toast-info');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('auto-dismisses after 4000ms', () => {
|
|
30
|
+
useStore.setState({ toasts: [{ id: 't1', type: 'success', message: 'Auto dismiss' }] });
|
|
31
|
+
render(<Toast />);
|
|
32
|
+
expect(useStore.getState().toasts).toHaveLength(1);
|
|
33
|
+
vi.advanceTimersByTime(4000);
|
|
34
|
+
expect(useStore.getState().toasts).toHaveLength(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('hover pauses auto-dismiss timer', () => {
|
|
38
|
+
useStore.setState({ toasts: [{ id: 't1', type: 'info', message: 'Hover test' }] });
|
|
39
|
+
render(<Toast />);
|
|
40
|
+
const toastEl = screen.getByText('Hover test').closest('.toast')!;
|
|
41
|
+
fireEvent.mouseEnter(toastEl);
|
|
42
|
+
vi.advanceTimersByTime(4000);
|
|
43
|
+
// Still present β timer was cleared
|
|
44
|
+
expect(useStore.getState().toasts).toHaveLength(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('mouse leave restarts timer with 2000ms delay', () => {
|
|
48
|
+
useStore.setState({ toasts: [{ id: 't1', type: 'info', message: 'Mouse leave' }] });
|
|
49
|
+
render(<Toast />);
|
|
50
|
+
const toastEl = screen.getByText('Mouse leave').closest('.toast')!;
|
|
51
|
+
fireEvent.mouseEnter(toastEl);
|
|
52
|
+
fireEvent.mouseLeave(toastEl);
|
|
53
|
+
vi.advanceTimersByTime(1999);
|
|
54
|
+
expect(useStore.getState().toasts).toHaveLength(1);
|
|
55
|
+
vi.advanceTimersByTime(1);
|
|
56
|
+
expect(useStore.getState().toasts).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('undo button calls undoAction then removes toast', () => {
|
|
60
|
+
const undoFn = vi.fn();
|
|
61
|
+
useStore.setState({
|
|
62
|
+
toasts: [{ id: 't1', type: 'error', message: 'Deleted', undoAction: undoFn, undoLabel: 'Undo' }],
|
|
63
|
+
});
|
|
64
|
+
render(<Toast />);
|
|
65
|
+
const undoBtn = document.querySelector('.toast-undo') as HTMLElement;
|
|
66
|
+
fireEvent.click(undoBtn);
|
|
67
|
+
expect(undoFn).toHaveBeenCalled();
|
|
68
|
+
expect(useStore.getState().toasts).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('close button removes toast', () => {
|
|
72
|
+
useStore.setState({ toasts: [{ id: 't1', type: 'warn', message: 'Warning' }] });
|
|
73
|
+
render(<Toast />);
|
|
74
|
+
// The close button contains an X icon β find by the .toast-close class
|
|
75
|
+
const closeBtn = document.querySelector('.toast-close') as HTMLElement;
|
|
76
|
+
fireEvent.click(closeBtn);
|
|
77
|
+
expect(useStore.getState().toasts).toHaveLength(0);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -40,11 +40,7 @@ function ToastItem({ toast }: { toast: ToastType }) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
|
-
<div
|
|
44
|
-
className={`toast toast-${toast.type}`}
|
|
45
|
-
onMouseEnter={handleMouseEnter}
|
|
46
|
-
onMouseLeave={handleMouseLeave}
|
|
47
|
-
>
|
|
43
|
+
<div className={`toast toast-${toast.type}`} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
|
48
44
|
<Icon size={16} />
|
|
49
45
|
<span className="toast-message">{toast.message}</span>
|
|
50
46
|
{toast.undoAction && (
|
|
@@ -77,4 +73,4 @@ export default function Toast() {
|
|
|
77
73
|
))}
|
|
78
74
|
</div>
|
|
79
75
|
);
|
|
80
|
-
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
+
import TurnContent from './TurnContent.tsx';
|
|
4
|
+
import { makeTurn, makeItem } from '../test/fixtures.ts';
|
|
5
|
+
import type { Turn, Item } from '@agentpane/shared';
|
|
6
|
+
|
|
7
|
+
/** Helper: build a turn with items. */
|
|
8
|
+
function turnWithItems(items: Item[], overrides: Partial<Turn> = {}): Turn {
|
|
9
|
+
return makeTurn('t1', 's1', { items, ...overrides });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Non-tool items (reasoning, todo_list, error) are in otherItems β visible when intermediate expanded. */
|
|
13
|
+
function turnWithNonToolItem(item: Item): Turn {
|
|
14
|
+
return turnWithItems([item], { status: 'running' });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Tool items (command_execution, file_change, mcp_tool_call, web_search) go after agent_message β always visible. */
|
|
18
|
+
function turnWithToolItem(item: Item): Turn {
|
|
19
|
+
const agentMsg = makeItem('i-agent', 't1', 'agent_message');
|
|
20
|
+
agentMsg.data = { text: 'Done' };
|
|
21
|
+
return turnWithItems([agentMsg, item], { status: 'completed', agent_response: 'Done' });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('TurnContent', () => {
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
cleanup();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders user_prompt at top', () => {
|
|
30
|
+
const turn = turnWithItems([], { user_prompt: 'Fix the bug' });
|
|
31
|
+
render(<TurnContent turn={turn} />);
|
|
32
|
+
expect(screen.getByText('Fix the bug')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders agent_message item with markdown', () => {
|
|
36
|
+
const item = makeItem('i1', 't1', 'agent_message');
|
|
37
|
+
item.data = { text: 'Hello **world**' };
|
|
38
|
+
const turn = turnWithItems([item], { agent_response: 'Hello **world**' });
|
|
39
|
+
render(<TurnContent turn={turn} />);
|
|
40
|
+
expect(screen.getByText('world').tagName).toBe('STRONG');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders reasoning item with "Reasoning" header', () => {
|
|
44
|
+
const item = makeItem('i1', 't1', 'reasoning');
|
|
45
|
+
item.data = { text: 'Thinking about it' };
|
|
46
|
+
render(<TurnContent turn={turnWithNonToolItem(item)} />);
|
|
47
|
+
expect(screen.getByText('Reasoning')).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders command_execution item with command in pre', () => {
|
|
51
|
+
const item = makeItem('i1', 't1', 'command_execution');
|
|
52
|
+
item.data = { command: '/bin/zsh -lc "echo hello"', aggregated_output: 'hello', exit_code: 0 };
|
|
53
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
54
|
+
expect(screen.getByText('Command')).toBeInTheDocument();
|
|
55
|
+
expect(screen.getByText('echo hello')).toBeInTheDocument();
|
|
56
|
+
expect(screen.getByText('exit 0')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('shows truncation toggle when output exceeds 10KB', () => {
|
|
60
|
+
const item = makeItem('i1', 't1', 'command_execution');
|
|
61
|
+
const longOutput = 'x'.repeat(11_000);
|
|
62
|
+
item.data = { command: 'ls', aggregated_output: longOutput, exit_code: 0 };
|
|
63
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
64
|
+
expect(screen.getByText(/Show full output/)).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('renders file_change item with file list and A/D/M status', () => {
|
|
68
|
+
const item = makeItem('i1', 't1', 'file_change');
|
|
69
|
+
item.data = { changes: [{ path: 'src/new.ts', kind: 'add' }, { path: 'src/old.ts', kind: 'delete' }] };
|
|
70
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
71
|
+
expect(screen.getByText('src/new.ts')).toBeInTheDocument();
|
|
72
|
+
expect(screen.getByText('src/old.ts')).toBeInTheDocument();
|
|
73
|
+
expect(screen.getByText('A')).toBeInTheDocument();
|
|
74
|
+
expect(screen.getByText('D')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('renders mcp_tool_call item with tool name and server', () => {
|
|
78
|
+
const item = makeItem('i1', 't1', 'mcp_tool_call');
|
|
79
|
+
item.data = { tool: 'search', server: 'my-server', arguments: { query: 'test' } };
|
|
80
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
81
|
+
expect(screen.getByText(/search/)).toBeInTheDocument();
|
|
82
|
+
expect(screen.getByText('my-server')).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('renders web_search item with query', () => {
|
|
86
|
+
const item = makeItem('i1', 't1', 'web_search');
|
|
87
|
+
item.data = { query: 'how to test react' };
|
|
88
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
89
|
+
expect(screen.getByText('Web Search')).toBeInTheDocument();
|
|
90
|
+
expect(screen.getByText('how to test react')).toBeInTheDocument();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('renders todo_list item with completed and pending items', () => {
|
|
94
|
+
const item = makeItem('i1', 't1', 'todo_list');
|
|
95
|
+
item.data = { items: [{ text: 'Task 1', completed: true }, { text: 'Task 2', completed: false }] };
|
|
96
|
+
render(<TurnContent turn={turnWithNonToolItem(item)} />);
|
|
97
|
+
expect(screen.getByText(/Task 1/)).toBeInTheDocument();
|
|
98
|
+
expect(screen.getByText(/Task 2/)).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('renders error item with error message', () => {
|
|
102
|
+
const item = makeItem('i1', 't1', 'error');
|
|
103
|
+
item.data = { message: 'Something broke' };
|
|
104
|
+
render(<TurnContent turn={turnWithNonToolItem(item)} />);
|
|
105
|
+
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
106
|
+
expect(screen.getByText('Something broke')).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('auto-expands intermediate steps when turn status is running', () => {
|
|
110
|
+
const reasoning = makeItem('i1', 't1', 'reasoning');
|
|
111
|
+
reasoning.data = { text: 'Thinking' };
|
|
112
|
+
const agentMsg = makeItem('i2', 't1', 'agent_message');
|
|
113
|
+
agentMsg.data = { text: 'Done' };
|
|
114
|
+
// Reasoning is intermediate (before final agent_message)
|
|
115
|
+
const turn = turnWithItems([reasoning, agentMsg], { status: 'running', agent_response: 'Done' });
|
|
116
|
+
render(<TurnContent turn={turn} />);
|
|
117
|
+
// Reasoning is a non-tool item β visible when intermediate expanded
|
|
118
|
+
expect(screen.getByText('Reasoning')).toBeInTheDocument();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('auto-collapses intermediate steps when turn status is completed', () => {
|
|
122
|
+
const reasoning = makeItem('i1', 't1', 'reasoning');
|
|
123
|
+
reasoning.data = { text: 'Thinking' };
|
|
124
|
+
const agentMsg = makeItem('i2', 't1', 'agent_message');
|
|
125
|
+
agentMsg.data = { text: 'Done' };
|
|
126
|
+
const turn = turnWithItems([reasoning, agentMsg], { status: 'completed', agent_response: 'Done' });
|
|
127
|
+
render(<TurnContent turn={turn} />);
|
|
128
|
+
// Collapsed β reasoning header not visible
|
|
129
|
+
expect(screen.queryByText('Reasoning')).not.toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('intermediate steps header click toggles expand', () => {
|
|
133
|
+
const reasoning = makeItem('i1', 't1', 'reasoning');
|
|
134
|
+
reasoning.data = { text: 'Thinking' };
|
|
135
|
+
const agentMsg = makeItem('i2', 't1', 'agent_message');
|
|
136
|
+
agentMsg.data = { text: 'Done' };
|
|
137
|
+
const turn = turnWithItems([reasoning, agentMsg], { status: 'completed', agent_response: 'Done' });
|
|
138
|
+
render(<TurnContent turn={turn} />);
|
|
139
|
+
// Initially collapsed
|
|
140
|
+
expect(screen.queryByText('Reasoning')).not.toBeInTheDocument();
|
|
141
|
+
// Click to expand
|
|
142
|
+
const header = document.querySelector('.tool-calls-header')!;
|
|
143
|
+
fireEvent.click(header);
|
|
144
|
+
expect(screen.getByText('Reasoning')).toBeInTheDocument();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('shows "Waiting to startβ¦" for pending turn with no items', () => {
|
|
148
|
+
const turn = turnWithItems([], { status: 'pending' });
|
|
149
|
+
render(<TurnContent turn={turn} />);
|
|
150
|
+
expect(screen.getByText('Waiting to startβ¦')).toBeInTheDocument();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('shows "Agent is thinkingβ¦" for running turn with no items', () => {
|
|
154
|
+
const turn = turnWithItems([], { status: 'running' });
|
|
155
|
+
render(<TurnContent turn={turn} />);
|
|
156
|
+
expect(screen.getByText('Agent is thinkingβ¦')).toBeInTheDocument();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// --- command_execution edge cases ---
|
|
160
|
+
|
|
161
|
+
it('command_execution in_progress shows "runningβ¦" summary', () => {
|
|
162
|
+
const item = makeItem('i1', 't1', 'command_execution');
|
|
163
|
+
item.status = 'in_progress';
|
|
164
|
+
item.data = { command: 'ls', aggregated_output: 'partial', exit_code: null };
|
|
165
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
166
|
+
expect(screen.getByText('runningβ¦')).toBeInTheDocument();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('command_execution with no output renders no output pre', () => {
|
|
170
|
+
const item = makeItem('i1', 't1', 'command_execution');
|
|
171
|
+
item.data = { command: 'ls', exit_code: 0 };
|
|
172
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
173
|
+
expect(screen.getByText('ls')).toBeInTheDocument();
|
|
174
|
+
expect(document.querySelector('.cmd-output')).not.toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('command_execution truncate toggle click expands and collapses', () => {
|
|
178
|
+
const item = makeItem('i1', 't1', 'command_execution');
|
|
179
|
+
item.data = { command: 'ls', aggregated_output: 'x'.repeat(11_000), exit_code: 0 };
|
|
180
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
181
|
+
const toggle = screen.getByText(/Show full output/);
|
|
182
|
+
fireEvent.click(toggle);
|
|
183
|
+
expect(screen.getByText('Show less')).toBeInTheDocument();
|
|
184
|
+
fireEvent.click(screen.getByText('Show less'));
|
|
185
|
+
expect(screen.getByText(/Show full output/)).toBeInTheDocument();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// --- file_change edge cases ---
|
|
189
|
+
|
|
190
|
+
it('file_change with single file shows singular "file"', () => {
|
|
191
|
+
const item = makeItem('i1', 't1', 'file_change');
|
|
192
|
+
item.data = { changes: [{ path: 'src/one.ts', kind: 'modify' }] };
|
|
193
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
194
|
+
expect(screen.getByText('1 file')).toBeInTheDocument();
|
|
195
|
+
expect(screen.getByText('M')).toBeInTheDocument();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('file_change with modify kind shows M and fc-mod class', () => {
|
|
199
|
+
const item = makeItem('i1', 't1', 'file_change');
|
|
200
|
+
item.data = { changes: [{ path: 'src/mod.ts', kind: 'modify' }] };
|
|
201
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
202
|
+
const statusSpan = document.querySelector('.fc-status')!;
|
|
203
|
+
expect(statusSpan).toHaveClass('fc-mod');
|
|
204
|
+
expect(statusSpan.textContent).toBe('M');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// --- mcp_tool_call edge cases ---
|
|
208
|
+
|
|
209
|
+
it('mcp_tool_call failed status shows "failed" summary', () => {
|
|
210
|
+
const item = makeItem('i1', 't1', 'mcp_tool_call');
|
|
211
|
+
item.status = 'failed';
|
|
212
|
+
item.data = { tool: 'broken', server: 'srv' };
|
|
213
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
214
|
+
expect(screen.getByText('failed')).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('mcp_tool_call with code arg renders in pre block', () => {
|
|
218
|
+
const item = makeItem('i1', 't1', 'mcp_tool_call');
|
|
219
|
+
item.data = { tool: 'exec', arguments: { code: 'console.log(1)' } };
|
|
220
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
221
|
+
expect(screen.getByText('console.log(1)')).toBeInTheDocument();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('mcp_tool_call with non-string arg renders JSON', () => {
|
|
225
|
+
const item = makeItem('i1', 't1', 'mcp_tool_call');
|
|
226
|
+
item.data = { tool: 'exec', arguments: { count: 42 } };
|
|
227
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
228
|
+
expect(screen.getByText('42')).toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('mcp_tool_call with result text renders pre', () => {
|
|
232
|
+
const item = makeItem('i1', 't1', 'mcp_tool_call');
|
|
233
|
+
item.data = {
|
|
234
|
+
tool: 'query',
|
|
235
|
+
result: { content: [{ type: 'text', text: 'result here' }] },
|
|
236
|
+
};
|
|
237
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
238
|
+
expect(screen.getByText('result here')).toBeInTheDocument();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('mcp_tool_call with error renders error in red pre', () => {
|
|
242
|
+
const item = makeItem('i1', 't1', 'mcp_tool_call');
|
|
243
|
+
item.data = { tool: 'fail', error: 'MCP failed' };
|
|
244
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
245
|
+
expect(screen.getByText('MCP failed')).toBeInTheDocument();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('mcp_tool_call with no args and no server renders minimal', () => {
|
|
249
|
+
const item = makeItem('i1', 't1', 'mcp_tool_call');
|
|
250
|
+
item.data = { tool: 'noop' };
|
|
251
|
+
render(<TurnContent turn={turnWithToolItem(item)} />);
|
|
252
|
+
expect(screen.getByText(/noop/)).toBeInTheDocument();
|
|
253
|
+
expect(document.querySelector('.mcp-args')).not.toBeInTheDocument();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// --- turn-level edge cases ---
|
|
257
|
+
|
|
258
|
+
it('renders turn.error in error block', () => {
|
|
259
|
+
const turn = turnWithItems([], { status: 'failed', error: 'Turn failed' });
|
|
260
|
+
render(<TurnContent turn={turn} />);
|
|
261
|
+
expect(screen.getByText('Turn failed')).toBeInTheDocument();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('renders items after final agent_message', () => {
|
|
265
|
+
const agentMsg = makeItem('i1', 't1', 'agent_message');
|
|
266
|
+
agentMsg.data = { text: 'Final answer' };
|
|
267
|
+
const errorItem = makeItem('i2', 't1', 'error');
|
|
268
|
+
errorItem.data = { message: 'Post-error' };
|
|
269
|
+
const turn = turnWithItems([agentMsg, errorItem], { status: 'completed', agent_response: 'Final answer' });
|
|
270
|
+
render(<TurnContent turn={turn} />);
|
|
271
|
+
expect(screen.getByText('Final answer')).toBeInTheDocument();
|
|
272
|
+
expect(screen.getByText('Post-error')).toBeInTheDocument();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('unknown item type renders null (default case)', () => {
|
|
276
|
+
const item = makeItem('i1', 't1', 'unknown_type' as never);
|
|
277
|
+
item.data = {};
|
|
278
|
+
const agentMsg = makeItem('i2', 't1', 'agent_message');
|
|
279
|
+
agentMsg.data = { text: 'Done' };
|
|
280
|
+
const turn = turnWithItems([item, agentMsg], { status: 'completed', agent_response: 'Done' });
|
|
281
|
+
render(<TurnContent turn={turn} />);
|
|
282
|
+
// Agent message renders, unknown item produces no content card
|
|
283
|
+
expect(screen.getByText('Done')).toBeInTheDocument();
|
|
284
|
+
// The unknown item is intermediate β no card for it
|
|
285
|
+
expect(document.querySelectorAll('.render-content').length).toBeLessThan(3);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -18,9 +18,19 @@ function itemText(item: Item): string {
|
|
|
18
18
|
return '';
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Strip shell wrapper from command for display (e.g. /bin/zsh -lc "actual cmd"). */
|
|
22
|
+
function cleanCommand(cmd: string): string {
|
|
23
|
+
const match = cmd.match(/^\/bin\/\w+\s+-\w+\s+"(.*)"$/s);
|
|
24
|
+
return match ? match[1] : cmd;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Maximum output length before truncation (10KB). */
|
|
28
|
+
const MAX_OUTPUT = 10_000;
|
|
29
|
+
|
|
21
30
|
/** Render a single item as a turn card. */
|
|
22
31
|
function ItemCard({ item }: { item: Item }) {
|
|
23
32
|
const isRunning = item.status === 'in_progress';
|
|
33
|
+
const [showFullOutput, setShowFullOutput] = useState(false);
|
|
24
34
|
|
|
25
35
|
switch (item.type) {
|
|
26
36
|
case 'agent_message': {
|
|
@@ -40,7 +50,9 @@ function ItemCard({ item }: { item: Item }) {
|
|
|
40
50
|
return (
|
|
41
51
|
<div className="render-content">
|
|
42
52
|
<div className="turn-header">
|
|
43
|
-
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}
|
|
53
|
+
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}>
|
|
54
|
+
π
|
|
55
|
+
</span>
|
|
44
56
|
<span className="turn-header-title">Reasoning</span>
|
|
45
57
|
</div>
|
|
46
58
|
<div dangerouslySetInnerHTML={{ __html: renderMarkdown(itemText(item)) }} />
|
|
@@ -50,19 +62,37 @@ function ItemCard({ item }: { item: Item }) {
|
|
|
50
62
|
|
|
51
63
|
case 'command_execution': {
|
|
52
64
|
const data = item.data || {};
|
|
53
|
-
const command = (data.command as string) || '';
|
|
65
|
+
const command = cleanCommand((data.command as string) || '');
|
|
54
66
|
const output = (data.aggregated_output as string) || '';
|
|
55
67
|
const exitCode = data.exit_code as number | null;
|
|
56
|
-
const summary = isRunning ? 'runningβ¦' :
|
|
68
|
+
const summary = isRunning ? 'runningβ¦' : exitCode !== null ? `exit ${exitCode}` : '';
|
|
69
|
+
const isTruncated = output.length > MAX_OUTPUT;
|
|
70
|
+
const displayOutput =
|
|
71
|
+
isTruncated && !showFullOutput ? output.slice(0, MAX_OUTPUT) + '\n⦠(truncated, click to expand)' : output;
|
|
57
72
|
return (
|
|
58
73
|
<div className="render-content">
|
|
59
74
|
<div className="turn-header">
|
|
60
|
-
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}
|
|
75
|
+
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}>
|
|
76
|
+
π§
|
|
77
|
+
</span>
|
|
61
78
|
<span className="turn-header-title">Command</span>
|
|
62
79
|
{summary && <span className="turn-header-meta">{summary}</span>}
|
|
63
80
|
</div>
|
|
64
|
-
<pre className="cmd-block"
|
|
65
|
-
|
|
81
|
+
<pre className="cmd-block">
|
|
82
|
+
<code>{command}</code>
|
|
83
|
+
</pre>
|
|
84
|
+
{output && (
|
|
85
|
+
<>
|
|
86
|
+
<pre className="cmd-output">
|
|
87
|
+
<code>{displayOutput}</code>
|
|
88
|
+
</pre>
|
|
89
|
+
{isTruncated && (
|
|
90
|
+
<button className="cmd-output-toggle" onClick={() => setShowFullOutput((v) => !v)}>
|
|
91
|
+
{showFullOutput ? 'Show less' : `Show full output (${(output.length / 1024).toFixed(0)}KB)`}
|
|
92
|
+
</button>
|
|
93
|
+
)}
|
|
94
|
+
</>
|
|
95
|
+
)}
|
|
66
96
|
</div>
|
|
67
97
|
);
|
|
68
98
|
}
|
|
@@ -73,7 +103,9 @@ function ItemCard({ item }: { item: Item }) {
|
|
|
73
103
|
return (
|
|
74
104
|
<div className="render-content">
|
|
75
105
|
<div className="turn-header">
|
|
76
|
-
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}
|
|
106
|
+
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}>
|
|
107
|
+
π
|
|
108
|
+
</span>
|
|
77
109
|
<span className="turn-header-title">File Changes</span>
|
|
78
110
|
<span className="turn-header-meta">{summary}</span>
|
|
79
111
|
</div>
|
|
@@ -97,13 +129,58 @@ function ItemCard({ item }: { item: Item }) {
|
|
|
97
129
|
const data = item.data || {};
|
|
98
130
|
const tool = (data.tool as string) || 'mcp';
|
|
99
131
|
const server = (data.server as string) || '';
|
|
132
|
+
const args = data.arguments as Record<string, unknown> | null;
|
|
133
|
+
const result = data.result as {
|
|
134
|
+
content?: Array<{ type: string; text: string }>;
|
|
135
|
+
structured_content?: Record<string, unknown>;
|
|
136
|
+
} | null;
|
|
137
|
+
const error = data.error as string | null;
|
|
138
|
+
const isFailed = item.status === 'failed' || !!error;
|
|
139
|
+
const summary = isRunning ? 'runningβ¦' : isFailed ? 'failed' : 'done';
|
|
140
|
+
const resultTexts = result?.content?.filter((c) => c.type === 'text').map((c) => c.text) ?? [];
|
|
100
141
|
return (
|
|
101
142
|
<div className="render-content">
|
|
102
143
|
<div className="turn-header">
|
|
103
|
-
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}
|
|
144
|
+
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}>
|
|
145
|
+
π
|
|
146
|
+
</span>
|
|
104
147
|
<span className="turn-header-title">MCP: {tool}</span>
|
|
105
148
|
{server && <span className="turn-header-meta">{server}</span>}
|
|
149
|
+
{summary && (
|
|
150
|
+
<span className="turn-header-meta" style={isFailed ? { color: 'var(--red)' } : undefined}>
|
|
151
|
+
{summary}
|
|
152
|
+
</span>
|
|
153
|
+
)}
|
|
106
154
|
</div>
|
|
155
|
+
{args && Object.keys(args).length > 0 && (
|
|
156
|
+
<div className="mcp-args">
|
|
157
|
+
{Object.entries(args).map(([key, value]) => (
|
|
158
|
+
<div key={key} className="mcp-arg">
|
|
159
|
+
<span className="mcp-arg-key">{key}</span>
|
|
160
|
+
{key === 'code' ? (
|
|
161
|
+
<pre className="cmd-block">
|
|
162
|
+
<code>{String(value)}</code>
|
|
163
|
+
</pre>
|
|
164
|
+
) : (
|
|
165
|
+
<span className="mcp-arg-value">
|
|
166
|
+
{typeof value === 'string' ? value : JSON.stringify(value, null, 2)}
|
|
167
|
+
</span>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
{resultTexts.length > 0 &&
|
|
174
|
+
resultTexts.map((text, i) => (
|
|
175
|
+
<pre key={i} className="cmd-output">
|
|
176
|
+
<code>{text}</code>
|
|
177
|
+
</pre>
|
|
178
|
+
))}
|
|
179
|
+
{error && (
|
|
180
|
+
<pre className="cmd-output" style={{ color: 'var(--red)' }}>
|
|
181
|
+
<code>{error}</code>
|
|
182
|
+
</pre>
|
|
183
|
+
)}
|
|
107
184
|
</div>
|
|
108
185
|
);
|
|
109
186
|
}
|
|
@@ -113,7 +190,9 @@ function ItemCard({ item }: { item: Item }) {
|
|
|
113
190
|
return (
|
|
114
191
|
<div className="render-content">
|
|
115
192
|
<div className="turn-header">
|
|
116
|
-
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}
|
|
193
|
+
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}>
|
|
194
|
+
π
|
|
195
|
+
</span>
|
|
117
196
|
<span className="turn-header-title">Web Search</span>
|
|
118
197
|
</div>
|
|
119
198
|
<p className="user-message">{query}</p>
|
|
@@ -126,7 +205,9 @@ function ItemCard({ item }: { item: Item }) {
|
|
|
126
205
|
return (
|
|
127
206
|
<div className="render-content">
|
|
128
207
|
<div className="turn-header">
|
|
129
|
-
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}
|
|
208
|
+
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}>
|
|
209
|
+
π
|
|
210
|
+
</span>
|
|
130
211
|
<span className="turn-header-title">Todo List</span>
|
|
131
212
|
</div>
|
|
132
213
|
<ul className="todo-list">
|
|
@@ -145,10 +226,14 @@ function ItemCard({ item }: { item: Item }) {
|
|
|
145
226
|
return (
|
|
146
227
|
<div className="render-content">
|
|
147
228
|
<div className="turn-header">
|
|
148
|
-
<span className="turn-header-icon" style={{ background: 'var(--red)', color: '#fff' }}
|
|
229
|
+
<span className="turn-header-icon" style={{ background: 'var(--red)', color: '#fff' }}>
|
|
230
|
+
!
|
|
231
|
+
</span>
|
|
149
232
|
<span className="turn-header-title">Error</span>
|
|
150
233
|
</div>
|
|
151
|
-
<pre className="cmd-output" style={{ color: 'var(--red)' }}
|
|
234
|
+
<pre className="cmd-output" style={{ color: 'var(--red)' }}>
|
|
235
|
+
<code>{msg}</code>
|
|
236
|
+
</pre>
|
|
152
237
|
</div>
|
|
153
238
|
);
|
|
154
239
|
}
|
|
@@ -188,9 +273,7 @@ function TurnContentBase({ turn }: Props) {
|
|
|
188
273
|
|
|
189
274
|
const hasFinalResponse = lastAgentIdx >= 0;
|
|
190
275
|
// Intermediate = everything before final agent_message (or ALL items if running)
|
|
191
|
-
const intermediateItems = hasFinalResponse
|
|
192
|
-
? turn.items.slice(0, lastAgentIdx)
|
|
193
|
-
: turn.items;
|
|
276
|
+
const intermediateItems = hasFinalResponse ? turn.items.slice(0, lastAgentIdx) : turn.items;
|
|
194
277
|
|
|
195
278
|
// Group intermediate items into segments by agent_message boundaries
|
|
196
279
|
// Each segment: one agent_message + following tool/non-tool items until next agent_message
|
|
@@ -220,7 +303,9 @@ function TurnContentBase({ turn }: Props) {
|
|
|
220
303
|
{/* User prompt β always visible at top */}
|
|
221
304
|
<div className="render-content">
|
|
222
305
|
<div className="turn-header">
|
|
223
|
-
<span className="turn-header-icon" style={{ background: 'var(--amber)', color: '#fff' }}>
|
|
306
|
+
<span className="turn-header-icon" style={{ background: 'var(--amber)', color: '#fff' }}>
|
|
307
|
+
U
|
|
308
|
+
</span>
|
|
224
309
|
<span className="turn-header-title">You</span>
|
|
225
310
|
</div>
|
|
226
311
|
<p className="user-message">{turn.user_prompt}</p>
|
|
@@ -262,14 +347,10 @@ function TurnContentBase({ turn }: Props) {
|
|
|
262
347
|
)}
|
|
263
348
|
|
|
264
349
|
{/* Final agent message β always visible */}
|
|
265
|
-
{hasFinalResponse &&
|
|
266
|
-
<MemoItemCard key={turn.items[lastAgentIdx].id} item={turn.items[lastAgentIdx]} />
|
|
267
|
-
)}
|
|
350
|
+
{hasFinalResponse && <MemoItemCard key={turn.items[lastAgentIdx].id} item={turn.items[lastAgentIdx]} />}
|
|
268
351
|
|
|
269
352
|
{/* Items after final agent message (rare β errors, etc.) */}
|
|
270
|
-
{hasFinalResponse && turn.items.slice(lastAgentIdx + 1).map((item) =>
|
|
271
|
-
<MemoItemCard key={item.id} item={item} />
|
|
272
|
-
))}
|
|
353
|
+
{hasFinalResponse && turn.items.slice(lastAgentIdx + 1).map((item) => <MemoItemCard key={item.id} item={item} />)}
|
|
273
354
|
|
|
274
355
|
{/* Pending state (no items at all) */}
|
|
275
356
|
{turn.items.length === 0 && !turn.error && (turn.status === 'pending' || turn.status === 'running') && (
|
|
@@ -283,7 +364,9 @@ function TurnContentBase({ turn }: Props) {
|
|
|
283
364
|
{/* Turn error (if any) */}
|
|
284
365
|
{turn.error && (
|
|
285
366
|
<div className="render-content">
|
|
286
|
-
<pre className="cmd-output" style={{ color: 'var(--red)' }}
|
|
367
|
+
<pre className="cmd-output" style={{ color: 'var(--red)' }}>
|
|
368
|
+
<code>{turn.error}</code>
|
|
369
|
+
</pre>
|
|
287
370
|
</div>
|
|
288
371
|
)}
|
|
289
372
|
</div>
|
|
@@ -291,4 +374,4 @@ function TurnContentBase({ turn }: Props) {
|
|
|
291
374
|
}
|
|
292
375
|
|
|
293
376
|
const TurnContent = memo(TurnContentBase);
|
|
294
|
-
export default TurnContent;
|
|
377
|
+
export default TurnContent;
|