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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent-pane embed — loads the agent-pane backend bundle in-process so the
|
|
4
|
+
* daemon serves it behind its own port (no second listener).
|
|
5
|
+
*
|
|
6
|
+
* The bundle (agent-pane/backend/dist/server.js, ESM, built by esbuild) is
|
|
7
|
+
* loaded with a dynamic import; its `createAgentPaneApp` factory builds the
|
|
8
|
+
* Fastify app with an explicit DB path. The daemon delegates `/api/*` requests
|
|
9
|
+
* to `fastify.routing()` and routes `/ws` upgrades to the app's WebSocket
|
|
10
|
+
* server. Env kill-switch: REPOBURG_EMBED_AGENT_PANE=0 disables the embed.
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.resolveAgentPaneDist = resolveAgentPaneDist;
|
|
17
|
+
exports.agentPaneDbDir = agentPaneDbDir;
|
|
18
|
+
exports.migrateLegacyDb = migrateLegacyDb;
|
|
19
|
+
exports.loadAgentPane = loadAgentPane;
|
|
20
|
+
exports.createPaneDelegate = createPaneDelegate;
|
|
21
|
+
exports.createUpgradeRouter = createUpgradeRouter;
|
|
22
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
23
|
+
const path_1 = __importDefault(require("path"));
|
|
24
|
+
const os_1 = __importDefault(require("os"));
|
|
25
|
+
const url_1 = require("url");
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the agent-pane bundle path ABSOLUTELY (pm2 God-daemon-cwd bug class:
|
|
28
|
+
* never resolve runtime paths against the process cwd).
|
|
29
|
+
*
|
|
30
|
+
* Works for dev (daemon/src or daemon/dist under the repo) and for the
|
|
31
|
+
* published layout (repoburg/daemon/dist -> repoburg/agent-pane/backend/dist).
|
|
32
|
+
*/
|
|
33
|
+
function resolveAgentPaneDist(fromDir) {
|
|
34
|
+
return path_1.default.resolve(fromDir, '../../agent-pane/backend/dist/server.js');
|
|
35
|
+
}
|
|
36
|
+
/** Directory holding the embedded app's DB (alongside services.json). */
|
|
37
|
+
function agentPaneDbDir(home = os_1.default.homedir()) {
|
|
38
|
+
return path_1.default.join(home, '.repoburg', 'agent-pane');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One-time migration: copy the legacy dev DB into the embedded location.
|
|
42
|
+
*
|
|
43
|
+
* Copies the DB file plus any WAL/SHM sidecars. No-op when the source is
|
|
44
|
+
* missing (published installs) or the destination already exists.
|
|
45
|
+
*
|
|
46
|
+
* @returns true when a copy was performed.
|
|
47
|
+
*/
|
|
48
|
+
function migrateLegacyDb(sourceDbPath, destDbPath) {
|
|
49
|
+
if (!fs_extra_1.default.existsSync(sourceDbPath) || fs_extra_1.default.existsSync(destDbPath)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(destDbPath));
|
|
53
|
+
const sidecars = ['', '-wal', '-shm'];
|
|
54
|
+
for (const suffix of sidecars) {
|
|
55
|
+
const src = `${sourceDbPath}${suffix}`;
|
|
56
|
+
if (fs_extra_1.default.existsSync(src)) {
|
|
57
|
+
fs_extra_1.default.copyFileSync(src, `${destDbPath}${suffix}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
console.log(`[agentPane] migrated legacy DB ${sourceDbPath} -> ${destDbPath}`);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
/** Load and initialise the embedded agent-pane app. Returns null when disabled/absent. */
|
|
64
|
+
async function loadAgentPane(opts = {}) {
|
|
65
|
+
if ((opts.envDisable ?? process.env.REPOBURG_EMBED_AGENT_PANE) === '0') {
|
|
66
|
+
console.log('[agentPane] embed disabled via REPOBURG_EMBED_AGENT_PANE=0');
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const distPath = opts.distPath ?? resolveAgentPaneDist(__dirname);
|
|
70
|
+
if (!fs_extra_1.default.existsSync(distPath)) {
|
|
71
|
+
console.warn(`[agentPane] bundle not found at ${distPath} — /api/* responds 503`);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const dbPath = opts.dbPath ?? path_1.default.join(agentPaneDbDir(), 'data.db');
|
|
75
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(dbPath));
|
|
76
|
+
migrateLegacyDb(opts.legacyDbPath ?? resolveAgentPaneDist(__dirname).replace('dist/server.js', 'data.db'), dbPath);
|
|
77
|
+
// Dynamic import: the bundle is ESM; the daemon itself is CommonJS.
|
|
78
|
+
// new Function keeps the RUNTIME import even under ts-jest, which would
|
|
79
|
+
// otherwise transpile import() into require() (ESM-incompatible).
|
|
80
|
+
const dynamicImport = new Function('specifier', 'return import(specifier);');
|
|
81
|
+
const mod = await dynamicImport((0, url_1.pathToFileURL)(distPath).href);
|
|
82
|
+
const { fastify, wss } = await mod.createAgentPaneApp(dbPath);
|
|
83
|
+
await fastify.ready();
|
|
84
|
+
console.log(`[agentPane] embedded app ready (db: ${dbPath})`);
|
|
85
|
+
return {
|
|
86
|
+
routing: fastify.routing.bind(fastify),
|
|
87
|
+
handleWsUpgrade: (req, socket, head) => {
|
|
88
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
89
|
+
wss.emit('connection', ws, req);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
shutdown: mod.shutdownAllProviders,
|
|
93
|
+
close: () => fastify.close(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Express middleware delegating `/api/*` to the embedded app.
|
|
98
|
+
*
|
|
99
|
+
* Mount BEFORE `express.json()` — Fastify parses its own request bodies, and
|
|
100
|
+
* the stream can only be consumed once.
|
|
101
|
+
*/
|
|
102
|
+
function createPaneDelegate(pane) {
|
|
103
|
+
return (req, res) => {
|
|
104
|
+
if (!pane) {
|
|
105
|
+
res.status(503).json({ error: 'agent-pane is not available on this daemon' });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
pane.routing(req, res);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Single upgrade router for the daemon's HTTP server.
|
|
113
|
+
*
|
|
114
|
+
* `/ws` goes to the embedded agent-pane app (when present); everything else —
|
|
115
|
+
* e.g. the platform frontend's auth WebSocket — goes to the daemon wss.
|
|
116
|
+
*/
|
|
117
|
+
function createUpgradeRouter(pane, daemonWss) {
|
|
118
|
+
return (req, socket, head) => {
|
|
119
|
+
const url = req.url || '';
|
|
120
|
+
if (pane && url.startsWith('/ws')) {
|
|
121
|
+
pane.handleWsUpgrade(req, socket, head);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
daemonWss.handleUpgrade(req, socket, head, (ws) => {
|
|
125
|
+
daemonWss.emit('connection', ws, req);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Specs for the agent-pane embed: path resolution, legacy DB migration,
|
|
4
|
+
* delegation middleware, upgrade routing, and the loader (stub + real bundle).
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const agentPane_1 = require("./agentPane");
|
|
14
|
+
// Stub bundle contents: an ESM module satisfying the embed contract.
|
|
15
|
+
const STUB_DIST = `
|
|
16
|
+
export async function createAgentPaneApp(dbPath) {
|
|
17
|
+
globalThis.__stubDbPath = dbPath;
|
|
18
|
+
return {
|
|
19
|
+
fastify: {
|
|
20
|
+
ready: async () => {},
|
|
21
|
+
routing: (req, res) => { res.end('pane:' + req.url); },
|
|
22
|
+
close: async () => {},
|
|
23
|
+
},
|
|
24
|
+
wss: {
|
|
25
|
+
handleUpgrade: (req, socket, head, cb) => { cb({ stub: true }); },
|
|
26
|
+
emit: () => {},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function shutdownAllProviders() { globalThis.__stubShutdown = true; }
|
|
31
|
+
`;
|
|
32
|
+
function writeStubDist(dir) {
|
|
33
|
+
fs_extra_1.default.ensureDirSync(dir);
|
|
34
|
+
const distPath = path_1.default.join(dir, 'server.js');
|
|
35
|
+
fs_extra_1.default.writeFileSync(distPath, STUB_DIST);
|
|
36
|
+
return distPath;
|
|
37
|
+
}
|
|
38
|
+
function tmpDir(name) {
|
|
39
|
+
return fs_extra_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `agentpane-${name}-`));
|
|
40
|
+
}
|
|
41
|
+
function makeLegacyDb(dir, content = 'legacy-data') {
|
|
42
|
+
const legacyDb = path_1.default.join(dir, 'legacy', 'data.db');
|
|
43
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(legacyDb));
|
|
44
|
+
fs_extra_1.default.writeFileSync(legacyDb, content);
|
|
45
|
+
return legacyDb;
|
|
46
|
+
}
|
|
47
|
+
describe('resolveAgentPaneDist', () => {
|
|
48
|
+
it('resolves an absolute path under the repo/package layout from either src or dist', () => {
|
|
49
|
+
const fromSrc = (0, agentPane_1.resolveAgentPaneDist)('/repo/daemon/src');
|
|
50
|
+
const fromDist = (0, agentPane_1.resolveAgentPaneDist)('/repo/daemon/dist');
|
|
51
|
+
expect(fromSrc).toBe('/repo/agent-pane/backend/dist/server.js');
|
|
52
|
+
expect(fromDist).toBe('/repo/agent-pane/backend/dist/server.js');
|
|
53
|
+
expect(path_1.default.isAbsolute(fromSrc)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('agentPaneDbDir', () => {
|
|
57
|
+
it('lives under ~/.repoburg/agent-pane', () => {
|
|
58
|
+
expect((0, agentPane_1.agentPaneDbDir)('/home/tester')).toBe(path_1.default.join('/home/tester', '.repoburg', 'agent-pane'));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('migrateLegacyDb', () => {
|
|
62
|
+
it('copies the DB plus WAL/SHM sidecars when the destination is missing', () => {
|
|
63
|
+
const dir = tmpDir('migrate');
|
|
64
|
+
const src = path_1.default.join(dir, 'data.db');
|
|
65
|
+
fs_extra_1.default.writeFileSync(src, 'base');
|
|
66
|
+
fs_extra_1.default.writeFileSync(`${src}-wal`, 'wal');
|
|
67
|
+
const dest = path_1.default.join(dir, 'dest', 'data.db');
|
|
68
|
+
expect((0, agentPane_1.migrateLegacyDb)(src, dest)).toBe(true);
|
|
69
|
+
expect(fs_extra_1.default.readFileSync(dest, 'utf-8')).toBe('base');
|
|
70
|
+
expect(fs_extra_1.default.readFileSync(`${dest}-wal`, 'utf-8')).toBe('wal');
|
|
71
|
+
});
|
|
72
|
+
it('is a no-op when the destination already exists', () => {
|
|
73
|
+
const dir = tmpDir('migrate-dest-exists');
|
|
74
|
+
const src = path_1.default.join(dir, 'data.db');
|
|
75
|
+
const dest = path_1.default.join(dir, 'out', 'data.db');
|
|
76
|
+
fs_extra_1.default.writeFileSync(src, 'base');
|
|
77
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(dest));
|
|
78
|
+
fs_extra_1.default.writeFileSync(dest, 'existing');
|
|
79
|
+
expect((0, agentPane_1.migrateLegacyDb)(src, dest)).toBe(false);
|
|
80
|
+
expect(fs_extra_1.default.readFileSync(dest, 'utf-8')).toBe('existing');
|
|
81
|
+
});
|
|
82
|
+
it('is a no-op when the source is missing', () => {
|
|
83
|
+
const dir = tmpDir('migrate-no-source');
|
|
84
|
+
expect((0, agentPane_1.migrateLegacyDb)(path_1.default.join(dir, 'absent.db'), path_1.default.join(dir, 'out', 'data.db'))).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('createPaneDelegate', () => {
|
|
88
|
+
it('responds 503 when the pane is not loaded', () => {
|
|
89
|
+
const res = { status: jest.fn().mockReturnThis(), json: jest.fn() };
|
|
90
|
+
(0, agentPane_1.createPaneDelegate)(null)({}, res);
|
|
91
|
+
expect(res.status).toHaveBeenCalledWith(503);
|
|
92
|
+
expect(res.json).toHaveBeenCalledWith({ error: 'agent-pane is not available on this daemon' });
|
|
93
|
+
});
|
|
94
|
+
it('routes raw request/response into the embedded fastify router', () => {
|
|
95
|
+
const routing = jest.fn();
|
|
96
|
+
const pane = {
|
|
97
|
+
routing,
|
|
98
|
+
handleWsUpgrade: jest.fn(),
|
|
99
|
+
shutdown: jest.fn(),
|
|
100
|
+
close: jest.fn(),
|
|
101
|
+
};
|
|
102
|
+
const req = { url: '/api/sessions' };
|
|
103
|
+
const res = {};
|
|
104
|
+
(0, agentPane_1.createPaneDelegate)(pane)(req, res);
|
|
105
|
+
expect(routing).toHaveBeenCalledWith(req, res);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('createUpgradeRouter', () => {
|
|
109
|
+
it('sends /ws upgrades to the embedded pane', () => {
|
|
110
|
+
const pane = {
|
|
111
|
+
routing: jest.fn(),
|
|
112
|
+
handleWsUpgrade: jest.fn(),
|
|
113
|
+
shutdown: jest.fn(),
|
|
114
|
+
close: jest.fn(),
|
|
115
|
+
};
|
|
116
|
+
const daemonWss = { handleUpgrade: jest.fn(), emit: jest.fn() };
|
|
117
|
+
const route = (0, agentPane_1.createUpgradeRouter)(pane, daemonWss);
|
|
118
|
+
route({ url: '/ws' }, {}, Buffer.alloc(0));
|
|
119
|
+
expect(pane.handleWsUpgrade).toHaveBeenCalled();
|
|
120
|
+
expect(daemonWss.handleUpgrade).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
it('sends non-/ws upgrades to the daemon wss and emits connection', () => {
|
|
123
|
+
const pane = { handleWsUpgrade: jest.fn() };
|
|
124
|
+
const daemonWss = {
|
|
125
|
+
handleUpgrade: jest.fn((_req, _socket, _head, cb) => cb({ daemon: true })),
|
|
126
|
+
emit: jest.fn(),
|
|
127
|
+
};
|
|
128
|
+
const route = (0, agentPane_1.createUpgradeRouter)(pane, daemonWss);
|
|
129
|
+
route({ url: '/' }, {}, Buffer.alloc(0));
|
|
130
|
+
expect(pane.handleWsUpgrade).not.toHaveBeenCalled();
|
|
131
|
+
expect(daemonWss.handleUpgrade).toHaveBeenCalled();
|
|
132
|
+
expect(daemonWss.emit).toHaveBeenCalledWith('connection', { daemon: true }, { url: '/' });
|
|
133
|
+
});
|
|
134
|
+
it('falls back to the daemon wss for /ws when the pane is absent', () => {
|
|
135
|
+
const daemonWss = {
|
|
136
|
+
handleUpgrade: jest.fn((_req, _socket, _head, cb) => cb({})),
|
|
137
|
+
emit: jest.fn(),
|
|
138
|
+
};
|
|
139
|
+
const route = (0, agentPane_1.createUpgradeRouter)(null, daemonWss);
|
|
140
|
+
route({ url: '/ws' }, {}, Buffer.alloc(0));
|
|
141
|
+
expect(daemonWss.handleUpgrade).toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('loadAgentPane', () => {
|
|
145
|
+
it('returns null when disabled via the kill-switch', async () => {
|
|
146
|
+
expect(await (0, agentPane_1.loadAgentPane)({ envDisable: '0', distPath: 'unused' })).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
it('returns null when the bundle is missing', async () => {
|
|
149
|
+
const dir = tmpDir('no-dist');
|
|
150
|
+
expect(await (0, agentPane_1.loadAgentPane)({ distPath: path_1.default.join(dir, 'absent.js') })).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
it('loads the stub bundle, wires routing/shutdown, and migrates the legacy DB', async () => {
|
|
153
|
+
const dir = tmpDir('full');
|
|
154
|
+
const distPath = writeStubDist(path_1.default.join(dir, 'dist'));
|
|
155
|
+
const dbPath = path_1.default.join(dir, 'home', 'data.db');
|
|
156
|
+
const legacyDb = makeLegacyDb(dir);
|
|
157
|
+
const pane = await (0, agentPane_1.loadAgentPane)({ distPath, dbPath, legacyDbPath: legacyDb });
|
|
158
|
+
expect(pane).not.toBeNull();
|
|
159
|
+
// DB dir created + legacy DB migrated in.
|
|
160
|
+
expect(fs_extra_1.default.readFileSync(dbPath, 'utf-8')).toBe('legacy-data');
|
|
161
|
+
// routing delegates to the stub fastify.
|
|
162
|
+
const chunks = [];
|
|
163
|
+
const res = { end: (s) => chunks.push(s) };
|
|
164
|
+
pane.routing({ url: '/api/sessions' }, res);
|
|
165
|
+
expect(chunks[0]).toBe('pane:/api/sessions');
|
|
166
|
+
await pane.shutdown();
|
|
167
|
+
await pane.close();
|
|
168
|
+
});
|
|
169
|
+
it('does not copy a legacy DB over an existing embedded DB', async () => {
|
|
170
|
+
const dir = tmpDir('no-overwrite');
|
|
171
|
+
const distPath = writeStubDist(path_1.default.join(dir, 'dist'));
|
|
172
|
+
const dbPath = path_1.default.join(dir, 'home', 'data.db');
|
|
173
|
+
const legacyDb = makeLegacyDb(dir);
|
|
174
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(dbPath));
|
|
175
|
+
fs_extra_1.default.writeFileSync(dbPath, 'embedded-data');
|
|
176
|
+
await (0, agentPane_1.loadAgentPane)({ distPath, dbPath, legacyDbPath: legacyDb });
|
|
177
|
+
expect(fs_extra_1.default.readFileSync(dbPath, 'utf-8')).toBe('embedded-data');
|
|
178
|
+
});
|
|
179
|
+
// NOTE: the real bundle is NOT loaded here — jest's vm module loader
|
|
180
|
+
// cannot provide the CJS interop the bundle's native deps need
|
|
181
|
+
// ("exports is not defined"). Real-bundle boot is validated by the live
|
|
182
|
+
// daemon E2E (Phase 5 of the merge plan).
|
|
183
|
+
});
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const express_1 = require("express");
|
|
7
7
|
const serviceManager_1 = require("../serviceManager");
|
|
8
8
|
const checkAuth_1 = require("../middleware/checkAuth");
|
|
9
|
+
const workspace_validation_1 = require("../workspace-validation");
|
|
9
10
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
11
|
const router = (0, express_1.Router)();
|
|
11
12
|
router.get('/', (req, res) => {
|
|
@@ -28,6 +29,9 @@ router.post('/start', checkAuth_1.checkAuth, async (req, res) => {
|
|
|
28
29
|
res.status(201).json(service);
|
|
29
30
|
}
|
|
30
31
|
catch (error) {
|
|
32
|
+
if (error instanceof workspace_validation_1.WorkspaceValidationError) {
|
|
33
|
+
return res.status(400).json({ error: error.message });
|
|
34
|
+
}
|
|
31
35
|
const message = error instanceof Error ? error.message : 'An unknown error occurred';
|
|
32
36
|
res.status(500).json({ error: message });
|
|
33
37
|
}
|
package/daemon/dist/index.js
CHANGED
|
@@ -13,17 +13,28 @@ const services_1 = __importDefault(require("./api/services"));
|
|
|
13
13
|
const auth_1 = __importDefault(require("./api/auth"));
|
|
14
14
|
const registry_1 = __importDefault(require("./api/registry"));
|
|
15
15
|
const mcp_gateway_1 = require("./mcp-gateway");
|
|
16
|
+
const agentPane_1 = require("./agentPane");
|
|
16
17
|
const PORT = 9998;
|
|
17
18
|
async function main() {
|
|
18
19
|
await serviceManager_1.serviceManager.connect();
|
|
20
|
+
// Embedded agent-pane backend — served on THIS port (no second listener).
|
|
21
|
+
// Failure is non-fatal: the daemon keeps managing services, /api/* -> 503.
|
|
22
|
+
const pane = await (0, agentPane_1.loadAgentPane)().catch((err) => {
|
|
23
|
+
console.warn('[agentPane] embed failed:', err);
|
|
24
|
+
return null;
|
|
25
|
+
});
|
|
19
26
|
const app = (0, express_1.default)();
|
|
20
27
|
const server = http_1.default.createServer(app);
|
|
21
|
-
|
|
28
|
+
// Daemon wss in noServer mode — the shared upgrade router dispatches below.
|
|
29
|
+
const wss = new ws_1.WebSocketServer({ noServer: true });
|
|
22
30
|
app.use((0, cors_1.default)({
|
|
23
31
|
origin: '*', // Allow all
|
|
24
32
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
|
25
33
|
credentials: true,
|
|
26
34
|
}));
|
|
35
|
+
// Embedded agent-pane API. MUST precede express.json() — Fastify parses its
|
|
36
|
+
// own request bodies; the stream can only be consumed once.
|
|
37
|
+
app.all('/api/*', (0, agentPane_1.createPaneDelegate)(pane));
|
|
27
38
|
app.use(express_1.default.json());
|
|
28
39
|
// API Routers
|
|
29
40
|
app.use('/services', services_1.default);
|
|
@@ -51,19 +62,28 @@ async function main() {
|
|
|
51
62
|
ws.on('close', () => console.log('Client disconnected from daemon WebSocket'));
|
|
52
63
|
ws.on('error', (error) => console.error('WebSocket error:', error));
|
|
53
64
|
});
|
|
65
|
+
// Single upgrade router: '/ws' -> embedded agent-pane, everything else -> daemon wss.
|
|
66
|
+
server.on('upgrade', (0, agentPane_1.createUpgradeRouter)(pane, wss));
|
|
54
67
|
server.listen(PORT, () => {
|
|
55
68
|
console.log(`Repoburg Platform Daemon listening on http://localhost:${PORT}`);
|
|
56
69
|
console.log(`MCP Gateway available at http://localhost:${PORT}/mcp`);
|
|
57
70
|
});
|
|
58
|
-
// Clean up MCP gateway sessions on shutdown.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
// Clean up MCP gateway sessions and the embedded agent-pane on shutdown.
|
|
72
|
+
const shutdown = async () => {
|
|
73
|
+
if (pane) {
|
|
74
|
+
try {
|
|
75
|
+
await pane.shutdown();
|
|
76
|
+
await pane.close();
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
console.error('[agentPane] shutdown failed:', err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
64
82
|
await mcp_gateway_1.mcpGateway.closeAllSessions();
|
|
65
83
|
process.exit(0);
|
|
66
|
-
}
|
|
84
|
+
};
|
|
85
|
+
process.on('SIGINT', shutdown);
|
|
86
|
+
process.on('SIGTERM', shutdown);
|
|
67
87
|
}
|
|
68
88
|
main().catch(err => {
|
|
69
89
|
console.error("Failed to start daemon:", err);
|
|
@@ -37,10 +37,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.serviceManager = void 0;
|
|
40
|
+
exports.backendScriptPath = backendScriptPath;
|
|
41
|
+
exports.describeError = describeError;
|
|
40
42
|
const pm2_1 = __importDefault(require("pm2"));
|
|
41
43
|
const path_1 = __importDefault(require("path"));
|
|
42
44
|
const os_1 = __importDefault(require("os"));
|
|
43
45
|
const stateManager_1 = require("./stateManager");
|
|
46
|
+
const workspace_validation_1 = require("./workspace-validation");
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the backend entry script. Production resolves ABSOLUTELY from the
|
|
49
|
+
* package layout: pm2 resolves bare script names against ITS daemon cwd,
|
|
50
|
+
* which breaks `repoburg-backend` whenever the PM2 God daemon runs from an
|
|
51
|
+
* unrelated directory.
|
|
52
|
+
*/
|
|
53
|
+
function backendScriptPath(fromDir, isDevelopment) {
|
|
54
|
+
return isDevelopment
|
|
55
|
+
? path_1.default.resolve(fromDir, '../../backend/dist/main.js')
|
|
56
|
+
: path_1.default.resolve(fromDir, '../../backend/dist/src/main.js');
|
|
57
|
+
}
|
|
58
|
+
/** Human message for unknown errors (pm2 throws arrays of Errors). */
|
|
59
|
+
function describeError(error) {
|
|
60
|
+
if (error instanceof Error)
|
|
61
|
+
return error.message;
|
|
62
|
+
if (Array.isArray(error))
|
|
63
|
+
return error.map(describeError).join('; ');
|
|
64
|
+
return String(error);
|
|
65
|
+
}
|
|
44
66
|
const get_port_1 = __importStar(require("./utils/get-port"));
|
|
45
67
|
class ServiceManager {
|
|
46
68
|
constructor() {
|
|
@@ -137,27 +159,25 @@ class ServiceManager {
|
|
|
137
159
|
};
|
|
138
160
|
}
|
|
139
161
|
async start(projectPath, customName) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
const name = customName || path_1.default.basename(path_1.default.resolve(projectPath));
|
|
163
|
+
// Validate BEFORE the same-name replacement below: the service being
|
|
164
|
+
// replaced must not collide with itself, but every other service counts.
|
|
165
|
+
const others = stateManager_1.stateManager.getServices().filter((s) => s.name !== name);
|
|
166
|
+
const absolutePath = (0, workspace_validation_1.validateWorkspacePath)(projectPath, others.map((s) => ({ name: s.name, path: s.path })));
|
|
167
|
+
console.log(`Starting service for workspace ${absolutePath}`);
|
|
143
168
|
if (stateManager_1.stateManager.getService(name)) {
|
|
144
169
|
console.log(`Service '${name}' already exists. Removing it before starting a new one.`);
|
|
145
170
|
await this.remove(name);
|
|
146
171
|
}
|
|
147
172
|
const port = await (0, get_port_1.default)({ port: (0, get_port_1.portNumbers)(3400, 3999) });
|
|
148
173
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
149
|
-
let script;
|
|
150
174
|
let interpreter = undefined;
|
|
175
|
+
const script = backendScriptPath(__dirname, isDevelopment);
|
|
151
176
|
if (isDevelopment) {
|
|
152
177
|
interpreter = 'node';
|
|
153
|
-
// In development, resolve relative to __dirname of this file
|
|
154
|
-
script = path_1.default.resolve(__dirname, '../../backend/dist/main.js');
|
|
155
178
|
console.log(`[ServiceManager] Development mode detected (NODE_ENV=${process.env.NODE_ENV}).`);
|
|
156
179
|
}
|
|
157
180
|
else {
|
|
158
|
-
// In production, we use the command exposed by the npm package's bin field.
|
|
159
|
-
// pm2 will resolve this from the system's PATH.
|
|
160
|
-
script = 'repoburg-backend';
|
|
161
181
|
console.log(`[ServiceManager] Production mode detected (NODE_ENV=${process.env.NODE_ENV}).`);
|
|
162
182
|
}
|
|
163
183
|
console.log(`[ServiceManager] Starting backend service using script: ${script}`);
|
|
@@ -190,9 +210,8 @@ class ServiceManager {
|
|
|
190
210
|
return newService;
|
|
191
211
|
}
|
|
192
212
|
catch (error) {
|
|
193
|
-
const message = error instanceof Error ? error.message : 'An unknown error occurred';
|
|
194
213
|
console.error(`Failed to start service for ${projectPath}:`, error);
|
|
195
|
-
throw new Error(`Failed to start service: ${
|
|
214
|
+
throw new Error(`Failed to start service: ${describeError(error)}`);
|
|
196
215
|
}
|
|
197
216
|
}
|
|
198
217
|
async stop(name) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const serviceManager_1 = require("./serviceManager");
|
|
8
|
+
describe('backendScriptPath', () => {
|
|
9
|
+
it('resolves the repo build in development', () => {
|
|
10
|
+
expect((0, serviceManager_1.backendScriptPath)(path_1.default.join('/repo', 'daemon', 'src'), true)).toBe(path_1.default.join('/repo', 'backend', 'dist', 'main.js'));
|
|
11
|
+
});
|
|
12
|
+
it('resolves the package build absolutely in production', () => {
|
|
13
|
+
expect((0, serviceManager_1.backendScriptPath)(path_1.default.join('/pkg', 'repoburg', 'daemon', 'dist'), false)).toBe(path_1.default.join('/pkg', 'repoburg', 'backend', 'dist', 'src', 'main.js'));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('describeError', () => {
|
|
17
|
+
it('returns the message of an Error', () => {
|
|
18
|
+
expect((0, serviceManager_1.describeError)(new Error('boom'))).toBe('boom');
|
|
19
|
+
});
|
|
20
|
+
it('joins pm2 arrays of Errors', () => {
|
|
21
|
+
expect((0, serviceManager_1.describeError)([new Error('a'), new Error('b')])).toBe('a; b');
|
|
22
|
+
});
|
|
23
|
+
it('stringifies non-Error values', () => {
|
|
24
|
+
expect((0, serviceManager_1.describeError)('plain')).toBe('plain');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkspaceValidationError = void 0;
|
|
4
|
+
exports.validateWorkspacePath = validateWorkspacePath;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
/** Thrown when a workspace path fails start validation (HTTP 400 in the API). */
|
|
9
|
+
class WorkspaceValidationError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'WorkspaceValidationError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.WorkspaceValidationError = WorkspaceValidationError;
|
|
16
|
+
/** true when child is strictly under parent (not equal). */
|
|
17
|
+
function isUnder(child, parent) {
|
|
18
|
+
return child.startsWith(parent + path_1.sep);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validate a workspace path before a backend is started for it:
|
|
22
|
+
* - must be absolute,
|
|
23
|
+
* - must exist and be a directory,
|
|
24
|
+
* - must not be $HOME or a filesystem root,
|
|
25
|
+
* - must not overlap another service workspace (nested either way).
|
|
26
|
+
* Returns the resolved absolute path; throws WorkspaceValidationError
|
|
27
|
+
* with a clear message otherwise.
|
|
28
|
+
*/
|
|
29
|
+
function validateWorkspacePath(projectPath, existingServices) {
|
|
30
|
+
if (!projectPath || !(0, path_1.isAbsolute)(projectPath)) {
|
|
31
|
+
throw new WorkspaceValidationError(`Project path must be absolute (got "${projectPath}").`);
|
|
32
|
+
}
|
|
33
|
+
const resolved = (0, path_1.resolve)(projectPath);
|
|
34
|
+
let stat;
|
|
35
|
+
try {
|
|
36
|
+
stat = (0, fs_1.statSync)(resolved);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new WorkspaceValidationError(`Workspace directory does not exist: ${resolved}`);
|
|
40
|
+
}
|
|
41
|
+
if (!stat.isDirectory()) {
|
|
42
|
+
throw new WorkspaceValidationError(`Workspace path is not a directory: ${resolved}`);
|
|
43
|
+
}
|
|
44
|
+
if (resolved === (0, os_1.homedir)()) {
|
|
45
|
+
throw new WorkspaceValidationError(`Refusing to use the home directory as a workspace: ${resolved}`);
|
|
46
|
+
}
|
|
47
|
+
if (resolved === (0, path_1.parse)(resolved).root) {
|
|
48
|
+
throw new WorkspaceValidationError(`Refusing to use a filesystem root as a workspace: ${resolved}`);
|
|
49
|
+
}
|
|
50
|
+
for (const svc of existingServices) {
|
|
51
|
+
const svcPath = (0, path_1.resolve)(svc.path);
|
|
52
|
+
if (svcPath === resolved) {
|
|
53
|
+
throw new WorkspaceValidationError(`Workspace ${resolved} is already served by service "${svc.name}".`);
|
|
54
|
+
}
|
|
55
|
+
if (isUnder(resolved, svcPath)) {
|
|
56
|
+
throw new WorkspaceValidationError(`Workspace ${resolved} is nested inside existing service "${svc.name}" (${svcPath}).`);
|
|
57
|
+
}
|
|
58
|
+
if (isUnder(svcPath, resolved)) {
|
|
59
|
+
throw new WorkspaceValidationError(`Workspace ${resolved} contains existing service "${svc.name}" (${svcPath}).`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return resolved;
|
|
63
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs_1 = require("fs");
|
|
4
|
+
const os_1 = require("os");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const workspace_validation_1 = require("./workspace-validation");
|
|
7
|
+
describe('validateWorkspacePath', () => {
|
|
8
|
+
let dir;
|
|
9
|
+
let otherDir;
|
|
10
|
+
let filePath;
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
dir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'ws-valid-'));
|
|
13
|
+
otherDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'ws-other-'));
|
|
14
|
+
(0, fs_1.mkdirSync)((0, path_1.join)(dir, 'child'));
|
|
15
|
+
filePath = (0, path_1.join)(dir, 'not-a-dir');
|
|
16
|
+
(0, fs_1.writeFileSync)(filePath, 'file');
|
|
17
|
+
});
|
|
18
|
+
afterAll(() => {
|
|
19
|
+
(0, fs_1.rmSync)(dir, { recursive: true, force: true });
|
|
20
|
+
(0, fs_1.rmSync)(otherDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
it('accepts an existing absolute directory and returns the resolved path', () => {
|
|
23
|
+
const resolved = (0, path_1.resolve)(dir);
|
|
24
|
+
expect((0, workspace_validation_1.validateWorkspacePath)(dir, [])).toBe(resolved);
|
|
25
|
+
});
|
|
26
|
+
it('normalizes redundant path segments', () => {
|
|
27
|
+
const messy = (0, path_1.join)(dir, 'a', '..', 'b', '..');
|
|
28
|
+
expect((0, workspace_validation_1.validateWorkspacePath)(messy, [])).toBe((0, path_1.resolve)(dir));
|
|
29
|
+
});
|
|
30
|
+
it('rejects non-absolute paths', () => {
|
|
31
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)('relative/path', [])).toThrow(workspace_validation_1.WorkspaceValidationError);
|
|
32
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)('', [])).toThrow(workspace_validation_1.WorkspaceValidationError);
|
|
33
|
+
});
|
|
34
|
+
it('rejects paths that do not exist', () => {
|
|
35
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)((0, path_1.join)(dir, 'missing'), [])).toThrow(/Workspace directory does not exist/);
|
|
36
|
+
});
|
|
37
|
+
it('rejects paths that are not directories', () => {
|
|
38
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)(filePath, [])).toThrow(/not a directory/);
|
|
39
|
+
});
|
|
40
|
+
it('rejects the home directory', () => {
|
|
41
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)((0, os_1.homedir)(), [])).toThrow(/home directory/);
|
|
42
|
+
});
|
|
43
|
+
it('rejects filesystem roots', () => {
|
|
44
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)((0, path_1.parse)(dir).root, [])).toThrow(/filesystem root/);
|
|
45
|
+
});
|
|
46
|
+
it('rejects a duplicate of an existing service workspace', () => {
|
|
47
|
+
const existing = [{ name: 'svc', path: otherDir }];
|
|
48
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)(otherDir, existing)).toThrow(/already served/);
|
|
49
|
+
});
|
|
50
|
+
it('rejects a workspace nested inside an existing service', () => {
|
|
51
|
+
const existing = [{ name: 'svc', path: dir }];
|
|
52
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)((0, path_1.join)(dir, 'child'), [])).not.toThrow();
|
|
53
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)((0, path_1.join)(dir, 'child'), existing)).toThrow(/nested inside/);
|
|
54
|
+
});
|
|
55
|
+
it('rejects a workspace that contains an existing service', () => {
|
|
56
|
+
const existing = [{ name: 'svc', path: (0, path_1.join)(dir, 'child') }];
|
|
57
|
+
expect(() => (0, workspace_validation_1.validateWorkspacePath)(dir, existing)).toThrow(/contains existing service/);
|
|
58
|
+
});
|
|
59
|
+
it('accepts a sibling of an existing service', () => {
|
|
60
|
+
const existing = [{ name: 'svc', path: otherDir }];
|
|
61
|
+
expect((0, workspace_validation_1.validateWorkspacePath)(dir, existing)).toBe((0, path_1.resolve)(dir));
|
|
62
|
+
});
|
|
63
|
+
});
|
package/daemon/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"start": "cross-env NODE_ENV=production node dist/index.js",
|
|
10
10
|
"dev": "NODE_ENV=development ts-node-dev --respawn --transpile-only src/index.ts",
|
|
11
|
-
"test": "jest"
|
|
11
|
+
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [],
|
|
14
14
|
"author": "Celal Ertug",
|