repoburg 1.3.202 → 1.3.209
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 +2103 -32003
- package/agent-pane/backend/dist/server.js +45313 -0
- package/backend/dist/src/agent-runtime/agent-runtime.types.d.ts +1 -0
- package/backend/dist/src/llm-orchestration/hooks/frontend-notification.hook.js +6 -6
- package/backend/dist/src/llm-orchestration/hooks/frontend-notification.hook.js.map +1 -1
- package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js +1 -0
- package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js.map +1 -1
- 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/model-selector-scroll.png +0 -0
- package/package.json +10 -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,143 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
-
import AskUserItem, { resetAskUserStore, buildSelectionPrompt } from './AskUserItem.tsx';
|
|
4
|
-
import { useStore } from '../store/store.ts';
|
|
5
|
-
import { resetStore } from '../test/component-helpers.ts';
|
|
6
|
-
import { makeTurn, makeItem, makeDetail } from '../test/fixtures.ts';
|
|
7
|
-
import type { Item, Turn } from '@agentpane/shared';
|
|
8
|
-
|
|
9
|
-
const GROUPS = [
|
|
10
|
-
{ question: 'Which database?', selections: ['Postgres', 'SQLite'] },
|
|
11
|
-
{ question: 'Proceed?', selections: ['yes', 'no'] },
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
function askItem(id: string, turnId: string, data: Record<string, unknown> | null): Item {
|
|
15
|
-
const item = makeItem(id, turnId, 'ask_user');
|
|
16
|
-
item.data = data;
|
|
17
|
-
return item;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function setup(turn: Turn, isRunning = 0) {
|
|
21
|
-
resetStore({
|
|
22
|
-
activeSessionId: turn.session_id,
|
|
23
|
-
sessionDetails: { [turn.session_id]: { ...makeDetail(turn.session_id, [turn]), is_running: isRunning } },
|
|
24
|
-
});
|
|
25
|
-
render(<AskUserItem item={turn.items[turn.items.length - 1]} turn={turn} />);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function makeAskTurn(items: Item[]): Turn {
|
|
29
|
-
return makeTurn('t1', 's1', { items, status: 'completed' });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('AskUserItem', () => {
|
|
33
|
-
afterEach(() => {
|
|
34
|
-
cleanup();
|
|
35
|
-
resetAskUserStore();
|
|
36
|
-
resetStore();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('renders all groups with their options', () => {
|
|
40
|
-
const turn = makeAskTurn([askItem('i1', 't1', { question: '', groups: GROUPS })]);
|
|
41
|
-
setup(turn);
|
|
42
|
-
expect(screen.getByText('Which database?')).toBeInTheDocument();
|
|
43
|
-
expect(screen.getByText('Proceed?')).toBeInTheDocument();
|
|
44
|
-
expect(screen.getByText('Postgres')).toBeInTheDocument();
|
|
45
|
-
expect(screen.getByText('SQLite')).toBeInTheDocument();
|
|
46
|
-
expect(screen.getByText('yes')).toBeInTheDocument();
|
|
47
|
-
expect(screen.getByText('no')).toBeInTheDocument();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('toggles one answer per group; re-click deselects', () => {
|
|
51
|
-
const turn = makeAskTurn([askItem('i1', 't1', { question: '', groups: GROUPS })]);
|
|
52
|
-
setup(turn);
|
|
53
|
-
|
|
54
|
-
const send = screen.getByRole('button', { name: /send/i });
|
|
55
|
-
expect(send).toBeDisabled();
|
|
56
|
-
|
|
57
|
-
fireEvent.click(screen.getByText('Postgres'));
|
|
58
|
-
expect(send).toBeEnabled();
|
|
59
|
-
|
|
60
|
-
// Selecting another option in the same group replaces the answer.
|
|
61
|
-
fireEvent.click(screen.getByText('SQLite'));
|
|
62
|
-
expect(screen.getByText('SQLite').className).toContain('selected');
|
|
63
|
-
expect(screen.getByText('Postgres').className).not.toContain('selected');
|
|
64
|
-
|
|
65
|
-
// Re-click deselects.
|
|
66
|
-
fireEvent.click(screen.getByText('SQLite'));
|
|
67
|
-
expect(screen.getByText('SQLite').className).not.toContain('selected');
|
|
68
|
-
expect(send).toBeDisabled();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('submits the combined prompt for all selected groups in ONE send', () => {
|
|
72
|
-
const turn = makeAskTurn([askItem('i1', 't1', { question: '', groups: GROUPS })]);
|
|
73
|
-
setup(turn);
|
|
74
|
-
const submitPrompt = vi.fn();
|
|
75
|
-
useStore.setState({ submitPrompt });
|
|
76
|
-
|
|
77
|
-
fireEvent.click(screen.getByText('Postgres'));
|
|
78
|
-
fireEvent.click(screen.getByText('yes'));
|
|
79
|
-
fireEvent.click(screen.getByRole('button', { name: /send/i }));
|
|
80
|
-
|
|
81
|
-
expect(submitPrompt).toHaveBeenCalledTimes(1);
|
|
82
|
-
expect(submitPrompt).toHaveBeenCalledWith('> Which database?\nPostgres\n\n> Proceed?\nyes');
|
|
83
|
-
expect(screen.getByText('Answer sent')).toBeInTheDocument();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('submits answers from MULTIPLE ask_user items in one turn together, sorted by item order', () => {
|
|
87
|
-
const turn = makeAskTurn([
|
|
88
|
-
askItem('i1', 't1', { question: '', groups: [GROUPS[0]] }),
|
|
89
|
-
askItem('i2', 't1', { question: '', groups: [GROUPS[1]] }),
|
|
90
|
-
]);
|
|
91
|
-
resetStore({
|
|
92
|
-
activeSessionId: 's1',
|
|
93
|
-
sessionDetails: { s1: makeDetail('s1', [turn]) },
|
|
94
|
-
});
|
|
95
|
-
const submitPrompt = vi.fn();
|
|
96
|
-
useStore.setState({ submitPrompt });
|
|
97
|
-
|
|
98
|
-
const { rerender } = render(<AskUserItem item={turn.items[0]} turn={turn} />);
|
|
99
|
-
fireEvent.click(screen.getByText('Postgres'));
|
|
100
|
-
|
|
101
|
-
// Select on the SECOND card; its Send must carry the FIRST card's answer too.
|
|
102
|
-
rerender(<AskUserItem item={turn.items[1]} turn={turn} />);
|
|
103
|
-
fireEvent.click(screen.getByText('yes'));
|
|
104
|
-
fireEvent.click(screen.getByRole('button', { name: /send/i }));
|
|
105
|
-
|
|
106
|
-
expect(submitPrompt).toHaveBeenCalledTimes(1);
|
|
107
|
-
expect(submitPrompt).toHaveBeenCalledWith('> Which database?\nPostgres\n\n> Proceed?\nyes');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('disables buttons while the session is running', () => {
|
|
111
|
-
const turn = makeAskTurn([askItem('i1', 't1', { question: '', groups: GROUPS })]);
|
|
112
|
-
setup(turn, 1);
|
|
113
|
-
expect(screen.getByText('Postgres')).toBeDisabled();
|
|
114
|
-
// No send affordance while disabled.
|
|
115
|
-
expect(screen.queryByRole('button', { name: /send/i })).not.toBeInTheDocument();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('renders a plain-text fallback for malformed selections payloads', () => {
|
|
119
|
-
const turn = makeAskTurn([askItem('i1', 't1', { question: 'Plain question?', groups: [] })]);
|
|
120
|
-
setup(turn);
|
|
121
|
-
expect(screen.getByText('Plain question?')).toBeInTheDocument();
|
|
122
|
-
expect(screen.queryByRole('button', { name: /send/i })).not.toBeInTheDocument();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('renders the waiting note when there is no payload', () => {
|
|
126
|
-
const turn = makeAskTurn([askItem('i1', 't1', null)]);
|
|
127
|
-
setup(turn);
|
|
128
|
-
expect(screen.getByText(/Waiting for user response/i)).toBeInTheDocument();
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('buildSelectionPrompt', () => {
|
|
133
|
-
it('sorts by item index then group index and joins with \\n\\n', () => {
|
|
134
|
-
const prompt = buildSelectionPrompt([
|
|
135
|
-
{ itemIndex: 1, groupIndex: 0, question: 'Second?', answer: 'b' },
|
|
136
|
-
{ itemIndex: 0, groupIndex: 1, question: 'First group 2?', answer: 'a2' },
|
|
137
|
-
{ itemIndex: 0, groupIndex: 0, question: 'First group 1?', answer: 'a1' },
|
|
138
|
-
]);
|
|
139
|
-
expect(prompt).toBe(
|
|
140
|
-
'> First group 1?\na1\n\n> First group 2?\na2\n\n> Second?\nb',
|
|
141
|
-
);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AskUserItem — renders the runtime's ask_user action as clickable
|
|
3
|
-
* selection buttons (platform parity: SelectionButtons in the webcoder
|
|
4
|
-
* platform frontend).
|
|
5
|
-
*
|
|
6
|
-
* Behavior:
|
|
7
|
-
* - All groups render; selection is a TOGGLE (one answer per group).
|
|
8
|
-
* - Selection state is shared PER TURN (module store keyed by turn id) —
|
|
9
|
-
* if a turn ever holds multiple ask_user items, ONE Send submits ALL
|
|
10
|
-
* selected answers together as a single user_prompt.
|
|
11
|
-
* - Combined prompt = `> ${question}\n${answer}` per selected group,
|
|
12
|
-
* sorted by item order then group index, joined with `\n\n`.
|
|
13
|
-
* - Disabled once the turn is submitted, the session is running, or a
|
|
14
|
-
* newer turn exists.
|
|
15
|
-
*/
|
|
16
|
-
import { useCallback, useMemo, useSyncExternalStore } from 'react';
|
|
17
|
-
import { CircleHelp, Send } from 'lucide-react';
|
|
18
|
-
import type { AskUserItemData, Item, Turn } from '../types/index.ts';
|
|
19
|
-
import { useStore } from '../store/store.ts';
|
|
20
|
-
|
|
21
|
-
/** One selected answer with its ordering key. */
|
|
22
|
-
interface SelectionEntry {
|
|
23
|
-
itemIndex: number;
|
|
24
|
-
groupIndex: number;
|
|
25
|
-
question: string;
|
|
26
|
-
answer: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// --- Per-turn shared selection store (module-scope, tiny pub/sub) ---
|
|
30
|
-
const turnSelections = new Map<string, Map<string, SelectionEntry>>();
|
|
31
|
-
const submittedTurns = new Set<string>();
|
|
32
|
-
const listeners = new Set<() => void>();
|
|
33
|
-
const EMPTY = new Map<string, SelectionEntry>();
|
|
34
|
-
|
|
35
|
-
function notify(): void {
|
|
36
|
-
listeners.forEach((l) => l());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function selectionKey(itemIndex: number, groupIndex: number): string {
|
|
40
|
-
return `${itemIndex}:${groupIndex}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function toggleSelection(
|
|
44
|
-
turnId: string,
|
|
45
|
-
itemIndex: number,
|
|
46
|
-
groupIndex: number,
|
|
47
|
-
question: string,
|
|
48
|
-
answer: string,
|
|
49
|
-
): void {
|
|
50
|
-
const key = selectionKey(itemIndex, groupIndex);
|
|
51
|
-
const current = turnSelections.get(turnId) ?? new Map<string, SelectionEntry>();
|
|
52
|
-
const next = new Map(current);
|
|
53
|
-
if (next.get(key)?.answer === answer) {
|
|
54
|
-
next.delete(key); // re-click deselects
|
|
55
|
-
} else {
|
|
56
|
-
next.set(key, { itemIndex, groupIndex, question, answer });
|
|
57
|
-
}
|
|
58
|
-
turnSelections.set(turnId, next);
|
|
59
|
-
notify();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function clearTurnSelections(turnId: string): void {
|
|
63
|
-
turnSelections.delete(turnId);
|
|
64
|
-
notify();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function markSubmitted(turnId: string): void {
|
|
68
|
-
submittedTurns.add(turnId);
|
|
69
|
-
clearTurnSelections(turnId);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Reset state (used by tests). */
|
|
73
|
-
export function resetAskUserStore(): void {
|
|
74
|
-
turnSelections.clear();
|
|
75
|
-
submittedTurns.clear();
|
|
76
|
-
notify();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function useTurnSelections(turnId: string): Map<string, SelectionEntry> {
|
|
80
|
-
const subscribe = useCallback((cb: () => void) => {
|
|
81
|
-
listeners.add(cb);
|
|
82
|
-
return () => {
|
|
83
|
-
listeners.delete(cb);
|
|
84
|
-
};
|
|
85
|
-
}, []);
|
|
86
|
-
return useSyncExternalStore(
|
|
87
|
-
subscribe,
|
|
88
|
-
() => turnSelections.get(turnId) ?? EMPTY,
|
|
89
|
-
() => EMPTY,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Build the combined prompt (platform parity: sorted, `> q\nanswer`, `\n\n` join). */
|
|
94
|
-
export function buildSelectionPrompt(entries: SelectionEntry[]): string {
|
|
95
|
-
return [...entries]
|
|
96
|
-
.sort((a, b) => a.itemIndex - b.itemIndex || a.groupIndex - b.groupIndex)
|
|
97
|
-
.map((e) => `> ${e.question}\n${e.answer}`)
|
|
98
|
-
.join('\n\n');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
interface Props {
|
|
102
|
-
item: Item;
|
|
103
|
-
turn: Turn;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export default function AskUserItem({ item, turn }: Props) {
|
|
107
|
-
const data = (item.data ?? {}) as Partial<AskUserItemData>;
|
|
108
|
-
const groups = useMemo(
|
|
109
|
-
() =>
|
|
110
|
-
Array.isArray(data.groups)
|
|
111
|
-
? data.groups.filter(
|
|
112
|
-
(g): g is { question: string; selections: string[] } =>
|
|
113
|
-
!!g && Array.isArray(g.selections) && g.selections.length > 0,
|
|
114
|
-
)
|
|
115
|
-
: [],
|
|
116
|
-
[data.groups],
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const selections = useTurnSelections(turn.id);
|
|
120
|
-
const detail = useStore((s) =>
|
|
121
|
-
turn.session_id ? s.sessionDetails[turn.session_id] : null,
|
|
122
|
-
);
|
|
123
|
-
const activeSessionId = useStore((s) => s.activeSessionId);
|
|
124
|
-
|
|
125
|
-
const itemIndex = turn.items.findIndex((i) => i.id === item.id);
|
|
126
|
-
const isLastTurn = detail
|
|
127
|
-
? detail.turns[detail.turns.length - 1]?.id === turn.id
|
|
128
|
-
: true;
|
|
129
|
-
const submitted = submittedTurns.has(turn.id);
|
|
130
|
-
const disabled =
|
|
131
|
-
submitted ||
|
|
132
|
-
detail?.is_running === 1 ||
|
|
133
|
-
!isLastTurn ||
|
|
134
|
-
turn.session_id !== activeSessionId;
|
|
135
|
-
|
|
136
|
-
const handleToggle = (groupIndex: number, question: string, answer: string) => {
|
|
137
|
-
if (disabled) return;
|
|
138
|
-
toggleSelection(turn.id, itemIndex, groupIndex, question, answer);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const handleSend = () => {
|
|
142
|
-
const entries = [...selections.values()];
|
|
143
|
-
if (entries.length === 0) return;
|
|
144
|
-
const prompt = buildSelectionPrompt(entries);
|
|
145
|
-
markSubmitted(turn.id);
|
|
146
|
-
void useStore.getState().submitPrompt(prompt);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<div className="render-content ask-user-card">
|
|
151
|
-
<div className="turn-header">
|
|
152
|
-
<span className="turn-header-icon" style={{ background: 'var(--surface-2)', color: 'var(--text-3)' }}>
|
|
153
|
-
<CircleHelp size={14} />
|
|
154
|
-
</span>
|
|
155
|
-
<span className="turn-header-title">Question</span>
|
|
156
|
-
{submitted && <span className="turn-header-meta">answered</span>}
|
|
157
|
-
</div>
|
|
158
|
-
|
|
159
|
-
{groups.length > 0 ? (
|
|
160
|
-
<div className="ask-user-groups">
|
|
161
|
-
{groups.map((group, gi) => (
|
|
162
|
-
<div key={gi} className="ask-user-group">
|
|
163
|
-
{group.question && <div className="ask-user-question">{group.question}</div>}
|
|
164
|
-
<div className="ask-user-options">
|
|
165
|
-
{group.selections.map((option) => {
|
|
166
|
-
const selected =
|
|
167
|
-
selections.get(selectionKey(itemIndex, gi))?.answer === option;
|
|
168
|
-
return (
|
|
169
|
-
<button
|
|
170
|
-
key={option}
|
|
171
|
-
type="button"
|
|
172
|
-
className={`ask-user-option ${selected ? 'selected' : ''}`}
|
|
173
|
-
disabled={disabled}
|
|
174
|
-
onClick={() => handleToggle(gi, group.question, option)}
|
|
175
|
-
>
|
|
176
|
-
{option}
|
|
177
|
-
</button>
|
|
178
|
-
);
|
|
179
|
-
})}
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
))}
|
|
183
|
-
{disabled ? (
|
|
184
|
-
submitted && <div className="ask-user-submitted">Answer sent</div>
|
|
185
|
-
) : (
|
|
186
|
-
<button
|
|
187
|
-
type="button"
|
|
188
|
-
className="ask-user-send"
|
|
189
|
-
disabled={selections.size === 0}
|
|
190
|
-
onClick={handleSend}
|
|
191
|
-
>
|
|
192
|
-
<Send size={12} />
|
|
193
|
-
Send
|
|
194
|
-
</button>
|
|
195
|
-
)}
|
|
196
|
-
</div>
|
|
197
|
-
) : data.question ? (
|
|
198
|
-
// Malformed payload fallback — plain-text question.
|
|
199
|
-
<p className="user-message">{data.question}</p>
|
|
200
|
-
) : (
|
|
201
|
-
<p style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>
|
|
202
|
-
Waiting for user response
|
|
203
|
-
</p>
|
|
204
|
-
)}
|
|
205
|
-
</div>
|
|
206
|
-
);
|
|
207
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
-
import CollapsibleToolCalls, { isToolItem } from './CollapsibleToolCalls.tsx';
|
|
4
|
-
import { makeItem, makeTurn } from '../test/fixtures.ts';
|
|
5
|
-
|
|
6
|
-
describe('isToolItem', () => {
|
|
7
|
-
it('returns true for tool types', () => {
|
|
8
|
-
expect(isToolItem(makeItem('i1', 't1', 'command_execution'))).toBe(true);
|
|
9
|
-
expect(isToolItem(makeItem('i2', 't1', 'file_change'))).toBe(true);
|
|
10
|
-
expect(isToolItem(makeItem('i3', 't1', 'mcp_tool_call'))).toBe(true);
|
|
11
|
-
expect(isToolItem(makeItem('i4', 't1', 'web_search'))).toBe(true);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('returns false for non-tool types', () => {
|
|
15
|
-
expect(isToolItem(makeItem('i1', 't1', 'agent_message'))).toBe(false);
|
|
16
|
-
expect(isToolItem(makeItem('i2', 't1', 'reasoning'))).toBe(false);
|
|
17
|
-
expect(isToolItem(makeItem('i3', 't1', 'todo_list'))).toBe(false);
|
|
18
|
-
expect(isToolItem(makeItem('i4', 't1', 'error'))).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('CollapsibleToolCalls', () => {
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
cleanup();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('returns null when items is empty', () => {
|
|
28
|
-
const { container } = render(<CollapsibleToolCalls items={[]} turn={makeTurn('t1', 's1')} />);
|
|
29
|
-
expect(container.firstChild).toBeNull();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('shows collapsed state by default (no item content visible)', () => {
|
|
33
|
-
const item = makeItem('i1', 't1', 'command_execution');
|
|
34
|
-
item.data = { command: 'ls', aggregated_output: '' };
|
|
35
|
-
render(<CollapsibleToolCalls items={[item]} turn={makeTurn('t1', 's1')} />);
|
|
36
|
-
expect(screen.getByText(/1 tool call/)).toBeInTheDocument();
|
|
37
|
-
// Item card content should NOT be rendered when collapsed
|
|
38
|
-
expect(screen.queryByText('Command')).not.toBeInTheDocument();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('expands on header click and shows item cards', () => {
|
|
42
|
-
const item = makeItem('i1', 't1', 'command_execution');
|
|
43
|
-
item.data = { command: 'echo hello', aggregated_output: 'hello' };
|
|
44
|
-
render(<CollapsibleToolCalls items={[item]} turn={makeTurn('t1', 's1')} />);
|
|
45
|
-
const header = document.querySelector('.tool-calls-header')!;
|
|
46
|
-
fireEvent.click(header);
|
|
47
|
-
expect(screen.getByText('Command')).toBeInTheDocument();
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { Wrench, ChevronRight } from 'lucide-react';
|
|
3
|
-
import { MemoItemCard } from './TurnContent.tsx';
|
|
4
|
-
import type { Item, Turn, DiscussionThread } from '../types/index.ts';
|
|
5
|
-
|
|
6
|
-
const TOOL_TYPES = new Set(['command_execution', 'file_change', 'mcp_tool_call', 'web_search']);
|
|
7
|
-
|
|
8
|
-
export function isToolItem(item: Item): boolean {
|
|
9
|
-
return TOOL_TYPES.has(item.type);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
items: Item[];
|
|
14
|
-
runtimeType?: string;
|
|
15
|
-
turn: Turn;
|
|
16
|
-
threads?: DiscussionThread[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default function CollapsibleToolCalls({ items, runtimeType, turn, threads = [] }: Props) {
|
|
20
|
-
const [expanded, setExpanded] = useState(false);
|
|
21
|
-
|
|
22
|
-
if (items.length === 0) return null;
|
|
23
|
-
|
|
24
|
-
const count = items.length;
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div className="tool-calls">
|
|
28
|
-
<div className="tool-calls-header" onClick={() => setExpanded((e) => !e)}>
|
|
29
|
-
<span className="tool-calls-icon">
|
|
30
|
-
<Wrench size={12} />
|
|
31
|
-
</span>
|
|
32
|
-
<span className="tool-calls-summary">
|
|
33
|
-
{count} tool call{count > 1 ? 's' : ''}
|
|
34
|
-
</span>
|
|
35
|
-
<ChevronRight
|
|
36
|
-
size={14}
|
|
37
|
-
className="tool-calls-chevron"
|
|
38
|
-
style={{ transform: expanded ? 'rotate(90deg)' : 'none' }}
|
|
39
|
-
/>
|
|
40
|
-
</div>
|
|
41
|
-
{expanded && (
|
|
42
|
-
<div className="tool-calls-body">
|
|
43
|
-
{items.map((item) => (
|
|
44
|
-
<MemoItemCard
|
|
45
|
-
key={item.id}
|
|
46
|
-
item={item}
|
|
47
|
-
runtimeType={runtimeType}
|
|
48
|
-
turn={turn}
|
|
49
|
-
threads={threads}
|
|
50
|
-
/>
|
|
51
|
-
))}
|
|
52
|
-
</div>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
-
|
|
4
|
-
// Mock useProvider to avoid async fetch in tests
|
|
5
|
-
vi.mock('../hooks/useProvider.ts', () => ({
|
|
6
|
-
useProvider: () => ({
|
|
7
|
-
provider: { id: 'codex', label: 'Codex', available: true, capabilities: {
|
|
8
|
-
dynamicModels: true, skills: true, plugins: true, mcpServers: true,
|
|
9
|
-
imageAttachments: true, sandbox: true, webSearch: true, networkAccess: true, fileReferences: true,
|
|
10
|
-
} },
|
|
11
|
-
capabilities: {
|
|
12
|
-
dynamicModels: true, skills: true, plugins: true, mcpServers: true,
|
|
13
|
-
imageAttachments: true, sandbox: true, webSearch: true, networkAccess: true, fileReferences: true,
|
|
14
|
-
},
|
|
15
|
-
loading: false,
|
|
16
|
-
}),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
import CommandInputBar from './CommandInputBar.tsx';
|
|
20
|
-
import { useStore } from '../store/store.ts';
|
|
21
|
-
import { resetStore } from '../test/component-helpers.ts';
|
|
22
|
-
import { makeSession, makeDetail } from '../test/fixtures.ts';
|
|
23
|
-
|
|
24
|
-
describe('CommandInputBar', () => {
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
vi.stubGlobal('requestAnimationFrame', (cb: () => void) => cb());
|
|
27
|
-
resetStore();
|
|
28
|
-
});
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
cleanup();
|
|
31
|
-
vi.unstubAllGlobals();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('shows "Select or create a session…" placeholder when no active session', () => {
|
|
35
|
-
render(<CommandInputBar />);
|
|
36
|
-
expect(screen.getByPlaceholderText('Select or create a session…')).toBeInTheDocument();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('shows "Agent is running…" placeholder when is_running=1', () => {
|
|
40
|
-
const detail = makeDetail('s1', [], { is_running: 1 });
|
|
41
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, sessions: [makeSession('s1')] });
|
|
42
|
-
render(<CommandInputBar />);
|
|
43
|
-
expect(screen.getByPlaceholderText('Agent is running…')).toBeInTheDocument();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('shows "Type a message…" placeholder when idle', () => {
|
|
47
|
-
const detail = makeDetail('s1', []);
|
|
48
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, sessions: [makeSession('s1')] });
|
|
49
|
-
render(<CommandInputBar />);
|
|
50
|
-
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
|
|
51
|
-
expect(textarea.placeholder).toMatch(/Type a message/);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('disables textarea when no active session', () => {
|
|
55
|
-
render(<CommandInputBar />);
|
|
56
|
-
expect(screen.getByRole('textbox')).toBeDisabled();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('sends prompt on Cmd+Enter and clears input', async () => {
|
|
60
|
-
const detail = makeDetail('s1', []);
|
|
61
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, sessions: [makeSession('s1')] });
|
|
62
|
-
const submitPrompt = vi.fn().mockResolvedValue(undefined);
|
|
63
|
-
useStore.setState({ submitPrompt });
|
|
64
|
-
|
|
65
|
-
render(<CommandInputBar />);
|
|
66
|
-
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
|
|
67
|
-
fireEvent.change(textarea, { target: { value: 'hello world' } });
|
|
68
|
-
fireEvent.keyDown(textarea, { key: 'Enter', metaKey: true });
|
|
69
|
-
|
|
70
|
-
expect(submitPrompt).toHaveBeenCalledWith('hello world', undefined);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('send button calls submitPrompt with trimmed value', async () => {
|
|
74
|
-
const detail = makeDetail('s1', []);
|
|
75
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, sessions: [makeSession('s1')] });
|
|
76
|
-
const submitPrompt = vi.fn().mockResolvedValue(undefined);
|
|
77
|
-
useStore.setState({ submitPrompt });
|
|
78
|
-
|
|
79
|
-
render(<CommandInputBar />);
|
|
80
|
-
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
|
|
81
|
-
fireEvent.change(textarea, { target: { value: ' test ' } });
|
|
82
|
-
const sendBtn = document.querySelector('.cmd-send-btn') as HTMLElement;
|
|
83
|
-
fireEvent.click(sendBtn);
|
|
84
|
-
|
|
85
|
-
expect(submitPrompt).toHaveBeenCalledWith('test', undefined);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('send button is disabled when input is empty', () => {
|
|
89
|
-
const detail = makeDetail('s1', []);
|
|
90
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, sessions: [makeSession('s1')] });
|
|
91
|
-
render(<CommandInputBar />);
|
|
92
|
-
const sendBtn = document.querySelector('.cmd-send-btn') as HTMLButtonElement;
|
|
93
|
-
expect(sendBtn.disabled).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('stop button is visible when running and calls cancelTurn', async () => {
|
|
97
|
-
const detail = makeDetail('s1', [], { is_running: 1 });
|
|
98
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, sessions: [makeSession('s1')] });
|
|
99
|
-
const cancelTurn = vi.fn().mockResolvedValue(undefined);
|
|
100
|
-
useStore.setState({ cancelTurn });
|
|
101
|
-
|
|
102
|
-
render(<CommandInputBar />);
|
|
103
|
-
const stopBtn = document.querySelector('.cmd-stop-btn') as HTMLElement;
|
|
104
|
-
expect(stopBtn).toBeInTheDocument();
|
|
105
|
-
fireEvent.click(stopBtn);
|
|
106
|
-
expect(cancelTurn).toHaveBeenCalled();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('ESC key calls cancelTurn when running', () => {
|
|
110
|
-
const detail = makeDetail('s1', [], { is_running: 1 });
|
|
111
|
-
resetStore({ activeSessionId: 's1', sessionDetails: { s1: detail }, sessions: [makeSession('s1')] });
|
|
112
|
-
const cancelTurn = vi.fn().mockResolvedValue(undefined);
|
|
113
|
-
useStore.setState({ cancelTurn });
|
|
114
|
-
|
|
115
|
-
render(<CommandInputBar />);
|
|
116
|
-
fireEvent.keyDown(window, { key: 'Escape' });
|
|
117
|
-
expect(cancelTurn).toHaveBeenCalled();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('restoredPrompt sets textarea value and clears store', () => {
|
|
121
|
-
const detail = makeDetail('s1', []);
|
|
122
|
-
resetStore({
|
|
123
|
-
activeSessionId: 's1',
|
|
124
|
-
sessionDetails: { s1: detail },
|
|
125
|
-
sessions: [makeSession('s1')],
|
|
126
|
-
restoredPrompt: 'restored text',
|
|
127
|
-
});
|
|
128
|
-
render(<CommandInputBar />);
|
|
129
|
-
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
|
|
130
|
-
expect(textarea.value).toBe('restored text');
|
|
131
|
-
expect(useStore.getState().restoredPrompt).toBeNull();
|
|
132
|
-
});
|
|
133
|
-
});
|