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,197 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import Fastify, { type FastifyInstance } from 'fastify';
|
|
3
|
-
import { initDb, getDb } from '../db/index.ts';
|
|
4
|
-
import { registerWorkspaceRoutes } from './workspaces.ts';
|
|
5
|
-
import { execFile } from 'node:child_process';
|
|
6
|
-
|
|
7
|
-
// Mock child_process to avoid spawning osascript in tests
|
|
8
|
-
vi.mock('node:child_process', () => ({
|
|
9
|
-
execFile: vi.fn(),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
const NOW = '2025-01-01T00:00:00Z';
|
|
13
|
-
|
|
14
|
-
function insertWorkspace(
|
|
15
|
-
db: ReturnType<typeof getDb>,
|
|
16
|
-
id: string,
|
|
17
|
-
path: string,
|
|
18
|
-
overrides: Record<string, unknown> = {},
|
|
19
|
-
): void {
|
|
20
|
-
db.prepare('INSERT INTO workspaces (id, path, label, deleted, created_at) VALUES (?, ?, ?, ?, ?)').run(
|
|
21
|
-
id,
|
|
22
|
-
path,
|
|
23
|
-
(overrides['label'] as string | null) ?? null,
|
|
24
|
-
(overrides['deleted'] as number) ?? 0,
|
|
25
|
-
(overrides['created_at'] as string) ?? NOW,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Configure execFile mock to call callback with success.
|
|
30
|
-
* Passes an object as the first result arg so promisify resolves with { stdout, stderr }.
|
|
31
|
-
*/
|
|
32
|
-
function mockExecFileSuccess(stdout: string): void {
|
|
33
|
-
const mock = execFile as unknown as ReturnType<typeof vi.fn>;
|
|
34
|
-
mock.mockImplementation((...args: unknown[]) => {
|
|
35
|
-
const cb = args[args.length - 1] as ((err: Error | null, ...results: unknown[]) => void) | undefined;
|
|
36
|
-
if (typeof cb === 'function') cb(null, { stdout, stderr: '' });
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Configure execFile mock to call callback with error (simulates user cancel). */
|
|
41
|
-
function mockExecFileError(error: Error): void {
|
|
42
|
-
const mock = execFile as unknown as ReturnType<typeof vi.fn>;
|
|
43
|
-
mock.mockImplementation((...args: unknown[]) => {
|
|
44
|
-
const cb = args[args.length - 1] as ((err: Error | null, ...results: unknown[]) => void) | undefined;
|
|
45
|
-
if (typeof cb === 'function') cb(error);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('Workspace routes', () => {
|
|
50
|
-
let app: FastifyInstance;
|
|
51
|
-
|
|
52
|
-
beforeEach(async () => {
|
|
53
|
-
vi.clearAllMocks();
|
|
54
|
-
initDb(':memory:');
|
|
55
|
-
app = Fastify();
|
|
56
|
-
await registerWorkspaceRoutes(app);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('GET /api/workspaces', () => {
|
|
60
|
-
it('returns empty array when no workspaces', async () => {
|
|
61
|
-
const res = await app.inject({ method: 'GET', url: '/api/workspaces' });
|
|
62
|
-
expect(res.statusCode).toBe(200);
|
|
63
|
-
expect(res.json()).toEqual([]);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('returns all workspaces ordered by created_at ASC', async () => {
|
|
67
|
-
const db = getDb();
|
|
68
|
-
insertWorkspace(db, 'w1', '/path/a', { created_at: '2025-01-02T00:00:00Z' });
|
|
69
|
-
insertWorkspace(db, 'w2', '/path/b', { created_at: '2025-01-01T00:00:00Z' });
|
|
70
|
-
const res = await app.inject({ method: 'GET', url: '/api/workspaces' });
|
|
71
|
-
const workspaces = res.json();
|
|
72
|
-
expect(workspaces).toHaveLength(2);
|
|
73
|
-
expect(workspaces[0].id).toBe('w2');
|
|
74
|
-
expect(workspaces[1].id).toBe('w1');
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('POST /api/workspaces', () => {
|
|
79
|
-
it('returns 400 when path is missing', async () => {
|
|
80
|
-
const res = await app.inject({ method: 'POST', url: '/api/workspaces', payload: {} });
|
|
81
|
-
expect(res.statusCode).toBe(400);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns 400 when path is empty', async () => {
|
|
85
|
-
const res = await app.inject({ method: 'POST', url: '/api/workspaces', payload: { path: ' ' } });
|
|
86
|
-
expect(res.statusCode).toBe(400);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('returns 400 when path has wrong type', async () => {
|
|
90
|
-
const res = await app.inject({ method: 'POST', url: '/api/workspaces', payload: { path: { nested: true } } });
|
|
91
|
-
expect(res.statusCode).toBe(400);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('returns 409 for duplicate active workspace', async () => {
|
|
95
|
-
const db = getDb();
|
|
96
|
-
insertWorkspace(db, 'w1', '/existing/path');
|
|
97
|
-
const res = await app.inject({ method: 'POST', url: '/api/workspaces', payload: { path: '/existing/path' } });
|
|
98
|
-
expect(res.statusCode).toBe(409);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('creates a new workspace', async () => {
|
|
102
|
-
const res = await app.inject({
|
|
103
|
-
method: 'POST',
|
|
104
|
-
url: '/api/workspaces',
|
|
105
|
-
payload: { path: '/new/path', label: 'Test' },
|
|
106
|
-
});
|
|
107
|
-
expect(res.statusCode).toBe(200);
|
|
108
|
-
const ws = res.json();
|
|
109
|
-
expect(ws.id).toBeDefined();
|
|
110
|
-
expect(ws.path).toBe('/new/path');
|
|
111
|
-
expect(ws.label).toBe('Test');
|
|
112
|
-
expect(ws.deleted).toBe(0);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('restores a soft-deleted workspace', async () => {
|
|
116
|
-
const db = getDb();
|
|
117
|
-
insertWorkspace(db, 'w1', '/deleted/path', { deleted: 1, label: 'Old' });
|
|
118
|
-
const res = await app.inject({
|
|
119
|
-
method: 'POST',
|
|
120
|
-
url: '/api/workspaces',
|
|
121
|
-
payload: { path: '/deleted/path', label: 'Restored' },
|
|
122
|
-
});
|
|
123
|
-
expect(res.statusCode).toBe(200);
|
|
124
|
-
const ws = res.json();
|
|
125
|
-
expect(ws.id).toBe('w1');
|
|
126
|
-
expect(ws.deleted).toBe(0);
|
|
127
|
-
expect(ws.label).toBe('Restored');
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('POST /api/workspaces/hide', () => {
|
|
132
|
-
it('returns 400 when path is missing', async () => {
|
|
133
|
-
const res = await app.inject({ method: 'POST', url: '/api/workspaces/hide', payload: {} });
|
|
134
|
-
expect(res.statusCode).toBe(400);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('soft-deletes existing workspace', async () => {
|
|
138
|
-
const db = getDb();
|
|
139
|
-
insertWorkspace(db, 'w1', '/path/to/hide');
|
|
140
|
-
const res = await app.inject({ method: 'POST', url: '/api/workspaces/hide', payload: { path: '/path/to/hide' } });
|
|
141
|
-
expect(res.statusCode).toBe(200);
|
|
142
|
-
expect(res.json()).toEqual({ success: true });
|
|
143
|
-
const ws = db.prepare('SELECT deleted FROM workspaces WHERE id = ?').get('w1') as { deleted: number };
|
|
144
|
-
expect(ws.deleted).toBe(1);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('creates and soft-deletes new workspace when path does not exist', async () => {
|
|
148
|
-
const res = await app.inject({ method: 'POST', url: '/api/workspaces/hide', payload: { path: '/new/path' } });
|
|
149
|
-
expect(res.statusCode).toBe(200);
|
|
150
|
-
const db = getDb();
|
|
151
|
-
const ws = db.prepare('SELECT * FROM workspaces WHERE path = ?').get('/new/path') as {
|
|
152
|
-
deleted: number;
|
|
153
|
-
id: string;
|
|
154
|
-
};
|
|
155
|
-
expect(ws.deleted).toBe(1);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe('DELETE /api/workspaces/:id', () => {
|
|
160
|
-
it('returns 404 for missing workspace', async () => {
|
|
161
|
-
const res = await app.inject({ method: 'DELETE', url: '/api/workspaces/nonexistent' });
|
|
162
|
-
expect(res.statusCode).toBe(404);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('returns 404 for already-deleted workspace', async () => {
|
|
166
|
-
const db = getDb();
|
|
167
|
-
insertWorkspace(db, 'w1', '/path', { deleted: 1 });
|
|
168
|
-
const res = await app.inject({ method: 'DELETE', url: '/api/workspaces/w1' });
|
|
169
|
-
expect(res.statusCode).toBe(404);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('soft-deletes an active workspace', async () => {
|
|
173
|
-
const db = getDb();
|
|
174
|
-
insertWorkspace(db, 'w1', '/path');
|
|
175
|
-
const res = await app.inject({ method: 'DELETE', url: '/api/workspaces/w1' });
|
|
176
|
-
expect(res.statusCode).toBe(200);
|
|
177
|
-
expect(res.json()).toEqual({ success: true });
|
|
178
|
-
const ws = db.prepare('SELECT deleted FROM workspaces WHERE id = ?').get('w1') as { deleted: number };
|
|
179
|
-
expect(ws.deleted).toBe(1);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe('GET /api/pick-directory', () => {
|
|
184
|
-
it('returns path on success', async () => {
|
|
185
|
-
mockExecFileSuccess('/selected/path/\n');
|
|
186
|
-
const res = await app.inject({ method: 'GET', url: '/api/pick-directory' });
|
|
187
|
-
expect(res.statusCode).toBe(200);
|
|
188
|
-
expect(res.json()).toEqual({ path: '/selected/path' });
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('returns 204 when user cancels', async () => {
|
|
192
|
-
mockExecFileError(new Error('User cancelled'));
|
|
193
|
-
const res = await app.inject({ method: 'GET', url: '/api/pick-directory' });
|
|
194
|
-
expect(res.statusCode).toBe(204);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import type { FastifyInstance } from 'fastify';
|
|
2
|
-
import { execFile } from 'node:child_process';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
4
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
import { getDb } from '../db/index.ts';
|
|
6
|
-
import type { Workspace } from '../db/schema.ts';
|
|
7
|
-
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
|
|
10
|
-
export async function registerWorkspaceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
11
|
-
// GET /api/workspaces — list ALL workspaces (active + soft-deleted)
|
|
12
|
-
fastify.get('/api/workspaces', async () => {
|
|
13
|
-
const rows = getDb().prepare('SELECT * FROM workspaces ORDER BY created_at ASC').all() as Workspace[];
|
|
14
|
-
return rows;
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// POST /api/workspaces — create workspace (or restore if soft-deleted)
|
|
18
|
-
fastify.post<{ Body: { path: string; label?: string } }>('/api/workspaces', {
|
|
19
|
-
schema: {
|
|
20
|
-
body: {
|
|
21
|
-
type: 'object',
|
|
22
|
-
required: ['path'],
|
|
23
|
-
properties: {
|
|
24
|
-
path: { type: 'string' },
|
|
25
|
-
label: { type: 'string' },
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
}, async (request, reply) => {
|
|
30
|
-
const { path, label = null } = request.body || {};
|
|
31
|
-
if (!path || !path.trim()) {
|
|
32
|
-
return reply.code(400).send({ error: 'Path is required' });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const trimmedPath = path.trim();
|
|
36
|
-
const db = getDb();
|
|
37
|
-
|
|
38
|
-
// Check if workspace with this path already exists
|
|
39
|
-
const existing = db.prepare('SELECT * FROM workspaces WHERE path = ?').get(trimmedPath) as Workspace | undefined;
|
|
40
|
-
|
|
41
|
-
if (existing) {
|
|
42
|
-
if (existing.deleted === 0) {
|
|
43
|
-
return reply.code(409).send({ error: 'Workspace with this path already exists' });
|
|
44
|
-
}
|
|
45
|
-
// Restore soft-deleted workspace
|
|
46
|
-
db.prepare('UPDATE workspaces SET deleted = 0, label = ? WHERE id = ?').run(label, existing.id);
|
|
47
|
-
const restored = db.prepare('SELECT * FROM workspaces WHERE id = ?').get(existing.id) as Workspace;
|
|
48
|
-
return restored;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Create new workspace
|
|
52
|
-
const id = uuidv4();
|
|
53
|
-
const now = new Date().toISOString();
|
|
54
|
-
db.prepare('INSERT INTO workspaces (id, path, label, deleted, created_at) VALUES (?, ?, ?, 0, ?)').run(
|
|
55
|
-
id,
|
|
56
|
-
trimmedPath,
|
|
57
|
-
label,
|
|
58
|
-
now,
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const workspace = db.prepare('SELECT * FROM workspaces WHERE id = ?').get(id) as Workspace;
|
|
62
|
-
return workspace;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// GET /api/pick-directory — open native OS folder picker (macOS only)
|
|
66
|
-
fastify.get('/api/pick-directory', async (_request, reply) => {
|
|
67
|
-
try {
|
|
68
|
-
const { stdout } = await execFileAsync('osascript', [
|
|
69
|
-
'-e',
|
|
70
|
-
'POSIX path of (choose folder with prompt "Select a workspace directory")',
|
|
71
|
-
]);
|
|
72
|
-
const path = stdout.trim().replace(/\/$/, '');
|
|
73
|
-
return { path };
|
|
74
|
-
} catch {
|
|
75
|
-
// User cancelled the dialog or osascript not available
|
|
76
|
-
return reply.code(204).send({ cancelled: true });
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// POST /api/workspaces/hide — soft-delete workspace by path (creates record if not exists)
|
|
81
|
-
fastify.post<{ Body: { path: string } }>('/api/workspaces/hide', {
|
|
82
|
-
schema: {
|
|
83
|
-
body: {
|
|
84
|
-
type: 'object',
|
|
85
|
-
required: ['path'],
|
|
86
|
-
properties: {
|
|
87
|
-
path: { type: 'string' },
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
}, async (request, reply) => {
|
|
92
|
-
const { path } = request.body || {};
|
|
93
|
-
if (!path || !path.trim()) {
|
|
94
|
-
return reply.code(400).send({ error: 'Path is required' });
|
|
95
|
-
}
|
|
96
|
-
const trimmedPath = path.trim();
|
|
97
|
-
const db = getDb();
|
|
98
|
-
|
|
99
|
-
const existing = db.prepare('SELECT * FROM workspaces WHERE path = ?').get(trimmedPath) as Workspace | undefined;
|
|
100
|
-
if (existing) {
|
|
101
|
-
db.prepare('UPDATE workspaces SET deleted = 1 WHERE id = ?').run(existing.id);
|
|
102
|
-
} else {
|
|
103
|
-
const id = uuidv4();
|
|
104
|
-
const now = new Date().toISOString();
|
|
105
|
-
db.prepare('INSERT INTO workspaces (id, path, label, deleted, created_at) VALUES (?, ?, NULL, 1, ?)').run(
|
|
106
|
-
id,
|
|
107
|
-
trimmedPath,
|
|
108
|
-
now,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return { success: true };
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// DELETE /api/workspaces/:id — soft-delete workspace by ID (does NOT delete sessions)
|
|
116
|
-
fastify.delete<{ Params: { id: string } }>('/api/workspaces/:id', async (request, reply) => {
|
|
117
|
-
const { id } = request.params;
|
|
118
|
-
const db = getDb();
|
|
119
|
-
|
|
120
|
-
const existing = db.prepare('SELECT id FROM workspaces WHERE id = ? AND deleted = 0').get(id);
|
|
121
|
-
if (!existing) {
|
|
122
|
-
return reply.code(404).send({ error: 'Workspace not found' });
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
db.prepare('UPDATE workspaces SET deleted = 1 WHERE id = ?').run(id);
|
|
126
|
-
return { success: true };
|
|
127
|
-
});
|
|
128
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { broadcast, handleUpgrade, getWss, initWs } from './index.ts';
|
|
3
|
-
import type { WebSocket, WebSocketServer } from 'ws';
|
|
4
|
-
import type { IncomingMessage } from 'node:http';
|
|
5
|
-
import type { Duplex } from 'node:stream';
|
|
6
|
-
|
|
7
|
-
describe('getWss', () => {
|
|
8
|
-
it('returns null before initWs is called', async () => {
|
|
9
|
-
vi.resetModules();
|
|
10
|
-
const { getWss: freshGetWss } = await import('./index.ts');
|
|
11
|
-
expect(freshGetWss()).toBeNull();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('returns instance after initWs', () => {
|
|
15
|
-
const wss = initWs();
|
|
16
|
-
expect(getWss()).toBe(wss);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('broadcast', () => {
|
|
21
|
-
let wss: WebSocketServer;
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
wss = initWs();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('sends JSON message to OPEN clients', () => {
|
|
28
|
-
const client = { readyState: 1, OPEN: 1, send: vi.fn() } as unknown as WebSocket;
|
|
29
|
-
wss.clients.add(client);
|
|
30
|
-
broadcast('turn:started', { sessionId: 's1' });
|
|
31
|
-
expect(client.send).toHaveBeenCalledWith(JSON.stringify({ event: 'turn:started', data: { sessionId: 's1' } }));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('skips non-OPEN clients', () => {
|
|
35
|
-
const client = { readyState: 3, OPEN: 1, send: vi.fn() } as unknown as WebSocket;
|
|
36
|
-
wss.clients.add(client);
|
|
37
|
-
broadcast('turn:started', {});
|
|
38
|
-
expect(client.send).not.toHaveBeenCalled();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('sends to multiple OPEN clients', () => {
|
|
42
|
-
const c1 = { readyState: 1, OPEN: 1, send: vi.fn() } as unknown as WebSocket;
|
|
43
|
-
const c2 = { readyState: 1, OPEN: 1, send: vi.fn() } as unknown as WebSocket;
|
|
44
|
-
wss.clients.add(c1);
|
|
45
|
-
wss.clients.add(c2);
|
|
46
|
-
broadcast('item:completed', { id: 'i1' });
|
|
47
|
-
expect(c1.send).toHaveBeenCalledTimes(1);
|
|
48
|
-
expect(c2.send).toHaveBeenCalledTimes(1);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('is no-op when no clients connected', () => {
|
|
52
|
-
broadcast('turn:started', {});
|
|
53
|
-
// No error thrown
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('is no-op when wss is not initialised', async () => {
|
|
57
|
-
vi.resetModules();
|
|
58
|
-
const { broadcast: freshBroadcast } = await import('./index.ts');
|
|
59
|
-
freshBroadcast('turn:started', {});
|
|
60
|
-
// No error thrown
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('handleUpgrade', () => {
|
|
65
|
-
function makeMockWss(): WebSocketServer {
|
|
66
|
-
return { handleUpgrade: vi.fn(), emit: vi.fn() } as unknown as WebSocketServer;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
it('calls wss.handleUpgrade for /ws path', () => {
|
|
70
|
-
const mockWss = makeMockWss();
|
|
71
|
-
const request = { url: '/ws' } as unknown as IncomingMessage;
|
|
72
|
-
const socket = { destroy: vi.fn() } as unknown as Duplex;
|
|
73
|
-
const head = Buffer.alloc(0);
|
|
74
|
-
|
|
75
|
-
handleUpgrade(mockWss, request, socket, head);
|
|
76
|
-
|
|
77
|
-
expect(mockWss.handleUpgrade).toHaveBeenCalledWith(request, socket, head, expect.any(Function));
|
|
78
|
-
expect(socket.destroy).not.toHaveBeenCalled();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('emits connection event when upgrade completes', () => {
|
|
82
|
-
const mockWss = makeMockWss();
|
|
83
|
-
const request = { url: '/ws' } as unknown as IncomingMessage;
|
|
84
|
-
const socket = { destroy: vi.fn() } as unknown as Duplex;
|
|
85
|
-
const head = Buffer.alloc(0);
|
|
86
|
-
|
|
87
|
-
handleUpgrade(mockWss, request, socket, head);
|
|
88
|
-
|
|
89
|
-
const callback = (mockWss.handleUpgrade as ReturnType<typeof vi.fn>).mock.calls[0][3] as (ws: WebSocket) => void;
|
|
90
|
-
const mockWs = { readyState: 1 } as unknown as WebSocket;
|
|
91
|
-
callback(mockWs);
|
|
92
|
-
|
|
93
|
-
expect(mockWss.emit).toHaveBeenCalledWith('connection', mockWs, request);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('destroys socket for non-/ws path', () => {
|
|
97
|
-
const mockWss = makeMockWss();
|
|
98
|
-
const request = { url: '/api/something' } as unknown as IncomingMessage;
|
|
99
|
-
const socket = { destroy: vi.fn() } as unknown as Duplex;
|
|
100
|
-
const head = Buffer.alloc(0);
|
|
101
|
-
|
|
102
|
-
handleUpgrade(mockWss, request, socket, head);
|
|
103
|
-
|
|
104
|
-
expect(mockWss.handleUpgrade).not.toHaveBeenCalled();
|
|
105
|
-
expect(socket.destroy).toHaveBeenCalled();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage } from 'node:http';
|
|
2
|
-
import type { Duplex } from 'node:stream';
|
|
3
|
-
import type { WebSocket } from 'ws';
|
|
4
|
-
import { WebSocketServer } from 'ws';
|
|
5
|
-
|
|
6
|
-
let wss: WebSocketServer | null = null;
|
|
7
|
-
|
|
8
|
-
/** Initialise the WebSocket server in noServer mode.
|
|
9
|
-
* Call `handleUpgrade` from the HTTP server's 'upgrade' event to accept connections.
|
|
10
|
-
* @returns The created `WebSocketServer` instance. */
|
|
11
|
-
export function initWs(): WebSocketServer {
|
|
12
|
-
wss = new WebSocketServer({ noServer: true });
|
|
13
|
-
return wss;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Handle an HTTP upgrade request for the `/ws` path.
|
|
17
|
-
* If the URL does not start with `/ws`, the socket is destroyed.
|
|
18
|
-
* @param wss — The WebSocket server to upgrade to.
|
|
19
|
-
* @param request — The incoming HTTP upgrade request.
|
|
20
|
-
* @param socket — The raw TCP socket.
|
|
21
|
-
* @param head — The first packet of the upgraded connection. */
|
|
22
|
-
export function handleUpgrade(wss: WebSocketServer, request: IncomingMessage, socket: Duplex, head: Buffer): void {
|
|
23
|
-
const url = request.url || '';
|
|
24
|
-
if (url.startsWith('/ws')) {
|
|
25
|
-
wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {
|
|
26
|
-
wss.emit('connection', ws, request);
|
|
27
|
-
});
|
|
28
|
-
} else {
|
|
29
|
-
socket.destroy();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Broadcast a JSON event to all connected WebSocket clients.
|
|
34
|
-
* No-op if the server is not initialised or no clients are connected.
|
|
35
|
-
* @param event — Event name (e.g. `'turn:started'`, `'item:completed'`).
|
|
36
|
-
* @param data — Payload object to serialise as JSON. */
|
|
37
|
-
export function broadcast(event: string, data: unknown): void {
|
|
38
|
-
if (!wss) return;
|
|
39
|
-
const message = JSON.stringify({ event, data });
|
|
40
|
-
for (const client of wss.clients) {
|
|
41
|
-
if (client.readyState === client.OPEN) {
|
|
42
|
-
client.send(message);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Get the current WebSocket server instance.
|
|
48
|
-
* @returns The `WebSocketServer`, or `null` if not initialised. */
|
|
49
|
-
export function getWss(): WebSocketServer | null {
|
|
50
|
-
return wss;
|
|
51
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"outDir": "dist",
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"declaration": false,
|
|
12
|
-
"sourceMap": true,
|
|
13
|
-
"forceConsistentCasingInFileNames": true,
|
|
14
|
-
"noUnusedLocals": true,
|
|
15
|
-
"noUnusedParameters": true,
|
|
16
|
-
"noImplicitReturns": true,
|
|
17
|
-
"noFallthroughCasesInSwitch": true,
|
|
18
|
-
"allowImportingTsExtensions": true,
|
|
19
|
-
"paths": {
|
|
20
|
-
"@agentpane/shared": ["../shared/types.ts"]
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
"include": ["src/**/*"],
|
|
24
|
-
"exclude": ["node_modules", "dist"]
|
|
25
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vitest/config';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Backend vitest configuration.
|
|
5
|
-
*
|
|
6
|
-
* RAM mitigation: single-fork mode + no file parallelism.
|
|
7
|
-
* Runs all test files in one Node.js process, one file at a time.
|
|
8
|
-
*/
|
|
9
|
-
export default defineConfig({
|
|
10
|
-
test: {
|
|
11
|
-
environment: 'node',
|
|
12
|
-
// RAM: single process, one file at a time
|
|
13
|
-
pool: 'forks',
|
|
14
|
-
poolOptions: {
|
|
15
|
-
forks: { singleFork: true },
|
|
16
|
-
},
|
|
17
|
-
fileParallelism: false,
|
|
18
|
-
maxConcurrency: 1,
|
|
19
|
-
},
|
|
20
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js';
|
|
2
|
-
import tseslint from 'typescript-eslint';
|
|
3
|
-
import prettier from 'eslint-config-prettier';
|
|
4
|
-
|
|
5
|
-
export default [
|
|
6
|
-
{
|
|
7
|
-
ignores: ['**/dist/**', '**/node_modules/**', '**/*.config.{js,ts}'],
|
|
8
|
-
},
|
|
9
|
-
js.configs.recommended,
|
|
10
|
-
...tseslint.configs.recommended,
|
|
11
|
-
prettier,
|
|
12
|
-
{
|
|
13
|
-
languageOptions: {
|
|
14
|
-
ecmaVersion: 2022,
|
|
15
|
-
sourceType: 'module',
|
|
16
|
-
},
|
|
17
|
-
rules: {
|
|
18
|
-
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
19
|
-
'@typescript-eslint/no-explicit-any': 'warn',
|
|
20
|
-
'no-console': 'off',
|
|
21
|
-
'prefer-const': 'error',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
// React frontend rules
|
|
26
|
-
files: ['frontend/**/*.tsx', 'frontend/**/*.ts'],
|
|
27
|
-
languageOptions: {
|
|
28
|
-
parserOptions: {
|
|
29
|
-
ecmaFeatures: { jsx: true },
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
];
|