repoburg 1.3.201 → 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 +2714 -32395
- package/agent-pane/backend/dist/server.js +45304 -0
- package/askuser-after-step5-postgres-selected.png +0 -0
- package/askuser-after-step7-answer-sent.png +0 -0
- package/askuser-card-visible.png +0 -0
- package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.d.ts +16 -0
- package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.js +87 -0
- package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.js.map +1 -1
- package/backend/dist/src/agent-runtime/agent-runtime.types.d.ts +16 -1
- package/backend/dist/src/app.controller.d.ts +1 -0
- package/backend/dist/src/app.service.d.ts +1 -0
- package/backend/dist/src/app.service.js +4 -1
- package/backend/dist/src/app.service.js.map +1 -1
- package/backend/dist/src/interactive-chat/chat.service.d.ts +4 -0
- package/backend/dist/src/interactive-chat/chat.service.js +21 -1
- package/backend/dist/src/interactive-chat/chat.service.js.map +1 -1
- package/backend/dist/src/llm-orchestration/action-handlers/invoke-subagent.handler.js +12 -1
- package/backend/dist/src/llm-orchestration/action-handlers/invoke-subagent.handler.js.map +1 -1
- package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.d.ts +1 -0
- package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.js +1 -0
- package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.js.map +1 -1
- package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js +2 -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/api/services.js +4 -0
- package/daemon/dist/index.js +28 -8
- package/daemon/dist/serviceManager.js +30 -11
- package/daemon/dist/serviceManager.spec.js +26 -0
- package/daemon/dist/workspace-validation.js +63 -0
- package/daemon/dist/workspace-validation.spec.js +63 -0
- package/daemon/package.json +1 -1
- package/package.json +15 -2
- package/r2-step4-apple-banana.png +0 -0
- package/r2-step5-after-rewind-apple-last.png +0 -0
- package/r2-step7-apple-cherry.png +0 -0
- package/r2-step8-cherry-menu-no-restore.png +0 -0
- package/r3-step3-live-task-card-running.png +0 -0
- package/r3-step4-after-completion.png +0 -0
- package/r3-step5-persistence-after-reload.png +0 -0
- package/r3-step6-tasks-tab-card.png +0 -0
- package/reverify-answered-state-answer-sent.png +0 -0
- package/reverify-card-visible-after-completion.png +0 -0
- package/rewind-step3-apple-banana-turns.png +0 -0
- package/rewind-step5-ui-unchanged-after-confirmed-rewind.png +0 -0
- package/rewind-step6-apple-menu-no-restore.png +0 -0
- package/rewind-step7-final-turnlist-unchanged.png +0 -0
- package/rewind-step7-negative-error-toast.png +0 -0
- package/rewind-step7-negative-error-toast2.png +0 -0
- package/subagent-r2-step5-turn-after-reload-no-card.png +0 -0
- package/subagent-r2-step6-tasks-panel-card.png +0 -0
- package/subagent-step3-tasks-tab-done-card.png +0 -0
- package/subagent-step4-turn-stuck-no-cards.png +0 -0
- 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 -230
- 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 -43
- package/agent-pane/backend/src/repoburg/action-mapping.ts +0 -216
- package/agent-pane/backend/src/repoburg/agent-runtime-client.test.ts +0 -271
- package/agent-pane/backend/src/repoburg/agent-runtime-client.ts +0 -262
- package/agent-pane/backend/src/repoburg/backend-resolver.test.ts +0 -271
- package/agent-pane/backend/src/repoburg/backend-resolver.ts +0 -246
- package/agent-pane/backend/src/repoburg/repoburg-client.test.ts +0 -159
- package/agent-pane/backend/src/repoburg/repoburg-client.ts +0 -181
- package/agent-pane/backend/src/repoburg/repoburg-provider.test.ts +0 -723
- package/agent-pane/backend/src/repoburg/repoburg-provider.ts +0 -422
- package/agent-pane/backend/src/repoburg/turn-projector.test.ts +0 -475
- package/agent-pane/backend/src/repoburg/turn-projector.ts +0 -537
- 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 -558
- package/agent-pane/backend/src/routes/sessions.ts +0 -504
- 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-34WaZYsW.css +0 -1
- package/agent-pane/frontend/dist/assets/index-y7xlT86e.js +0 -362
- 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 -245
- 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 -238
- 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/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 -298
- package/agent-pane/frontend/src/components/TurnContent.tsx +0 -548
- 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 -83
- package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +0 -126
- 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 -1319
- 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 -361
- package/agent-pane/frontend/src/styles/app.css +0 -611
- 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 -100
- 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 -132
- 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 -75
- 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 -167
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { connectWebSocket, disconnectWebSocket } from './websocket.ts';
|
|
3
|
-
import type { Item, TokenUsage } from '../types/index.ts';
|
|
4
|
-
|
|
5
|
-
// Mock the store to capture state dispatches
|
|
6
|
-
vi.mock('../store/store.ts', () => {
|
|
7
|
-
const handlers: Record<string, (...args: unknown[]) => void> = {};
|
|
8
|
-
return {
|
|
9
|
-
useStore: {
|
|
10
|
-
getState: () => handlers,
|
|
11
|
-
setState: vi.fn(),
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// We need to dynamically set the handlers on the mock
|
|
17
|
-
import { useStore } from '../store/store.ts';
|
|
18
|
-
|
|
19
|
-
const NOW = '2025-01-01T00:00:00Z';
|
|
20
|
-
|
|
21
|
-
// Type-safe accessor for the mock store state
|
|
22
|
-
function getMockStore() {
|
|
23
|
-
return useStore.getState() as unknown as Record<string, ReturnType<typeof vi.fn>>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Reset the mock store to have all the actions as vi.fn()
|
|
27
|
-
function setupMockStore() {
|
|
28
|
-
const store = getMockStore();
|
|
29
|
-
const actions = [
|
|
30
|
-
'setWsConnected',
|
|
31
|
-
'addLogEntry',
|
|
32
|
-
'refreshActiveSession',
|
|
33
|
-
'addToast',
|
|
34
|
-
'onTurnStarted',
|
|
35
|
-
'onItemStarted',
|
|
36
|
-
'onItemUpdated',
|
|
37
|
-
'onItemCompleted',
|
|
38
|
-
'onItemDelta',
|
|
39
|
-
'onTurnCompleted',
|
|
40
|
-
'onTokenUsageUpdated',
|
|
41
|
-
'onTurnFailed',
|
|
42
|
-
'onTurnCancelled',
|
|
43
|
-
'onSessionTitle',
|
|
44
|
-
];
|
|
45
|
-
for (const action of actions) {
|
|
46
|
-
store[action] = vi.fn();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Mock WebSocket class
|
|
51
|
-
class MockWebSocket {
|
|
52
|
-
static instances: MockWebSocket[] = [];
|
|
53
|
-
static lastInstance: MockWebSocket | null = null;
|
|
54
|
-
|
|
55
|
-
static readonly OPEN = 1;
|
|
56
|
-
static readonly CONNECTING = 0;
|
|
57
|
-
static readonly CLOSED = 3;
|
|
58
|
-
|
|
59
|
-
readyState = MockWebSocket.CONNECTING;
|
|
60
|
-
onopen: (() => void) | null = null;
|
|
61
|
-
onclose: (() => void) | null = null;
|
|
62
|
-
onerror: (() => void) | null = null;
|
|
63
|
-
onmessage: ((event: { data: string }) => void) | null = null;
|
|
64
|
-
|
|
65
|
-
constructor(public url: string) {
|
|
66
|
-
MockWebSocket.instances.push(this);
|
|
67
|
-
MockWebSocket.lastInstance = this;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
close() {
|
|
71
|
-
this.readyState = MockWebSocket.CLOSED;
|
|
72
|
-
if (this.onclose) this.onclose();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
describe('connectWebSocket', () => {
|
|
77
|
-
beforeEach(() => {
|
|
78
|
-
vi.clearAllMocks();
|
|
79
|
-
MockWebSocket.instances = [];
|
|
80
|
-
MockWebSocket.lastInstance = null;
|
|
81
|
-
setupMockStore();
|
|
82
|
-
vi.stubGlobal('WebSocket', MockWebSocket);
|
|
83
|
-
// Reset module-level state by calling disconnect first
|
|
84
|
-
disconnectWebSocket();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
afterEach(() => {
|
|
88
|
-
disconnectWebSocket();
|
|
89
|
-
vi.unstubAllGlobals();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('creates a new WebSocket connection', () => {
|
|
93
|
-
connectWebSocket();
|
|
94
|
-
expect(MockWebSocket.lastInstance).not.toBeNull();
|
|
95
|
-
expect(MockWebSocket.lastInstance!.url).toContain('/ws');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('does not create duplicate connection when already connecting', () => {
|
|
99
|
-
connectWebSocket();
|
|
100
|
-
connectWebSocket();
|
|
101
|
-
expect(MockWebSocket.instances).toHaveLength(1);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('calls setWsConnected(true) and addLogEntry on open', () => {
|
|
105
|
-
connectWebSocket();
|
|
106
|
-
const ws = MockWebSocket.lastInstance!;
|
|
107
|
-
ws.readyState = MockWebSocket.OPEN;
|
|
108
|
-
ws.onopen!();
|
|
109
|
-
|
|
110
|
-
const store = getMockStore();
|
|
111
|
-
expect(store.setWsConnected).toHaveBeenCalledWith(true);
|
|
112
|
-
expect(store.addLogEntry).toHaveBeenCalledWith('INFO', 'WebSocket connected');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('does not call refreshActiveSession on first connect', () => {
|
|
116
|
-
connectWebSocket();
|
|
117
|
-
const ws = MockWebSocket.lastInstance!;
|
|
118
|
-
ws.readyState = MockWebSocket.OPEN;
|
|
119
|
-
ws.onopen!();
|
|
120
|
-
|
|
121
|
-
const store = getMockStore();
|
|
122
|
-
expect(store.refreshActiveSession).not.toHaveBeenCalled();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('calls setWsConnected(false) on close', () => {
|
|
126
|
-
connectWebSocket();
|
|
127
|
-
const ws = MockWebSocket.lastInstance!;
|
|
128
|
-
ws.onclose!();
|
|
129
|
-
|
|
130
|
-
const store = getMockStore();
|
|
131
|
-
expect(store.setWsConnected).toHaveBeenCalledWith(false);
|
|
132
|
-
expect(store.addLogEntry).toHaveBeenCalledWith('WARN', 'WebSocket disconnected');
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('disconnectWebSocket', () => {
|
|
137
|
-
beforeEach(() => {
|
|
138
|
-
vi.clearAllMocks();
|
|
139
|
-
MockWebSocket.instances = [];
|
|
140
|
-
MockWebSocket.lastInstance = null;
|
|
141
|
-
setupMockStore();
|
|
142
|
-
vi.stubGlobal('WebSocket', MockWebSocket);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
afterEach(() => {
|
|
146
|
-
vi.unstubAllGlobals();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('closes existing connection and clears handlers', () => {
|
|
150
|
-
connectWebSocket();
|
|
151
|
-
const ws = MockWebSocket.lastInstance!;
|
|
152
|
-
const closeSpy = vi.spyOn(ws, 'close');
|
|
153
|
-
|
|
154
|
-
disconnectWebSocket();
|
|
155
|
-
|
|
156
|
-
expect(closeSpy).toHaveBeenCalled();
|
|
157
|
-
expect(ws.onclose).toBeNull(); // prevents reconnect on intentional close
|
|
158
|
-
const store = getMockStore();
|
|
159
|
-
expect(store.setWsConnected).toHaveBeenCalledWith(false);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('does nothing when no connection exists', () => {
|
|
163
|
-
disconnectWebSocket();
|
|
164
|
-
// Should not throw
|
|
165
|
-
expect(MockWebSocket.instances).toHaveLength(0);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('WebSocket event dispatch', () => {
|
|
170
|
-
beforeEach(() => {
|
|
171
|
-
vi.clearAllMocks();
|
|
172
|
-
MockWebSocket.instances = [];
|
|
173
|
-
MockWebSocket.lastInstance = null;
|
|
174
|
-
setupMockStore();
|
|
175
|
-
vi.stubGlobal('WebSocket', MockWebSocket);
|
|
176
|
-
connectWebSocket();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
afterEach(() => {
|
|
180
|
-
disconnectWebSocket();
|
|
181
|
-
vi.unstubAllGlobals();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
function sendMessage(data: unknown) {
|
|
185
|
-
const ws = MockWebSocket.lastInstance!;
|
|
186
|
-
ws.onmessage!({ data: JSON.stringify(data) });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
it('dispatches turn:started event', () => {
|
|
190
|
-
sendMessage({ event: 'turn:started', data: { sessionId: 's1', turnId: 't1' } });
|
|
191
|
-
const store = getMockStore();
|
|
192
|
-
expect(store.onTurnStarted).toHaveBeenCalledWith('s1', 't1');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('dispatches item:started event', () => {
|
|
196
|
-
const item: Item = {
|
|
197
|
-
id: 'i1', turn_id: 't1', external_item_id: 'e1', type: 'agent_message',
|
|
198
|
-
data: { text: 'hello' }, status: 'in_progress', created_at: NOW, updated_at: NOW,
|
|
199
|
-
};
|
|
200
|
-
sendMessage({ event: 'item:started', data: { sessionId: 's1', turnId: 't1', item } });
|
|
201
|
-
const store = getMockStore();
|
|
202
|
-
expect(store.onItemStarted).toHaveBeenCalledWith('s1', 't1', item);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('dispatches item:updated event', () => {
|
|
206
|
-
const item: Item = {
|
|
207
|
-
id: 'i1', turn_id: 't1', external_item_id: 'e1', type: 'command_execution',
|
|
208
|
-
data: { command: 'ls' }, status: 'in_progress', created_at: NOW, updated_at: NOW,
|
|
209
|
-
};
|
|
210
|
-
sendMessage({ event: 'item:updated', data: { sessionId: 's1', turnId: 't1', item } });
|
|
211
|
-
const store = getMockStore();
|
|
212
|
-
expect(store.onItemUpdated).toHaveBeenCalledWith('s1', 't1', item);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('dispatches item:completed event', () => {
|
|
216
|
-
const item: Item = {
|
|
217
|
-
id: 'i1', turn_id: 't1', external_item_id: 'e1', type: 'agent_message',
|
|
218
|
-
data: { text: 'done' }, status: 'completed', created_at: NOW, updated_at: NOW,
|
|
219
|
-
};
|
|
220
|
-
sendMessage({ event: 'item:completed', data: { sessionId: 's1', turnId: 't1', item } });
|
|
221
|
-
const store = getMockStore();
|
|
222
|
-
expect(store.onItemCompleted).toHaveBeenCalledWith('s1', 't1', item);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('dispatches item:delta event', () => {
|
|
226
|
-
sendMessage({ event: 'item:delta', data: { sessionId: 's1', turnId: 't1', itemId: 'ext-i1', delta: 'Hello' } });
|
|
227
|
-
const store = getMockStore();
|
|
228
|
-
expect(store.onItemDelta).toHaveBeenCalledWith('s1', 't1', 'ext-i1', 'Hello');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('dispatches turn:tokenUsage event', () => {
|
|
232
|
-
const usage: TokenUsage = {
|
|
233
|
-
input_tokens: 100, cached_input_tokens: 0, cache_write_input_tokens: 0,
|
|
234
|
-
output_tokens: 200, reasoning_output_tokens: 0,
|
|
235
|
-
};
|
|
236
|
-
sendMessage({ event: 'turn:tokenUsage', data: { sessionId: 's1', turnId: 't1', usage } });
|
|
237
|
-
const store = getMockStore();
|
|
238
|
-
expect(store.onTokenUsageUpdated).toHaveBeenCalledWith('s1', 't1', usage);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('dispatches turn:completed event with usage', () => {
|
|
242
|
-
const usage: TokenUsage = {
|
|
243
|
-
input_tokens: 100, cached_input_tokens: 0, cache_write_input_tokens: 0,
|
|
244
|
-
output_tokens: 200, reasoning_output_tokens: 0,
|
|
245
|
-
};
|
|
246
|
-
sendMessage({ event: 'turn:completed', data: { sessionId: 's1', turnId: 't1', usage, hasUnreviewed: true } });
|
|
247
|
-
const store = getMockStore();
|
|
248
|
-
expect(store.onTurnCompleted).toHaveBeenCalledWith('s1', 't1', usage, true);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('dispatches turn:completed event without usage (real app-server)', () => {
|
|
252
|
-
sendMessage({ event: 'turn:completed', data: { sessionId: 's1', turnId: 't1', hasUnreviewed: true } });
|
|
253
|
-
const store = getMockStore();
|
|
254
|
-
expect(store.onTurnCompleted).toHaveBeenCalledWith('s1', 't1', undefined, true);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('dispatches turn:failed event with error', () => {
|
|
258
|
-
sendMessage({ event: 'turn:failed', data: { sessionId: 's1', turnId: 't1', error: 'crashed' } });
|
|
259
|
-
const store = getMockStore();
|
|
260
|
-
expect(store.onTurnFailed).toHaveBeenCalledWith('s1', 't1', 'crashed');
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('dispatches turn:cancelled event', () => {
|
|
264
|
-
sendMessage({ event: 'turn:cancelled', data: { sessionId: 's1', turnId: 't1' } });
|
|
265
|
-
const store = getMockStore();
|
|
266
|
-
expect(store.onTurnCancelled).toHaveBeenCalledWith('s1', 't1');
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('dispatches session:title event', () => {
|
|
270
|
-
sendMessage({ event: 'session:title', data: { sessionId: 's1', title: 'New Title' } });
|
|
271
|
-
const store = getMockStore();
|
|
272
|
-
expect(store.onSessionTitle).toHaveBeenCalledWith('s1', 'New Title');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('ignores malformed JSON without error', () => {
|
|
276
|
-
const ws = MockWebSocket.lastInstance!;
|
|
277
|
-
// Should not throw
|
|
278
|
-
ws.onmessage!({ data: 'not json' });
|
|
279
|
-
const store = getMockStore();
|
|
280
|
-
expect(store.onTurnStarted).not.toHaveBeenCalled();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('ignores unknown event types', () => {
|
|
284
|
-
sendMessage({ event: 'unknown:event', data: {} });
|
|
285
|
-
const store = getMockStore();
|
|
286
|
-
expect(store.onTurnStarted).not.toHaveBeenCalled();
|
|
287
|
-
});
|
|
288
|
-
});
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { useStore } from '../store/store.ts';
|
|
2
|
-
import type { Item, TokenUsage, Turn, DiscussionThread, ContextUsage } from '../types/index.ts';
|
|
3
|
-
|
|
4
|
-
type WsEventHandler = (data: unknown) => void;
|
|
5
|
-
|
|
6
|
-
let ws: WebSocket | null = null;
|
|
7
|
-
let reconnectTimer: number | null = null;
|
|
8
|
-
let reconnectDelay = 1000;
|
|
9
|
-
const MAX_RECONNECT_DELAY = 10000;
|
|
10
|
-
|
|
11
|
-
/** Build the WebSocket URL from the current page location. */
|
|
12
|
-
function buildWsUrl(): string {
|
|
13
|
-
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
14
|
-
return `${proto}//${window.location.host}/ws`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Connect to the backend WebSocket server. Dispatches events to store actions. */
|
|
18
|
-
export function connectWebSocket(): void {
|
|
19
|
-
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
ws = new WebSocket(buildWsUrl());
|
|
24
|
-
|
|
25
|
-
ws.onopen = () => {
|
|
26
|
-
const wasDisconnected = reconnectDelay > 1000;
|
|
27
|
-
reconnectDelay = 1000; // reset backoff
|
|
28
|
-
useStore.getState().setWsConnected(true);
|
|
29
|
-
useStore.getState().addLogEntry('INFO', 'WebSocket connected');
|
|
30
|
-
|
|
31
|
-
// On reconnect (not initial connect): reconcile active session
|
|
32
|
-
if (wasDisconnected) {
|
|
33
|
-
const state = useStore.getState();
|
|
34
|
-
if (state.activeSessionId) {
|
|
35
|
-
state.refreshActiveSession();
|
|
36
|
-
state.addToast('info', 'WebSocket reconnected — session state reconciled');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
ws.onclose = () => {
|
|
42
|
-
useStore.getState().setWsConnected(false);
|
|
43
|
-
useStore.getState().addLogEntry('WARN', 'WebSocket disconnected');
|
|
44
|
-
useStore.getState().addToast('warn', 'WebSocket disconnected — reconnecting…');
|
|
45
|
-
scheduleReconnect();
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
ws.onerror = () => {
|
|
49
|
-
// onclose will fire after onerror
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
ws.onmessage = (event: MessageEvent) => {
|
|
53
|
-
try {
|
|
54
|
-
const msg = JSON.parse(event.data as string);
|
|
55
|
-
const handler = HANDLERS[msg.event as string];
|
|
56
|
-
if (handler) {
|
|
57
|
-
handler(msg.data);
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// Ignore malformed messages
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Disconnect the WebSocket (used on unmount). */
|
|
66
|
-
export function disconnectWebSocket(): void {
|
|
67
|
-
if (reconnectTimer) {
|
|
68
|
-
clearTimeout(reconnectTimer);
|
|
69
|
-
reconnectTimer = null;
|
|
70
|
-
}
|
|
71
|
-
if (ws) {
|
|
72
|
-
ws.onclose = null; // prevent reconnect on intentional close
|
|
73
|
-
ws.close();
|
|
74
|
-
ws = null;
|
|
75
|
-
}
|
|
76
|
-
useStore.getState().setWsConnected(false);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Schedule a reconnect with exponential backoff. */
|
|
80
|
-
function scheduleReconnect(): void {
|
|
81
|
-
if (reconnectTimer) return;
|
|
82
|
-
reconnectTimer = window.setTimeout(() => {
|
|
83
|
-
reconnectTimer = null;
|
|
84
|
-
connectWebSocket();
|
|
85
|
-
}, reconnectDelay);
|
|
86
|
-
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// --- Event handlers — dispatch to store actions ---
|
|
90
|
-
|
|
91
|
-
interface TurnEventPayload {
|
|
92
|
-
sessionId: string;
|
|
93
|
-
turnId: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
interface ItemEventPayload {
|
|
97
|
-
sessionId: string;
|
|
98
|
-
turnId: string;
|
|
99
|
-
item: Item;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
interface TurnCompletedPayload {
|
|
103
|
-
sessionId: string;
|
|
104
|
-
turnId: string;
|
|
105
|
-
usage?: TokenUsage;
|
|
106
|
-
hasUnreviewed?: boolean;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
interface ItemDeltaPayload {
|
|
110
|
-
sessionId: string;
|
|
111
|
-
turnId: string;
|
|
112
|
-
itemId: string;
|
|
113
|
-
delta: string;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
interface TokenUsageUpdatedPayload {
|
|
117
|
-
sessionId: string;
|
|
118
|
-
turnId: string;
|
|
119
|
-
usage: TokenUsage;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
interface ContextUsagePayload {
|
|
123
|
-
sessionId: string;
|
|
124
|
-
totalTokens: number;
|
|
125
|
-
contextWindow: number;
|
|
126
|
-
percentage: number;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
interface TurnFailedPayload {
|
|
130
|
-
sessionId: string;
|
|
131
|
-
turnId: string;
|
|
132
|
-
error: string;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
interface SessionTitlePayload {
|
|
136
|
-
sessionId: string;
|
|
137
|
-
title: string;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
interface BackgroundTasksPayload {
|
|
141
|
-
sessionId: string;
|
|
142
|
-
tasks: Array<{ task_id: string; task_type: string; description: string }>;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
interface ThreadCreatedPayload {
|
|
146
|
-
sessionId: string;
|
|
147
|
-
thread: DiscussionThread;
|
|
148
|
-
turn: Turn;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
interface ThreadFollowupPayload {
|
|
152
|
-
sessionId: string;
|
|
153
|
-
threadId: string;
|
|
154
|
-
turn: Turn;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
interface ThreadResolvedPayload {
|
|
158
|
-
sessionId: string;
|
|
159
|
-
threadId: string;
|
|
160
|
-
status: 'open' | 'resolved';
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
interface ThreadDeletedPayload {
|
|
164
|
-
sessionId: string;
|
|
165
|
-
threadId: string;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const HANDLERS: Record<string, WsEventHandler> = {
|
|
169
|
-
'turn:started': (data) => {
|
|
170
|
-
const p = data as TurnEventPayload;
|
|
171
|
-
useStore.getState().onTurnStarted(p.sessionId, p.turnId);
|
|
172
|
-
},
|
|
173
|
-
'item:started': (data) => {
|
|
174
|
-
const p = data as ItemEventPayload;
|
|
175
|
-
useStore.getState().onItemStarted(p.sessionId, p.turnId, p.item);
|
|
176
|
-
},
|
|
177
|
-
'item:updated': (data) => {
|
|
178
|
-
const p = data as ItemEventPayload;
|
|
179
|
-
useStore.getState().onItemUpdated(p.sessionId, p.turnId, p.item);
|
|
180
|
-
},
|
|
181
|
-
'item:completed': (data) => {
|
|
182
|
-
const p = data as ItemEventPayload;
|
|
183
|
-
useStore.getState().onItemCompleted(p.sessionId, p.turnId, p.item);
|
|
184
|
-
},
|
|
185
|
-
'item:delta': (data) => {
|
|
186
|
-
const p = data as ItemDeltaPayload;
|
|
187
|
-
useStore.getState().onItemDelta(p.sessionId, p.turnId, p.itemId, p.delta);
|
|
188
|
-
},
|
|
189
|
-
'turn:completed': (data) => {
|
|
190
|
-
const p = data as TurnCompletedPayload;
|
|
191
|
-
useStore.getState().onTurnCompleted(p.sessionId, p.turnId, p.usage, p.hasUnreviewed);
|
|
192
|
-
},
|
|
193
|
-
'turn:tokenUsage': (data) => {
|
|
194
|
-
const p = data as TokenUsageUpdatedPayload;
|
|
195
|
-
useStore.getState().onTokenUsageUpdated(p.sessionId, p.turnId, p.usage);
|
|
196
|
-
},
|
|
197
|
-
'turn:contextUsage': (data) => {
|
|
198
|
-
const p = data as ContextUsagePayload;
|
|
199
|
-
const ctx: ContextUsage = {
|
|
200
|
-
totalTokens: p.totalTokens,
|
|
201
|
-
contextWindow: p.contextWindow,
|
|
202
|
-
percentage: p.percentage,
|
|
203
|
-
};
|
|
204
|
-
useStore.getState().onContextUsageUpdated(p.sessionId, ctx);
|
|
205
|
-
},
|
|
206
|
-
'turn:failed': (data) => {
|
|
207
|
-
const p = data as TurnFailedPayload;
|
|
208
|
-
useStore.getState().onTurnFailed(p.sessionId, p.turnId, p.error);
|
|
209
|
-
},
|
|
210
|
-
'turn:cancelled': (data) => {
|
|
211
|
-
const p = data as TurnEventPayload;
|
|
212
|
-
useStore.getState().onTurnCancelled(p.sessionId, p.turnId);
|
|
213
|
-
},
|
|
214
|
-
'session:title': (data) => {
|
|
215
|
-
const p = data as SessionTitlePayload;
|
|
216
|
-
useStore.getState().onSessionTitle(p.sessionId, p.title);
|
|
217
|
-
},
|
|
218
|
-
'background_tasks': (data) => {
|
|
219
|
-
const p = data as BackgroundTasksPayload;
|
|
220
|
-
useStore.getState().onBackgroundTasksChanged(p.sessionId, p.tasks);
|
|
221
|
-
},
|
|
222
|
-
'thread:created': (data) => {
|
|
223
|
-
const p = data as ThreadCreatedPayload;
|
|
224
|
-
useStore.getState().onThreadCreated(p.sessionId, p.thread, p.turn);
|
|
225
|
-
},
|
|
226
|
-
'thread:followup': (data) => {
|
|
227
|
-
const p = data as ThreadFollowupPayload;
|
|
228
|
-
useStore.getState().onThreadFollowup(p.sessionId, p.threadId, p.turn);
|
|
229
|
-
},
|
|
230
|
-
'thread:resolved': (data) => {
|
|
231
|
-
const p = data as ThreadResolvedPayload;
|
|
232
|
-
useStore.getState().onThreadResolved(p.sessionId, p.threadId, p.status);
|
|
233
|
-
},
|
|
234
|
-
'thread:deleted': (data) => {
|
|
235
|
-
const p = data as ThreadDeletedPayload;
|
|
236
|
-
useStore.getState().onThreadDeleted(p.sessionId, p.threadId);
|
|
237
|
-
},
|
|
238
|
-
};
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Mock the client module to capture call signatures without network access
|
|
4
|
-
vi.mock('./client.ts', () => ({
|
|
5
|
-
apiGet: vi.fn(),
|
|
6
|
-
apiPost: vi.fn(),
|
|
7
|
-
apiDelete: vi.fn(),
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
import { apiGet, apiPost, apiDelete } from './client.ts';
|
|
11
|
-
import {
|
|
12
|
-
fetchWorkspaces,
|
|
13
|
-
createWorkspace,
|
|
14
|
-
hideWorkspace,
|
|
15
|
-
deleteWorkspace,
|
|
16
|
-
pickDirectory,
|
|
17
|
-
} from './workspaces.ts';
|
|
18
|
-
import type { Workspace } from '../types/index.ts';
|
|
19
|
-
|
|
20
|
-
const NOW = '2025-01-01T00:00:00Z';
|
|
21
|
-
|
|
22
|
-
function makeWorkspace(overrides: Partial<Workspace> = {}): Workspace {
|
|
23
|
-
return {
|
|
24
|
-
id: 'w1',
|
|
25
|
-
path: '/test/path',
|
|
26
|
-
label: null,
|
|
27
|
-
deleted: 0,
|
|
28
|
-
created_at: NOW,
|
|
29
|
-
...overrides,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('fetchWorkspaces', () => {
|
|
34
|
-
beforeEach(() => vi.clearAllMocks());
|
|
35
|
-
|
|
36
|
-
it('calls apiGet with /workspaces', async () => {
|
|
37
|
-
const workspaces = [makeWorkspace()];
|
|
38
|
-
vi.mocked(apiGet).mockResolvedValue(workspaces);
|
|
39
|
-
const result = await fetchWorkspaces();
|
|
40
|
-
expect(apiGet).toHaveBeenCalledWith('/workspaces');
|
|
41
|
-
expect(result).toEqual(workspaces);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe('createWorkspace', () => {
|
|
46
|
-
beforeEach(() => vi.clearAllMocks());
|
|
47
|
-
|
|
48
|
-
it('calls apiPost with path', async () => {
|
|
49
|
-
const ws = makeWorkspace({ path: '/new' });
|
|
50
|
-
vi.mocked(apiPost).mockResolvedValue(ws);
|
|
51
|
-
await createWorkspace('/new');
|
|
52
|
-
expect(apiPost).toHaveBeenCalledWith('/workspaces', { path: '/new', label: undefined });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('passes label when provided', async () => {
|
|
56
|
-
const ws = makeWorkspace({ path: '/new', label: 'My Project' });
|
|
57
|
-
vi.mocked(apiPost).mockResolvedValue(ws);
|
|
58
|
-
await createWorkspace('/new', 'My Project');
|
|
59
|
-
expect(apiPost).toHaveBeenCalledWith('/workspaces', { path: '/new', label: 'My Project' });
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('hideWorkspace', () => {
|
|
64
|
-
beforeEach(() => vi.clearAllMocks());
|
|
65
|
-
|
|
66
|
-
it('calls apiPost with path on /workspaces/hide', async () => {
|
|
67
|
-
vi.mocked(apiPost).mockResolvedValue({ success: true });
|
|
68
|
-
await hideWorkspace('/path');
|
|
69
|
-
expect(apiPost).toHaveBeenCalledWith('/workspaces/hide', { path: '/path' });
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('deleteWorkspace', () => {
|
|
74
|
-
beforeEach(() => vi.clearAllMocks());
|
|
75
|
-
|
|
76
|
-
it('calls apiDelete with /workspaces/:id', async () => {
|
|
77
|
-
vi.mocked(apiDelete).mockResolvedValue({ success: true });
|
|
78
|
-
await deleteWorkspace('w1');
|
|
79
|
-
expect(apiDelete).toHaveBeenCalledWith('/workspaces/w1');
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('pickDirectory', () => {
|
|
84
|
-
beforeEach(() => {
|
|
85
|
-
vi.clearAllMocks();
|
|
86
|
-
global.fetch = vi.fn();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('returns cancelled when status is 204', async () => {
|
|
90
|
-
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({ status: 204 });
|
|
91
|
-
const result = await pickDirectory();
|
|
92
|
-
expect(result).toEqual({ cancelled: true });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('returns path on ok response', async () => {
|
|
96
|
-
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
97
|
-
ok: true,
|
|
98
|
-
json: () => Promise.resolve({ path: '/selected/dir' }),
|
|
99
|
-
});
|
|
100
|
-
const result = await pickDirectory();
|
|
101
|
-
expect(result).toEqual({ path: '/selected/dir' });
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('throws on error response', async () => {
|
|
105
|
-
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
106
|
-
ok: false,
|
|
107
|
-
status: 500,
|
|
108
|
-
statusText: 'Internal Server Error',
|
|
109
|
-
});
|
|
110
|
-
await expect(pickDirectory()).rejects.toThrow('GET /api/pick-directory failed: 500 Internal Server Error');
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { apiGet, apiPost, apiDelete } from './client.ts';
|
|
2
|
-
import type { Workspace } from '../types/index.ts';
|
|
3
|
-
|
|
4
|
-
interface PickDirectoryResult {
|
|
5
|
-
path?: string;
|
|
6
|
-
cancelled?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Fetch all workspaces (active and soft-deleted), ordered by creation date. */
|
|
10
|
-
export function fetchWorkspaces(): Promise<Workspace[]> {
|
|
11
|
-
return apiGet<Workspace[]>('/workspaces');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Create a new workspace at the given path, or restore a soft-deleted one. */
|
|
15
|
-
export function createWorkspace(path: string, label?: string): Promise<Workspace> {
|
|
16
|
-
return apiPost<Workspace>('/workspaces', { path, label });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Soft-delete (hide) a workspace by path. Sessions keep their cwd. */
|
|
20
|
-
export function hideWorkspace(path: string): Promise<{ success: boolean }> {
|
|
21
|
-
return apiPost<{ success: boolean }>('/workspaces/hide', { path });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Soft-delete a workspace by ID. Does NOT delete sessions. */
|
|
25
|
-
export function deleteWorkspace(id: string): Promise<{ success: boolean }> {
|
|
26
|
-
return apiDelete<{ success: boolean }>(`/workspaces/${id}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Open the native OS folder picker dialog (macOS only via osascript).
|
|
30
|
-
* @returns `{ path }` on success, or `{ cancelled: true }` if the user dismissed the dialog. */
|
|
31
|
-
export async function pickDirectory(): Promise<PickDirectoryResult> {
|
|
32
|
-
const res = await fetch('/api/pick-directory');
|
|
33
|
-
if (res.status === 204) return { cancelled: true };
|
|
34
|
-
if (!res.ok) {
|
|
35
|
-
throw new Error(`GET /api/pick-directory failed: ${res.status} ${res.statusText}`);
|
|
36
|
-
}
|
|
37
|
-
return res.json() as Promise<PickDirectoryResult>;
|
|
38
|
-
}
|