repoburg 1.3.202 → 1.3.204
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/backend/dist/index.js +2092 -32001
- package/agent-pane/backend/dist/server.js +45304 -0
- package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
- package/daemon/dist/agentPane.js +128 -0
- package/daemon/dist/agentPane.spec.js +183 -0
- package/daemon/dist/index.js +28 -8
- package/daemon/package.json +1 -1
- package/package.json +15 -2
- package/agent-pane/.prettierignore +0 -5
- package/agent-pane/.prettierrc +0 -8
- package/agent-pane/backend/coverage/base.css +0 -224
- package/agent-pane/backend/coverage/block-navigation.js +0 -87
- package/agent-pane/backend/coverage/clover.xml +0 -887
- package/agent-pane/backend/coverage/coverage-final.json +0 -10
- package/agent-pane/backend/coverage/favicon.png +0 -0
- package/agent-pane/backend/coverage/index.html +0 -176
- package/agent-pane/backend/coverage/prettify.css +0 -1
- package/agent-pane/backend/coverage/prettify.js +0 -2
- package/agent-pane/backend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/backend/coverage/sorter.js +0 -210
- package/agent-pane/backend/coverage/src/codex/codex-provider.ts.html +0 -1402
- package/agent-pane/backend/coverage/src/codex/index.html +0 -131
- package/agent-pane/backend/coverage/src/codex/item-mapper.ts.html +0 -331
- package/agent-pane/backend/coverage/src/db/index.html +0 -131
- package/agent-pane/backend/coverage/src/db/index.ts.html +0 -436
- package/agent-pane/backend/coverage/src/db/schema.ts.html +0 -175
- package/agent-pane/backend/coverage/src/index.html +0 -116
- package/agent-pane/backend/coverage/src/index.ts.html +0 -262
- package/agent-pane/backend/coverage/src/routes/index.html +0 -146
- package/agent-pane/backend/coverage/src/routes/sessions.ts.html +0 -991
- package/agent-pane/backend/coverage/src/routes/turns.ts.html +0 -256
- package/agent-pane/backend/coverage/src/routes/workspaces.ts.html +0 -406
- package/agent-pane/backend/coverage/src/ws/index.html +0 -116
- package/agent-pane/backend/coverage/src/ws/index.ts.html +0 -238
- 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/package-lock.json +0 -5945
- package/agent-pane/backend/package.json +0 -33
- package/agent-pane/backend/scripts/backfill-from-jsonl.ts +0 -206
- package/agent-pane/backend/scripts/import-session-from-jsonl.ts +0 -264
- package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +0 -271
- package/agent-pane/backend/src/claude/claude-message-mapper.ts +0 -259
- package/agent-pane/backend/src/claude/claude-provider.test.ts +0 -455
- package/agent-pane/backend/src/claude/claude-provider.ts +0 -1319
- package/agent-pane/backend/src/claude/claude-types.ts +0 -45
- package/agent-pane/backend/src/codex/app-server-client.test.ts +0 -183
- package/agent-pane/backend/src/codex/app-server-client.ts +0 -247
- package/agent-pane/backend/src/codex/app-server-types.ts +0 -259
- package/agent-pane/backend/src/codex/codex-provider-runtime-config.test.ts +0 -56
- package/agent-pane/backend/src/codex/codex-provider.test.ts +0 -60
- package/agent-pane/backend/src/codex/codex-provider.ts +0 -619
- package/agent-pane/backend/src/codex/item-mapper.test.ts +0 -221
- package/agent-pane/backend/src/codex/item-mapper.ts +0 -86
- package/agent-pane/backend/src/codex/shared-app-server.ts +0 -25
- package/agent-pane/backend/src/db/index.test.ts +0 -125
- package/agent-pane/backend/src/db/index.ts +0 -236
- package/agent-pane/backend/src/db/schema.ts +0 -34
- package/agent-pane/backend/src/index.ts +0 -89
- package/agent-pane/backend/src/integration/__fixtures__/cancel-turn.json +0 -29
- package/agent-pane/backend/src/integration/__fixtures__/delta-streaming.json +0 -40
- package/agent-pane/backend/src/integration/__fixtures__/failed-turn.json +0 -16
- package/agent-pane/backend/src/integration/__fixtures__/mcp-tool-call.json +0 -60
- package/agent-pane/backend/src/integration/__fixtures__/missing-item-started.json +0 -34
- package/agent-pane/backend/src/integration/__fixtures__/multi-item-turn.json +0 -85
- package/agent-pane/backend/src/integration/__fixtures__/multi-turn.json +0 -48
- package/agent-pane/backend/src/integration/__fixtures__/simple-turn.json +0 -35
- package/agent-pane/backend/src/integration/delta-streaming.test.ts +0 -130
- package/agent-pane/backend/src/integration/helpers/app-server-mock.ts +0 -272
- package/agent-pane/backend/src/integration/helpers/test-app.ts +0 -110
- package/agent-pane/backend/src/integration/helpers/ws-capture.ts +0 -64
- package/agent-pane/backend/src/integration/item-lifecycle.test.ts +0 -133
- package/agent-pane/backend/src/integration/mcp-tool-call.test.ts +0 -102
- package/agent-pane/backend/src/integration/missing-item-started.test.ts +0 -91
- package/agent-pane/backend/src/integration/multi-turn.test.ts +0 -121
- package/agent-pane/backend/src/integration/session-operations.test.ts +0 -175
- package/agent-pane/backend/src/integration/threads-claude-fork.test.ts +0 -241
- package/agent-pane/backend/src/integration/threads.test.ts +0 -309
- package/agent-pane/backend/src/integration/title-generation.test.ts +0 -109
- package/agent-pane/backend/src/integration/turn-cancel.test.ts +0 -111
- package/agent-pane/backend/src/integration/turn-failure.test.ts +0 -97
- package/agent-pane/backend/src/integration/turn-lifecycle.test.ts +0 -141
- package/agent-pane/backend/src/providers/index.ts +0 -16
- package/agent-pane/backend/src/providers/registry.test.ts +0 -92
- package/agent-pane/backend/src/providers/registry.ts +0 -43
- package/agent-pane/backend/src/providers/runtime-provider.ts +0 -49
- package/agent-pane/backend/src/repoburg/action-mapping.test.ts +0 -122
- package/agent-pane/backend/src/repoburg/action-mapping.ts +0 -294
- package/agent-pane/backend/src/repoburg/agent-runtime-client.test.ts +0 -271
- package/agent-pane/backend/src/repoburg/agent-runtime-client.ts +0 -285
- package/agent-pane/backend/src/repoburg/backend-resolver.test.ts +0 -356
- package/agent-pane/backend/src/repoburg/backend-resolver.ts +0 -261
- package/agent-pane/backend/src/repoburg/repoburg-client.test.ts +0 -180
- package/agent-pane/backend/src/repoburg/repoburg-client.ts +0 -194
- package/agent-pane/backend/src/repoburg/repoburg-provider.test.ts +0 -780
- package/agent-pane/backend/src/repoburg/repoburg-provider.ts +0 -471
- package/agent-pane/backend/src/repoburg/turn-projector.test.ts +0 -621
- package/agent-pane/backend/src/repoburg/turn-projector.ts +0 -637
- package/agent-pane/backend/src/routes/files.ts +0 -31
- package/agent-pane/backend/src/routes/mcp.ts +0 -33
- package/agent-pane/backend/src/routes/models.test.ts +0 -92
- package/agent-pane/backend/src/routes/models.ts +0 -16
- package/agent-pane/backend/src/routes/plugins.ts +0 -40
- package/agent-pane/backend/src/routes/providers.test.ts +0 -86
- package/agent-pane/backend/src/routes/providers.ts +0 -86
- package/agent-pane/backend/src/routes/sessions.test.ts +0 -772
- package/agent-pane/backend/src/routes/sessions.ts +0 -565
- package/agent-pane/backend/src/routes/skills.test.ts +0 -114
- package/agent-pane/backend/src/routes/skills.ts +0 -70
- package/agent-pane/backend/src/routes/threads.ts +0 -323
- package/agent-pane/backend/src/routes/turns.test.ts +0 -196
- package/agent-pane/backend/src/routes/turns.ts +0 -103
- package/agent-pane/backend/src/routes/workspaces.test.ts +0 -197
- package/agent-pane/backend/src/routes/workspaces.ts +0 -128
- package/agent-pane/backend/src/ws/index.test.ts +0 -107
- package/agent-pane/backend/src/ws/index.ts +0 -51
- package/agent-pane/backend/tsconfig.json +0 -25
- package/agent-pane/backend/vitest.config.ts +0 -20
- package/agent-pane/eslint.config.js +0 -33
- package/agent-pane/frontend/coverage/base.css +0 -224
- package/agent-pane/frontend/coverage/block-navigation.js +0 -87
- package/agent-pane/frontend/coverage/clover.xml +0 -2962
- package/agent-pane/frontend/coverage/coverage-final.json +0 -37
- package/agent-pane/frontend/coverage/favicon.png +0 -0
- package/agent-pane/frontend/coverage/index.html +0 -251
- package/agent-pane/frontend/coverage/prettify.css +0 -1
- package/agent-pane/frontend/coverage/prettify.js +0 -2
- package/agent-pane/frontend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/frontend/coverage/sorter.js +0 -210
- package/agent-pane/frontend/coverage/src/App.tsx.html +0 -217
- package/agent-pane/frontend/coverage/src/AppInitializer.tsx.html +0 -223
- package/agent-pane/frontend/coverage/src/api/client.ts.html +0 -187
- package/agent-pane/frontend/coverage/src/api/index.html +0 -161
- package/agent-pane/frontend/coverage/src/api/sessions.ts.html +0 -316
- package/agent-pane/frontend/coverage/src/api/websocket.ts.html +0 -544
- package/agent-pane/frontend/coverage/src/api/workspaces.ts.html +0 -199
- package/agent-pane/frontend/coverage/src/components/CollapsibleToolCalls.tsx.html +0 -226
- package/agent-pane/frontend/coverage/src/components/CommandInputBar.tsx.html +0 -382
- package/agent-pane/frontend/coverage/src/components/CommandPalette.tsx.html +0 -715
- package/agent-pane/frontend/coverage/src/components/IdeWorkspace.tsx.html +0 -160
- package/agent-pane/frontend/coverage/src/components/ModelSelector.tsx.html +0 -478
- package/agent-pane/frontend/coverage/src/components/NewSessionDialog.tsx.html +0 -472
- package/agent-pane/frontend/coverage/src/components/RenderArea.tsx.html +0 -574
- package/agent-pane/frontend/coverage/src/components/RightPanel.tsx.html +0 -205
- package/agent-pane/frontend/coverage/src/components/RuntimeIcon.tsx.html +0 -133
- package/agent-pane/frontend/coverage/src/components/SessionTabs.tsx.html +0 -217
- package/agent-pane/frontend/coverage/src/components/StatusBar.tsx.html +0 -271
- package/agent-pane/frontend/coverage/src/components/StatusDot.tsx.html +0 -109
- package/agent-pane/frontend/coverage/src/components/Toast.tsx.html +0 -313
- package/agent-pane/frontend/coverage/src/components/TurnContent.tsx.html +0 -1216
- package/agent-pane/frontend/coverage/src/components/WorkspaceSidebar.tsx.html +0 -1138
- package/agent-pane/frontend/coverage/src/components/index.html +0 -326
- package/agent-pane/frontend/coverage/src/components/panels/ChangesPanel.tsx.html +0 -247
- package/agent-pane/frontend/coverage/src/components/panels/LogsPanel.tsx.html +0 -160
- package/agent-pane/frontend/coverage/src/components/panels/MetricsPanel.tsx.html +0 -427
- package/agent-pane/frontend/coverage/src/components/panels/TurnsPanel.tsx.html +0 -397
- package/agent-pane/frontend/coverage/src/components/panels/index.html +0 -161
- package/agent-pane/frontend/coverage/src/constants/index.html +0 -116
- package/agent-pane/frontend/coverage/src/constants/models.ts.html +0 -406
- package/agent-pane/frontend/coverage/src/hooks/index.html +0 -146
- package/agent-pane/frontend/coverage/src/hooks/useEscapeKey.ts.html +0 -124
- package/agent-pane/frontend/coverage/src/hooks/useOutsideClick.ts.html +0 -142
- package/agent-pane/frontend/coverage/src/hooks/useSessionStatus.ts.html +0 -187
- package/agent-pane/frontend/coverage/src/index.html +0 -146
- package/agent-pane/frontend/coverage/src/main.tsx.html +0 -115
- package/agent-pane/frontend/coverage/src/store/index.html +0 -116
- package/agent-pane/frontend/coverage/src/store/store.ts.html +0 -2404
- package/agent-pane/frontend/coverage/src/test/component-helpers.ts.html +0 -190
- package/agent-pane/frontend/coverage/src/test/fixtures.ts.html +0 -367
- package/agent-pane/frontend/coverage/src/test/index.html +0 -131
- package/agent-pane/frontend/coverage/src/types/index.html +0 -116
- package/agent-pane/frontend/coverage/src/types/index.ts.html +0 -466
- package/agent-pane/frontend/coverage/src/utils/changes.ts.html +0 -202
- package/agent-pane/frontend/coverage/src/utils/duration.ts.html +0 -127
- package/agent-pane/frontend/coverage/src/utils/index.html +0 -131
- package/agent-pane/frontend/dist/assets/index-CJxD4YmJ.js +0 -370
- package/agent-pane/frontend/dist/assets/index-r-xwfOKQ.css +0 -1
- package/agent-pane/frontend/dist/index.html +0 -19
- package/agent-pane/frontend/index.html +0 -18
- package/agent-pane/frontend/package-lock.json +0 -4211
- package/agent-pane/frontend/package.json +0 -35
- package/agent-pane/frontend/src/App.test.tsx +0 -74
- package/agent-pane/frontend/src/App.tsx +0 -44
- package/agent-pane/frontend/src/AppInitializer.test.tsx +0 -142
- package/agent-pane/frontend/src/AppInitializer.tsx +0 -46
- package/agent-pane/frontend/src/__tests__/ThreadsPanel.test.tsx +0 -192
- package/agent-pane/frontend/src/api/client.test.ts +0 -126
- package/agent-pane/frontend/src/api/client.ts +0 -34
- package/agent-pane/frontend/src/api/models.ts +0 -7
- package/agent-pane/frontend/src/api/providers.ts +0 -17
- package/agent-pane/frontend/src/api/sessions.test.ts +0 -246
- package/agent-pane/frontend/src/api/sessions.ts +0 -97
- package/agent-pane/frontend/src/api/skills.ts +0 -12
- package/agent-pane/frontend/src/api/websocket.test.ts +0 -288
- package/agent-pane/frontend/src/api/websocket.ts +0 -240
- package/agent-pane/frontend/src/api/workspaces.test.ts +0 -112
- package/agent-pane/frontend/src/api/workspaces.ts +0 -38
- package/agent-pane/frontend/src/components/AskUserItem.test.tsx +0 -143
- package/agent-pane/frontend/src/components/AskUserItem.tsx +0 -207
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +0 -49
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +0 -56
- package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +0 -133
- package/agent-pane/frontend/src/components/CommandInputBar.tsx +0 -584
- package/agent-pane/frontend/src/components/CommandPalette.test.tsx +0 -226
- package/agent-pane/frontend/src/components/CommandPalette.tsx +0 -194
- package/agent-pane/frontend/src/components/CommentableText.test.tsx +0 -196
- package/agent-pane/frontend/src/components/CommentableText.tsx +0 -379
- package/agent-pane/frontend/src/components/IdeWorkspace.test.tsx +0 -39
- package/agent-pane/frontend/src/components/IdeWorkspace.tsx +0 -51
- package/agent-pane/frontend/src/components/ModelSelector.test.tsx +0 -129
- package/agent-pane/frontend/src/components/ModelSelector.tsx +0 -191
- package/agent-pane/frontend/src/components/NewSessionDialog.test.tsx +0 -95
- package/agent-pane/frontend/src/components/NewSessionDialog.tsx +0 -129
- package/agent-pane/frontend/src/components/RenderArea.test.tsx +0 -160
- package/agent-pane/frontend/src/components/RenderArea.tsx +0 -170
- package/agent-pane/frontend/src/components/ResizeHandle.tsx +0 -29
- package/agent-pane/frontend/src/components/RightPanel.test.tsx +0 -43
- package/agent-pane/frontend/src/components/RightPanel.tsx +0 -46
- package/agent-pane/frontend/src/components/RuntimeIcon.test.tsx +0 -32
- package/agent-pane/frontend/src/components/RuntimeIcon.tsx +0 -16
- package/agent-pane/frontend/src/components/SessionTabs.test.tsx +0 -72
- package/agent-pane/frontend/src/components/SessionTabs.tsx +0 -49
- package/agent-pane/frontend/src/components/StatusBar.test.tsx +0 -56
- package/agent-pane/frontend/src/components/StatusBar.tsx +0 -129
- package/agent-pane/frontend/src/components/StatusDot.test.tsx +0 -25
- package/agent-pane/frontend/src/components/StatusDot.tsx +0 -8
- package/agent-pane/frontend/src/components/Toast.test.tsx +0 -79
- package/agent-pane/frontend/src/components/Toast.tsx +0 -76
- package/agent-pane/frontend/src/components/TurnContent.test.tsx +0 -377
- package/agent-pane/frontend/src/components/TurnContent.tsx +0 -594
- package/agent-pane/frontend/src/components/WorkspaceSidebar.test.tsx +0 -297
- package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +0 -351
- package/agent-pane/frontend/src/components/input/OptionSubPopup.tsx +0 -32
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.test.tsx +0 -63
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.tsx +0 -78
- package/agent-pane/frontend/src/components/input/SlashPopup.test.tsx +0 -96
- package/agent-pane/frontend/src/components/input/SlashPopup.tsx +0 -82
- package/agent-pane/frontend/src/components/panels/ChangesPanel.test.tsx +0 -61
- package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +0 -54
- package/agent-pane/frontend/src/components/panels/TasksPanel.test.tsx +0 -97
- package/agent-pane/frontend/src/components/panels/TasksPanel.tsx +0 -208
- package/agent-pane/frontend/src/components/panels/ThreadsPanel.tsx +0 -382
- package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +0 -131
- package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +0 -131
- package/agent-pane/frontend/src/constants/models.test.ts +0 -97
- package/agent-pane/frontend/src/constants/models.ts +0 -166
- package/agent-pane/frontend/src/hooks/useEscapeKey.test.ts +0 -42
- package/agent-pane/frontend/src/hooks/useEscapeKey.ts +0 -13
- package/agent-pane/frontend/src/hooks/useOutsideClick.test.ts +0 -92
- package/agent-pane/frontend/src/hooks/useOutsideClick.ts +0 -19
- package/agent-pane/frontend/src/hooks/useProvider.ts +0 -44
- package/agent-pane/frontend/src/hooks/useResizable.test.ts +0 -97
- package/agent-pane/frontend/src/hooks/useResizable.ts +0 -75
- package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +0 -116
- package/agent-pane/frontend/src/hooks/useSessionStatus.ts +0 -34
- package/agent-pane/frontend/src/main.test.tsx +0 -37
- package/agent-pane/frontend/src/main.tsx +0 -10
- package/agent-pane/frontend/src/store/sessionSlice.ts +0 -440
- package/agent-pane/frontend/src/store/store.test.ts +0 -1338
- package/agent-pane/frontend/src/store/store.ts +0 -37
- package/agent-pane/frontend/src/store/threadSlice.ts +0 -136
- package/agent-pane/frontend/src/store/uiSlice.ts +0 -133
- package/agent-pane/frontend/src/store/workspaceSlice.ts +0 -77
- package/agent-pane/frontend/src/store/wsSlice.ts +0 -368
- package/agent-pane/frontend/src/styles/app.css +0 -627
- package/agent-pane/frontend/src/styles/tokens.css +0 -62
- package/agent-pane/frontend/src/test/component-helpers.ts +0 -38
- package/agent-pane/frontend/src/test/fixtures.ts +0 -101
- package/agent-pane/frontend/src/test-setup.ts +0 -5
- package/agent-pane/frontend/src/types/index.test.ts +0 -128
- package/agent-pane/frontend/src/types/index.ts +0 -134
- package/agent-pane/frontend/src/utils/changes.test.ts +0 -81
- package/agent-pane/frontend/src/utils/changes.ts +0 -39
- package/agent-pane/frontend/src/utils/duration.test.ts +0 -76
- package/agent-pane/frontend/src/utils/duration.ts +0 -14
- package/agent-pane/frontend/tsconfig.json +0 -25
- package/agent-pane/frontend/vite.config.ts +0 -22
- package/agent-pane/frontend/vitest.config.ts +0 -29
- package/agent-pane/package-lock.json +0 -1527
- package/agent-pane/package.json +0 -19
- package/agent-pane/shared/provider-types.ts +0 -67
- package/agent-pane/shared/types.ts +0 -184
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react';
|
|
3
|
-
|
|
4
|
-
// Mock the skills API
|
|
5
|
-
vi.mock('../../api/skills.ts', () => ({
|
|
6
|
-
fetchSkills: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
import SkillMentionPopup from './SkillMentionPopup.tsx';
|
|
10
|
-
import { fetchSkills } from '../../api/skills.ts';
|
|
11
|
-
|
|
12
|
-
const MOCK_SKILLS = [
|
|
13
|
-
{ id: 'cloudflare', name: 'cloudflare', description: 'Cloudflare skill', type: 'skill' as const, enabled: true, metadata: { isSystem: false } },
|
|
14
|
-
{ id: '.system/imagegen', name: 'imagegen', description: 'Image gen skill', type: 'skill' as const, enabled: true, metadata: { isSystem: true } },
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
describe('SkillMentionPopup', () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
vi.clearAllMocks();
|
|
20
|
-
vi.mocked(fetchSkills).mockResolvedValue(MOCK_SKILLS);
|
|
21
|
-
});
|
|
22
|
-
afterEach(cleanup);
|
|
23
|
-
|
|
24
|
-
it('renders loading state then skills', async () => {
|
|
25
|
-
render(<SkillMentionPopup filter="" onSelect={() => {}} onClose={() => {}} />);
|
|
26
|
-
expect(screen.getByText('Loading skills…')).toBeInTheDocument();
|
|
27
|
-
await waitFor(() => expect(screen.getByText('cloudflare')).toBeInTheDocument());
|
|
28
|
-
expect(screen.getByText('imagegen')).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('filters skills by name', async () => {
|
|
32
|
-
render(<SkillMentionPopup filter="cloud" onSelect={() => {}} onClose={() => {}} />);
|
|
33
|
-
await waitFor(() => expect(screen.getByText('cloudflare')).toBeInTheDocument());
|
|
34
|
-
expect(screen.queryByText('imagegen')).not.toBeInTheDocument();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('shows system badge for system skills', async () => {
|
|
38
|
-
render(<SkillMentionPopup filter="" onSelect={() => {}} onClose={() => {}} />);
|
|
39
|
-
await waitFor(() => expect(screen.getByText('imagegen')).toBeInTheDocument());
|
|
40
|
-
expect(screen.getByText('system')).toBeInTheDocument();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('calls onSelect when skill is clicked', async () => {
|
|
44
|
-
const onSelect = vi.fn();
|
|
45
|
-
render(<SkillMentionPopup filter="" onSelect={onSelect} onClose={() => {}} />);
|
|
46
|
-
await waitFor(() => expect(screen.getByText('cloudflare')).toBeInTheDocument());
|
|
47
|
-
fireEvent.click(screen.getByText('cloudflare'));
|
|
48
|
-
expect(onSelect).toHaveBeenCalledWith(MOCK_SKILLS[0]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('calls onClose on Escape key', async () => {
|
|
52
|
-
const onClose = vi.fn();
|
|
53
|
-
render(<SkillMentionPopup filter="" onSelect={() => {}} onClose={onClose} />);
|
|
54
|
-
await waitFor(() => expect(screen.getByText('cloudflare')).toBeInTheDocument());
|
|
55
|
-
fireEvent.keyDown(window, { key: 'Escape' });
|
|
56
|
-
expect(onClose).toHaveBeenCalled();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('shows empty state when no skills match', async () => {
|
|
60
|
-
render(<SkillMentionPopup filter="xyz" onSelect={() => {}} onClose={() => {}} />);
|
|
61
|
-
await waitFor(() => expect(screen.getByText('No matching skills')).toBeInTheDocument());
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { fetchSkills } from '../../api/skills.ts';
|
|
3
|
-
import type { Extension } from '../../types/index.ts';
|
|
4
|
-
|
|
5
|
-
interface SkillMentionPopupProps {
|
|
6
|
-
filter: string;
|
|
7
|
-
onSelect: (skill: Extension) => void;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Skill search popup — triggered by `$` or `/skills` command. */
|
|
12
|
-
export default function SkillMentionPopup({ filter, onSelect, onClose }: SkillMentionPopupProps) {
|
|
13
|
-
const [skills, setSkills] = useState<Extension[]>([]);
|
|
14
|
-
const [loading, setLoading] = useState(true);
|
|
15
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
16
|
-
const listRef = useRef<HTMLDivElement>(null);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
fetchSkills()
|
|
20
|
-
.then(setSkills)
|
|
21
|
-
.catch(() => setSkills([]))
|
|
22
|
-
.finally(() => setLoading(false));
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
|
-
const filtered = skills.filter((s) =>
|
|
26
|
-
s.name.toLowerCase().includes(filter.toLowerCase()) ||
|
|
27
|
-
s.description.toLowerCase().includes(filter.toLowerCase()),
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
setSelectedIndex(0);
|
|
32
|
-
}, [filter]);
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
function onKey(e: KeyboardEvent) {
|
|
36
|
-
if (e.key === 'ArrowDown') {
|
|
37
|
-
e.preventDefault();
|
|
38
|
-
setSelectedIndex((i) => Math.min(i + 1, filtered.length - 1));
|
|
39
|
-
} else if (e.key === 'ArrowUp') {
|
|
40
|
-
e.preventDefault();
|
|
41
|
-
setSelectedIndex((i) => Math.max(i - 1, 0));
|
|
42
|
-
} else if (e.key === 'Enter') {
|
|
43
|
-
e.preventDefault();
|
|
44
|
-
if (filtered[selectedIndex]) onSelect(filtered[selectedIndex]);
|
|
45
|
-
} else if (e.key === 'Escape') {
|
|
46
|
-
e.preventDefault();
|
|
47
|
-
onClose();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
window.addEventListener('keydown', onKey);
|
|
51
|
-
return () => window.removeEventListener('keydown', onKey);
|
|
52
|
-
}, [filtered, selectedIndex, onSelect, onClose]);
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div className="slash-popup" ref={listRef}>
|
|
56
|
-
<div className="slash-popup-header">
|
|
57
|
-
<button className="slash-popup-back" onClick={onClose}>← Back</button>
|
|
58
|
-
<span>Skills</span>
|
|
59
|
-
</div>
|
|
60
|
-
{loading && <div className="slash-popup-empty">Loading skills…</div>}
|
|
61
|
-
{!loading && filtered.length === 0 && <div className="slash-popup-empty">No matching skills</div>}
|
|
62
|
-
{filtered.map((skill, i) => (
|
|
63
|
-
<div
|
|
64
|
-
key={skill.id}
|
|
65
|
-
className={`slash-popup-item ${i === selectedIndex ? 'selected' : ''}`}
|
|
66
|
-
onMouseEnter={() => setSelectedIndex(i)}
|
|
67
|
-
onClick={() => onSelect(skill)}
|
|
68
|
-
>
|
|
69
|
-
<div className="slash-popup-item-main">
|
|
70
|
-
<span className="slash-popup-label">{skill.name}</span>
|
|
71
|
-
{skill.description && <span className="slash-popup-hint">{skill.description}</span>}
|
|
72
|
-
</div>
|
|
73
|
-
{Boolean(skill.metadata.isSystem) && <span className="slash-popup-hint">system</span>}
|
|
74
|
-
</div>
|
|
75
|
-
))}
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
-
import SlashPopup, { filterCommands, type SlashCommand } from './SlashPopup.tsx';
|
|
4
|
-
|
|
5
|
-
const ALL_CAPS = {
|
|
6
|
-
dynamicModels: true,
|
|
7
|
-
skills: true,
|
|
8
|
-
plugins: true,
|
|
9
|
-
mcpServers: true,
|
|
10
|
-
imageAttachments: true,
|
|
11
|
-
sandbox: true,
|
|
12
|
-
webSearch: true,
|
|
13
|
-
networkAccess: true,
|
|
14
|
-
fileReferences: true,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const NO_CAPS = {
|
|
18
|
-
dynamicModels: false,
|
|
19
|
-
skills: false,
|
|
20
|
-
plugins: false,
|
|
21
|
-
mcpServers: false,
|
|
22
|
-
imageAttachments: false,
|
|
23
|
-
sandbox: false,
|
|
24
|
-
webSearch: false,
|
|
25
|
-
networkAccess: false,
|
|
26
|
-
fileReferences: false,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
describe('filterCommands', () => {
|
|
30
|
-
it('returns all commands when all capabilities enabled and no filter', () => {
|
|
31
|
-
const cmds = filterCommands('', ALL_CAPS);
|
|
32
|
-
expect(cmds.length).toBe(7);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('filters by text', () => {
|
|
36
|
-
const cmds = filterCommands('sand', ALL_CAPS);
|
|
37
|
-
expect(cmds).toHaveLength(1);
|
|
38
|
-
expect(cmds[0].id).toBe('sandbox');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('hides commands when capability is false', () => {
|
|
42
|
-
const cmds = filterCommands('', NO_CAPS);
|
|
43
|
-
// All commands have a capability requirement, so none should show
|
|
44
|
-
expect(cmds).toHaveLength(0);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('shows only commands with matching capability', () => {
|
|
48
|
-
const partial = { ...NO_CAPS, sandbox: true, webSearch: true };
|
|
49
|
-
const cmds = filterCommands('', partial);
|
|
50
|
-
expect(cmds).toHaveLength(2);
|
|
51
|
-
expect(cmds.map((c) => c.id)).toEqual(['sandbox', 'websearch']);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('SlashPopup', () => {
|
|
56
|
-
afterEach(cleanup);
|
|
57
|
-
|
|
58
|
-
const commands: SlashCommand[] = [
|
|
59
|
-
{ id: 'sandbox', label: '/sandbox', hint: 'Sandbox mode', capability: 'sandbox', subPopup: 'sandbox' },
|
|
60
|
-
{ id: 'websearch', label: '/websearch', hint: 'Web search', capability: 'webSearch', subPopup: 'websearch' },
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
it('renders all commands', () => {
|
|
64
|
-
render(<SlashPopup commands={commands} selectedIndex={0} onSelect={() => {}} onHover={() => {}} />);
|
|
65
|
-
expect(screen.getByText('/sandbox')).toBeInTheDocument();
|
|
66
|
-
expect(screen.getByText('/websearch')).toBeInTheDocument();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('shows empty state when no commands', () => {
|
|
70
|
-
render(<SlashPopup commands={[]} selectedIndex={0} onSelect={() => {}} onHover={() => {}} />);
|
|
71
|
-
expect(screen.getByText('No matching commands')).toBeInTheDocument();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('calls onSelect when command is clicked', () => {
|
|
75
|
-
const onSelect = vi.fn();
|
|
76
|
-
render(<SlashPopup commands={commands} selectedIndex={0} onSelect={onSelect} onHover={() => {}} />);
|
|
77
|
-
fireEvent.click(screen.getByText('/sandbox'));
|
|
78
|
-
expect(onSelect).toHaveBeenCalledWith(commands[0]);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('applies selected class to the selected index', () => {
|
|
82
|
-
const { container } = render(
|
|
83
|
-
<SlashPopup commands={commands} selectedIndex={1} onSelect={() => {}} onHover={() => {}} />,
|
|
84
|
-
);
|
|
85
|
-
const items = container.querySelectorAll('.slash-popup-item');
|
|
86
|
-
expect(items[1]).toHaveClass('selected');
|
|
87
|
-
expect(items[0]).not.toHaveClass('selected');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('calls onHover on mouse enter', () => {
|
|
91
|
-
const onHover = vi.fn();
|
|
92
|
-
render(<SlashPopup commands={commands} selectedIndex={0} onSelect={() => {}} onHover={onHover} />);
|
|
93
|
-
fireEvent.mouseEnter(screen.getByText('/websearch'));
|
|
94
|
-
expect(onHover).toHaveBeenCalledWith(1);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type { ProviderCapabilities, ProviderSlashCommand } from '../../types/index.ts';
|
|
2
|
-
|
|
3
|
-
/** A slash command definition. */
|
|
4
|
-
export interface SlashCommand {
|
|
5
|
-
id: string;
|
|
6
|
-
label: string;
|
|
7
|
-
hint: string;
|
|
8
|
-
/** Capability required to show this command. If null, always visible. */
|
|
9
|
-
capability: keyof ProviderCapabilities | null;
|
|
10
|
-
/** Sub-popup type to open when selected. */
|
|
11
|
-
subPopup?: 'sandbox' | 'websearch' | 'network';
|
|
12
|
-
/** When true, selecting inserts `/<id> ` as text (provider-native commands
|
|
13
|
-
* like /compact, /clear that are handled by the runtime, not the UI). */
|
|
14
|
-
insertAsText?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** All built-in slash commands. */
|
|
18
|
-
const BUILTIN_COMMANDS: SlashCommand[] = [
|
|
19
|
-
{ id: 'model', label: '/model', hint: 'Change model', capability: 'dynamicModels' },
|
|
20
|
-
{ id: 'sandbox', label: '/sandbox', hint: 'Sandbox mode', capability: 'sandbox', subPopup: 'sandbox' },
|
|
21
|
-
{ id: 'websearch', label: '/websearch', hint: 'Web search mode', capability: 'webSearch', subPopup: 'websearch' },
|
|
22
|
-
{ id: 'network', label: '/network', hint: 'Network access', capability: 'networkAccess', subPopup: 'network' },
|
|
23
|
-
{ id: 'skills', label: '/skills', hint: 'Browse skills', capability: 'skills' },
|
|
24
|
-
{ id: 'plugins', label: '/plugins', hint: 'Browse plugins', capability: 'plugins' },
|
|
25
|
-
{ id: 'mcp', label: '/mcp', hint: 'Browse MCP servers', capability: 'mcpServers' },
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
/** Map provider-native commands (fetched dynamically from the runtime) to SlashCommand. */
|
|
29
|
-
function mapProviderCommands(commands: ProviderSlashCommand[]): SlashCommand[] {
|
|
30
|
-
return commands.map((c) => ({
|
|
31
|
-
id: c.name,
|
|
32
|
-
label: `/${c.name}`,
|
|
33
|
-
hint: c.description,
|
|
34
|
-
capability: null,
|
|
35
|
-
insertAsText: true,
|
|
36
|
-
}));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Filter built-in and provider commands by capability and text filter. */
|
|
40
|
-
export function filterCommands(
|
|
41
|
-
filter: string,
|
|
42
|
-
capabilities: ProviderCapabilities,
|
|
43
|
-
providerCommands: ProviderSlashCommand[] = [],
|
|
44
|
-
): SlashCommand[] {
|
|
45
|
-
const all = [...BUILTIN_COMMANDS, ...mapProviderCommands(providerCommands)];
|
|
46
|
-
const seen = new Set<string>();
|
|
47
|
-
return all.filter((cmd) => {
|
|
48
|
-
if (seen.has(cmd.label)) return false;
|
|
49
|
-
seen.add(cmd.label);
|
|
50
|
-
if (cmd.capability && !capabilities[cmd.capability]) return false;
|
|
51
|
-
if (filter && !cmd.label.toLowerCase().includes(filter.toLowerCase())) return false;
|
|
52
|
-
return true;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface SlashPopupProps {
|
|
57
|
-
commands: SlashCommand[];
|
|
58
|
-
selectedIndex: number;
|
|
59
|
-
onSelect: (cmd: SlashCommand) => void;
|
|
60
|
-
onHover: (index: number) => void;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Presentational slash command popup. Keyboard navigation is handled by the parent. */
|
|
64
|
-
export default function SlashPopup({ commands, selectedIndex, onSelect, onHover }: SlashPopupProps) {
|
|
65
|
-
return (
|
|
66
|
-
<div className="slash-popup">
|
|
67
|
-
<div className="slash-popup-header">Commands</div>
|
|
68
|
-
{commands.length === 0 && <div className="slash-popup-empty">No matching commands</div>}
|
|
69
|
-
{commands.map((cmd, i) => (
|
|
70
|
-
<div
|
|
71
|
-
key={cmd.id}
|
|
72
|
-
className={`slash-popup-item ${i === selectedIndex ? 'selected' : ''}`}
|
|
73
|
-
onMouseEnter={() => onHover(i)}
|
|
74
|
-
onClick={() => onSelect(cmd)}
|
|
75
|
-
>
|
|
76
|
-
<span className="slash-popup-label">{cmd.label}</span>
|
|
77
|
-
<span className="slash-popup-hint">{cmd.hint}</span>
|
|
78
|
-
</div>
|
|
79
|
-
))}
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
-
import ChangesPanel from './ChangesPanel.tsx';
|
|
4
|
-
import { resetStore } from '../../test/component-helpers.ts';
|
|
5
|
-
import { makeDetail, makeTurn, makeItem } from '../../test/fixtures.ts';
|
|
6
|
-
|
|
7
|
-
describe('ChangesPanel', () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
resetStore();
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
cleanup();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('shows empty state when no file changes', () => {
|
|
16
|
-
const detail = makeDetail('s1', []);
|
|
17
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
18
|
-
render(<ChangesPanel />);
|
|
19
|
-
expect(screen.getByText('No file changes in this scope')).toBeInTheDocument();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('session scope button is active by default', () => {
|
|
23
|
-
const detail = makeDetail('s1', []);
|
|
24
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
25
|
-
render(<ChangesPanel />);
|
|
26
|
-
const sessionBtn = screen.getByText('Session');
|
|
27
|
-
expect(sessionBtn.closest('.scope-btn')).toHaveClass('active');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('click "Current Turn" switches scope to turngroup', () => {
|
|
31
|
-
const setChangesScope = vi.fn();
|
|
32
|
-
const detail = makeDetail('s1', []);
|
|
33
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, setChangesScope });
|
|
34
|
-
render(<ChangesPanel />);
|
|
35
|
-
fireEvent.click(screen.getByText('Current Turn'));
|
|
36
|
-
expect(setChangesScope).toHaveBeenCalledWith('turngroup');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('renders file change entries with A/D/M status', () => {
|
|
40
|
-
const item = makeItem('i1', 't1', 'file_change');
|
|
41
|
-
item.data = { changes: [{ path: 'src/main.ts', kind: 'add' }, { path: 'src/old.ts', kind: 'delete' }] };
|
|
42
|
-
const turn = makeTurn('t1', 's1', { items: [item] });
|
|
43
|
-
const detail = makeDetail('s1', [turn]);
|
|
44
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
45
|
-
render(<ChangesPanel />);
|
|
46
|
-
expect(screen.getByText(/main\.ts/)).toBeInTheDocument();
|
|
47
|
-
expect(screen.getByText(/old\.ts/)).toBeInTheDocument();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('click on file change calls setSelectedChangePath', () => {
|
|
51
|
-
const setSelectedChangePath = vi.fn();
|
|
52
|
-
const item = makeItem('i1', 't1', 'file_change');
|
|
53
|
-
item.data = { changes: [{ path: 'src/app.ts', kind: 'modify' }] };
|
|
54
|
-
const turn = makeTurn('t1', 's1', { items: [item] });
|
|
55
|
-
const detail = makeDetail('s1', [turn]);
|
|
56
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, setSelectedChangePath });
|
|
57
|
-
render(<ChangesPanel />);
|
|
58
|
-
fireEvent.click(screen.getByText(/app\.ts/).closest('.file-change')!);
|
|
59
|
-
expect(setSelectedChangePath).toHaveBeenCalledWith('src/app.ts');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { useStore } from '../../store/store.ts';
|
|
2
|
-
import { collectFileChanges } from '../../utils/changes.ts';
|
|
3
|
-
|
|
4
|
-
export default function ChangesPanel() {
|
|
5
|
-
const { activeSessionId, sessionDetails, changesScope, setChangesScope, activeTurnIndex, setSelectedChangePath } =
|
|
6
|
-
useStore();
|
|
7
|
-
const detail = activeSessionId ? sessionDetails[activeSessionId] : null;
|
|
8
|
-
const turns = detail?.turns ?? [];
|
|
9
|
-
|
|
10
|
-
const deduped = collectFileChanges(turns, changesScope, activeTurnIndex);
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<>
|
|
14
|
-
<div className="changes-scope-toggle">
|
|
15
|
-
<button
|
|
16
|
-
className={`scope-btn ${changesScope === 'session' ? 'active' : ''}`}
|
|
17
|
-
onClick={() => setChangesScope('session')}
|
|
18
|
-
>
|
|
19
|
-
Session
|
|
20
|
-
</button>
|
|
21
|
-
<button
|
|
22
|
-
className={`scope-btn ${changesScope === 'turngroup' ? 'active' : ''}`}
|
|
23
|
-
onClick={() => setChangesScope('turngroup')}
|
|
24
|
-
>
|
|
25
|
-
Current Turn
|
|
26
|
-
</button>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
{deduped.length === 0 ? (
|
|
30
|
-
<div className="empty-state">No file changes in this scope</div>
|
|
31
|
-
) : (
|
|
32
|
-
<div className="right-section">
|
|
33
|
-
<div className="right-section-title">Changed Files ({deduped.length})</div>
|
|
34
|
-
{deduped.map((entry) => {
|
|
35
|
-
const statusClass = entry.kind === 'add' ? 'fc-add' : entry.kind === 'delete' ? 'fc-del' : 'fc-mod';
|
|
36
|
-
const statusChar = entry.kind === 'add' ? 'A' : entry.kind === 'delete' ? 'D' : 'M';
|
|
37
|
-
const fileName = entry.path.split('/').pop() || entry.path;
|
|
38
|
-
const dir = entry.path.replace('/' + fileName, '');
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<div key={entry.path} className="file-change clickable" onClick={() => setSelectedChangePath(entry.path)}>
|
|
42
|
-
<span className={`fc-status ${statusClass}`}>{statusChar}</span>
|
|
43
|
-
<span className="fc-name">
|
|
44
|
-
{dir && <span style={{ color: 'var(--text-muted)' }}>{dir}/</span>}
|
|
45
|
-
{fileName}
|
|
46
|
-
</span>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
})}
|
|
50
|
-
</div>
|
|
51
|
-
)}
|
|
52
|
-
</>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { render, screen, cleanup } from '@testing-library/react';
|
|
3
|
-
import TasksPanel from './TasksPanel.tsx';
|
|
4
|
-
import { resetStore } from '../../test/component-helpers.ts';
|
|
5
|
-
import { makeDetail, makeTurn, makeItem } from '../../test/fixtures.ts';
|
|
6
|
-
|
|
7
|
-
describe('TasksPanel', () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
resetStore();
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
cleanup();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('shows empty state when no background tasks', () => {
|
|
16
|
-
const detail = makeDetail('s1', []);
|
|
17
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
18
|
-
render(<TasksPanel />);
|
|
19
|
-
expect(screen.getByText('No background tasks yet')).toBeInTheDocument();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('renders a Monitor task card with description', () => {
|
|
23
|
-
const item = makeItem('i1', 't1', 'background_task');
|
|
24
|
-
item.data = {
|
|
25
|
-
task_type: 'local_bash',
|
|
26
|
-
description: 'watch echo hello',
|
|
27
|
-
status: 'running',
|
|
28
|
-
output: 'hello\n',
|
|
29
|
-
};
|
|
30
|
-
item.status = 'in_progress';
|
|
31
|
-
const turn = makeTurn('t1', 's1', { items: [item] });
|
|
32
|
-
const detail = makeDetail('s1', [turn]);
|
|
33
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
34
|
-
render(<TasksPanel />);
|
|
35
|
-
expect(screen.getByText('Monitor')).toBeInTheDocument();
|
|
36
|
-
expect(screen.getByText('watch echo hello')).toBeInTheDocument();
|
|
37
|
-
// Output is in a <code> element inside .task-output
|
|
38
|
-
const outputEl = document.querySelector('.task-output code');
|
|
39
|
-
expect(outputEl?.textContent).toContain('hello');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('does not show Subagent (local_agent) tasks in Tasks panel', () => {
|
|
43
|
-
const item = makeItem('i2', 't1', 'background_task');
|
|
44
|
-
item.data = {
|
|
45
|
-
task_type: 'local_agent',
|
|
46
|
-
subagent_type: 'explore-codebase',
|
|
47
|
-
description: 'explore the codebase',
|
|
48
|
-
status: 'completed',
|
|
49
|
-
};
|
|
50
|
-
item.status = 'completed';
|
|
51
|
-
const turn = makeTurn('t1', 's1', { items: [item] });
|
|
52
|
-
const detail = makeDetail('s1', [turn]);
|
|
53
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
54
|
-
render(<TasksPanel />);
|
|
55
|
-
// local_agent items are filtered out — they appear in subagent tabs, not Tasks panel
|
|
56
|
-
expect(screen.getByText('No background tasks yet')).toBeInTheDocument();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('sorts running tasks before completed', () => {
|
|
60
|
-
const runningItem = makeItem('i1', 't2', 'background_task');
|
|
61
|
-
runningItem.data = { task_type: 'local_bash', description: 'running task', status: 'running' };
|
|
62
|
-
runningItem.status = 'in_progress';
|
|
63
|
-
|
|
64
|
-
const doneItem = makeItem('i2', 't1', 'background_task');
|
|
65
|
-
doneItem.data = { task_type: 'local_bash', description: 'done task', status: 'completed' };
|
|
66
|
-
doneItem.status = 'completed';
|
|
67
|
-
|
|
68
|
-
const turn1 = makeTurn('t1', 's1', { items: [doneItem] });
|
|
69
|
-
const turn2 = makeTurn('t2', 's1', { items: [runningItem] });
|
|
70
|
-
const detail = makeDetail('s1', [turn1, turn2]);
|
|
71
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
72
|
-
render(<TasksPanel />);
|
|
73
|
-
|
|
74
|
-
const cards = document.querySelectorAll('.task-card');
|
|
75
|
-
expect(cards.length).toBe(2);
|
|
76
|
-
// Running task should be first
|
|
77
|
-
expect(cards[0].textContent).toContain('running task');
|
|
78
|
-
expect(cards[1].textContent).toContain('done task');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('shows task count in section title (local_agent excluded)', () => {
|
|
82
|
-
const item1 = makeItem('i1', 't1', 'background_task');
|
|
83
|
-
item1.data = { task_type: 'local_bash', description: 'task 1', status: 'completed' };
|
|
84
|
-
item1.status = 'completed';
|
|
85
|
-
|
|
86
|
-
const item2 = makeItem('i2', 't1', 'background_task');
|
|
87
|
-
item2.data = { task_type: 'local_agent', description: 'task 2', status: 'completed' };
|
|
88
|
-
item2.status = 'completed';
|
|
89
|
-
|
|
90
|
-
const turn = makeTurn('t1', 's1', { items: [item1, item2] });
|
|
91
|
-
const detail = makeDetail('s1', [turn]);
|
|
92
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail } });
|
|
93
|
-
render(<TasksPanel />);
|
|
94
|
-
// Only local_bash counted — local_agent filtered out
|
|
95
|
-
expect(screen.getByText(/Background Tasks \(1\)/)).toBeInTheDocument();
|
|
96
|
-
});
|
|
97
|
-
});
|