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,504 +0,0 @@
|
|
|
1
|
-
import type { FastifyInstance } from 'fastify';
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import { getDb } from '../db/index.ts';
|
|
4
|
-
import { getProvider } from '../providers/registry.ts';
|
|
5
|
-
import type { Session, SessionDetail, Turn, Item, DiscussionThread, Attachment } from '../db/schema.ts';
|
|
6
|
-
|
|
7
|
-
/** Raw DB row for turns — token_usage and attachments are JSON strings, not parsed; no items field. */
|
|
8
|
-
interface TurnRow extends Omit<Turn, 'token_usage' | 'items' | 'attachments'> {
|
|
9
|
-
token_usage: string | null;
|
|
10
|
-
attachments: string | null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Raw DB row for items — data is a JSON string, not parsed. */
|
|
14
|
-
interface ItemRow extends Omit<Item, 'data'> {
|
|
15
|
-
data: string | null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function registerSessionRoutes(fastify: FastifyInstance): Promise<void> {
|
|
19
|
-
// GET /api/sessions — list all sessions
|
|
20
|
-
fastify.get('/api/sessions', async () => {
|
|
21
|
-
const rows = getDb().prepare('SELECT * FROM sessions ORDER BY created_at DESC').all() as Session[];
|
|
22
|
-
return rows;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// GET /api/sessions/:id — session with turns + items
|
|
26
|
-
fastify.get<{ Params: { id: string } }>('/api/sessions/:id', async (request, reply) => {
|
|
27
|
-
const { id } = request.params;
|
|
28
|
-
const db = getDb();
|
|
29
|
-
|
|
30
|
-
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
|
|
31
|
-
if (!session) {
|
|
32
|
-
return reply.code(404).send({ error: 'Session not found' });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// If the session is rewound, only show turns up to and including the rewind point
|
|
36
|
-
const turnFilter = session.rewound_at_turn_id
|
|
37
|
-
? db.prepare('SELECT created_at FROM turns WHERE id = ?').get(session.rewound_at_turn_id) as { created_at: string } | undefined
|
|
38
|
-
: null;
|
|
39
|
-
|
|
40
|
-
const turnRows = turnFilter
|
|
41
|
-
? db
|
|
42
|
-
.prepare('SELECT * FROM turns WHERE session_id = ? AND created_at <= ? ORDER BY created_at ASC')
|
|
43
|
-
.all(id, turnFilter.created_at) as TurnRow[]
|
|
44
|
-
: db
|
|
45
|
-
.prepare('SELECT * FROM turns WHERE session_id = ? ORDER BY created_at ASC')
|
|
46
|
-
.all(id) as TurnRow[];
|
|
47
|
-
|
|
48
|
-
const itemRows = db
|
|
49
|
-
.prepare(
|
|
50
|
-
'SELECT * FROM items WHERE turn_id IN (SELECT id FROM turns WHERE session_id = ?' +
|
|
51
|
-
(turnFilter ? ' AND created_at <= ?' : '') +
|
|
52
|
-
') ORDER BY created_at ASC',
|
|
53
|
-
)
|
|
54
|
-
.all(...(turnFilter ? [id, turnFilter.created_at] : [id])) as ItemRow[];
|
|
55
|
-
|
|
56
|
-
// Parse item data from JSON string and group by turn_id
|
|
57
|
-
const itemsByTurn = new Map<string, Item[]>();
|
|
58
|
-
for (const row of itemRows) {
|
|
59
|
-
const item: Item = {
|
|
60
|
-
...row,
|
|
61
|
-
data: row.data ? JSON.parse(row.data) : null,
|
|
62
|
-
};
|
|
63
|
-
const list = itemsByTurn.get(item.turn_id) ?? [];
|
|
64
|
-
list.push(item);
|
|
65
|
-
itemsByTurn.set(item.turn_id, list);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const detail: SessionDetail = {
|
|
69
|
-
...session,
|
|
70
|
-
turns: turnRows.map((row) => ({
|
|
71
|
-
...row,
|
|
72
|
-
token_usage: row.token_usage ? JSON.parse(row.token_usage) : null,
|
|
73
|
-
attachments: row.attachments ? (JSON.parse(row.attachments) as Attachment[]) : null,
|
|
74
|
-
items: itemsByTurn.get(row.id) ?? [],
|
|
75
|
-
})),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Fetch discussion threads for this session and attach to the response
|
|
79
|
-
const threadRows = db
|
|
80
|
-
.prepare('SELECT * FROM discussion_threads WHERE session_id = ? ORDER BY created_at ASC')
|
|
81
|
-
.all(id) as DiscussionThread[];
|
|
82
|
-
|
|
83
|
-
if (threadRows.length > 0) {
|
|
84
|
-
detail.threads = threadRows.map((tr) => ({
|
|
85
|
-
...tr,
|
|
86
|
-
turns: detail.turns.filter((t) => t.thread_id === tr.id),
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Restore persisted context-window usage so the status-bar counter survives reload.
|
|
91
|
-
const cuRow = db.prepare('SELECT context_usage FROM sessions WHERE id = ?').get(id) as
|
|
92
|
-
| { context_usage: string | null }
|
|
93
|
-
| undefined;
|
|
94
|
-
if (cuRow?.context_usage) {
|
|
95
|
-
try {
|
|
96
|
-
detail.contextUsage = JSON.parse(cuRow.context_usage) as SessionDetail['contextUsage'];
|
|
97
|
-
} catch {
|
|
98
|
-
// Corrupt JSON — leave contextUsage unset.
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return detail;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// POST /api/sessions — create session
|
|
106
|
-
fastify.post<{
|
|
107
|
-
Body: { title?: string; runtime_type?: string; model?: string; model_reasoning_effort?: string; cwd?: string; runtime_config?: string };
|
|
108
|
-
}>('/api/sessions', {
|
|
109
|
-
schema: {
|
|
110
|
-
body: {
|
|
111
|
-
type: 'object',
|
|
112
|
-
properties: {
|
|
113
|
-
title: { type: 'string' },
|
|
114
|
-
runtime_type: { type: 'string' },
|
|
115
|
-
model: { type: ['string', 'null'] },
|
|
116
|
-
model_reasoning_effort: { type: ['string', 'null'] },
|
|
117
|
-
cwd: { type: ['string', 'null'] },
|
|
118
|
-
runtime_config: { type: ['string', 'null'] },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
}, async (request) => {
|
|
123
|
-
const {
|
|
124
|
-
title = null,
|
|
125
|
-
runtime_type = 'codex',
|
|
126
|
-
model = null,
|
|
127
|
-
model_reasoning_effort = null,
|
|
128
|
-
cwd = null,
|
|
129
|
-
runtime_config = null,
|
|
130
|
-
} = request.body || {};
|
|
131
|
-
const id = uuidv4();
|
|
132
|
-
const now = new Date().toISOString();
|
|
133
|
-
|
|
134
|
-
getDb()
|
|
135
|
-
.prepare(
|
|
136
|
-
`INSERT INTO sessions (id, title, runtime_type, model, model_reasoning_effort, cwd, external_thread_id, status, is_running, created_at, updated_at, runtime_config)
|
|
137
|
-
VALUES (?, ?, ?, ?, ?, ?, NULL, 'active', 0, ?, ?, ?)`,
|
|
138
|
-
)
|
|
139
|
-
.run(id, title, runtime_type, model, model_reasoning_effort, cwd, now, now, runtime_config);
|
|
140
|
-
|
|
141
|
-
const session = getDb().prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
|
|
142
|
-
return session;
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// DELETE /api/sessions/:id — delete session (cascade)
|
|
146
|
-
fastify.delete<{ Params: { id: string } }>('/api/sessions/:id', async (request, reply) => {
|
|
147
|
-
const { id } = request.params;
|
|
148
|
-
const db = getDb();
|
|
149
|
-
|
|
150
|
-
const existing = db.prepare('SELECT id FROM sessions WHERE id = ?').get(id);
|
|
151
|
-
if (!existing) {
|
|
152
|
-
return reply.code(404).send({ error: 'Session not found' });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Delete items first (turns cascade via FK, but better-sqlite3 needs explicit cascade if pragma is set)
|
|
156
|
-
db.prepare('DELETE FROM items WHERE turn_id IN (SELECT id FROM turns WHERE session_id = ?)').run(id);
|
|
157
|
-
db.prepare('DELETE FROM turns WHERE session_id = ?').run(id);
|
|
158
|
-
db.prepare('DELETE FROM sessions WHERE id = ?').run(id);
|
|
159
|
-
|
|
160
|
-
return { success: true };
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// PATCH /api/sessions/:id — update session (title, lifecycle, has_unreviewed, model, model_reasoning_effort, runtime_config)
|
|
164
|
-
fastify.patch<{
|
|
165
|
-
Params: { id: string };
|
|
166
|
-
Body: {
|
|
167
|
-
title?: string;
|
|
168
|
-
lifecycle?: string;
|
|
169
|
-
has_unreviewed?: number;
|
|
170
|
-
model?: string | null;
|
|
171
|
-
model_reasoning_effort?: string | null;
|
|
172
|
-
runtime_config?: string | null;
|
|
173
|
-
runtime_type?: string;
|
|
174
|
-
};
|
|
175
|
-
}>('/api/sessions/:id', {
|
|
176
|
-
schema: {
|
|
177
|
-
body: {
|
|
178
|
-
type: 'object',
|
|
179
|
-
properties: {
|
|
180
|
-
title: { type: 'string' },
|
|
181
|
-
lifecycle: { type: 'string' },
|
|
182
|
-
has_unreviewed: { type: 'number' },
|
|
183
|
-
model: { type: ['string', 'null'] },
|
|
184
|
-
model_reasoning_effort: { type: ['string', 'null'] },
|
|
185
|
-
runtime_config: { type: ['string', 'null'] },
|
|
186
|
-
runtime_type: { type: 'string' },
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
}, async (request, reply) => {
|
|
191
|
-
const { id } = request.params;
|
|
192
|
-
const { title, lifecycle, has_unreviewed, model, model_reasoning_effort, runtime_config, runtime_type } = request.body || {};
|
|
193
|
-
const db = getDb();
|
|
194
|
-
|
|
195
|
-
const existing = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
|
|
196
|
-
if (!existing) {
|
|
197
|
-
return reply.code(404).send({ error: 'Session not found' });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Block provider switch while a turn is running
|
|
201
|
-
if (runtime_type !== undefined && runtime_type !== existing.runtime_type && existing.is_running) {
|
|
202
|
-
return reply.code(409).send({ error: 'Cannot switch provider while a turn is running' });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const now = new Date().toISOString();
|
|
206
|
-
const updates: string[] = ['updated_at = ?'];
|
|
207
|
-
const params: (string | number | null)[] = [now];
|
|
208
|
-
|
|
209
|
-
let providerChanged = false;
|
|
210
|
-
|
|
211
|
-
if (title !== undefined) {
|
|
212
|
-
updates.push('title = ?');
|
|
213
|
-
params.push(title);
|
|
214
|
-
}
|
|
215
|
-
if (lifecycle !== undefined) {
|
|
216
|
-
updates.push('lifecycle = ?');
|
|
217
|
-
params.push(lifecycle);
|
|
218
|
-
}
|
|
219
|
-
if (has_unreviewed !== undefined) {
|
|
220
|
-
updates.push('has_unreviewed = ?');
|
|
221
|
-
params.push(has_unreviewed);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Provider switch — clear model/reasoning if not explicitly provided
|
|
225
|
-
if (runtime_type !== undefined && runtime_type !== existing.runtime_type) {
|
|
226
|
-
providerChanged = true; // different provider can't resume the old thread
|
|
227
|
-
updates.push('runtime_type = ?');
|
|
228
|
-
params.push(runtime_type);
|
|
229
|
-
|
|
230
|
-
// Clear model if not explicitly set in this request
|
|
231
|
-
if (model === undefined) {
|
|
232
|
-
updates.push('model = ?');
|
|
233
|
-
params.push(null);
|
|
234
|
-
}
|
|
235
|
-
// Clear reasoning if not explicitly set
|
|
236
|
-
if (model_reasoning_effort === undefined) {
|
|
237
|
-
updates.push('model_reasoning_effort = ?');
|
|
238
|
-
params.push(null);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Model and reasoning changes update DB columns but do NOT clear the thread.
|
|
243
|
-
// The Claude SDK supports query({ resume: sessionId, model: newModel }) —
|
|
244
|
-
// changing the model within the same provider preserves conversation context.
|
|
245
|
-
// In streaming-input mode, also call query.setModel() on the live session
|
|
246
|
-
// so the CLI switches models without restarting.
|
|
247
|
-
if (model !== undefined) {
|
|
248
|
-
updates.push('model = ?');
|
|
249
|
-
params.push(model);
|
|
250
|
-
// If the provider supports mid-session model switching and the model
|
|
251
|
-
// actually changed (same provider), update the live query.
|
|
252
|
-
if (model !== existing.model && !providerChanged) {
|
|
253
|
-
const provider = getProvider(existing.runtime_type);
|
|
254
|
-
if (provider.setModel) {
|
|
255
|
-
provider.setModel(id, model ?? '').catch((err) => {
|
|
256
|
-
console.error(`[patch] setModel failed for session ${id}:`, err);
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
if (model_reasoning_effort !== undefined) {
|
|
262
|
-
updates.push('model_reasoning_effort = ?');
|
|
263
|
-
params.push(model_reasoning_effort);
|
|
264
|
-
}
|
|
265
|
-
if (runtime_config !== undefined) {
|
|
266
|
-
updates.push('runtime_config = ?');
|
|
267
|
-
params.push(runtime_config);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
db.prepare(`UPDATE sessions SET ${updates.join(', ')} WHERE id = ?`).run(...params, id);
|
|
271
|
-
|
|
272
|
-
// Only clear the thread when the PROVIDER changes (different provider can't resume).
|
|
273
|
-
// Model/reasoning changes within the same provider keep the thread intact.
|
|
274
|
-
if (providerChanged && existing.external_thread_id) {
|
|
275
|
-
db.prepare('UPDATE sessions SET external_thread_id = NULL, rewound_at_turn_id = NULL, pre_rewind_thread_id = NULL, updated_at = ? WHERE id = ?').run(now, id);
|
|
276
|
-
// Use old provider to clear its in-memory thread state
|
|
277
|
-
getProvider(existing.runtime_type).clearThread(id);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
|
|
281
|
-
return session;
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// POST /api/sessions/:id/rewind — non-destructive rewind: hide turns after target, fork provider session
|
|
285
|
-
fastify.post<{ Params: { id: string }; Body: { turn_id: string } }>(
|
|
286
|
-
'/api/sessions/:id/rewind',
|
|
287
|
-
{
|
|
288
|
-
schema: {
|
|
289
|
-
body: {
|
|
290
|
-
type: 'object',
|
|
291
|
-
required: ['turn_id'],
|
|
292
|
-
properties: {
|
|
293
|
-
turn_id: { type: 'string' },
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
async (request, reply) => {
|
|
299
|
-
const { id } = request.params;
|
|
300
|
-
const { turn_id } = request.body || {};
|
|
301
|
-
if (!turn_id) {
|
|
302
|
-
return reply.code(400).send({ error: 'turn_id is required' });
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const db = getDb();
|
|
306
|
-
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
|
|
307
|
-
if (!session) {
|
|
308
|
-
return reply.code(404).send({ error: 'Session not found' });
|
|
309
|
-
}
|
|
310
|
-
if (session.is_running) {
|
|
311
|
-
return reply.code(409).send({ error: 'Cannot rewind while a turn is running' });
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const targetTurnFull = db.prepare('SELECT * FROM turns WHERE id = ? AND session_id = ?').get(turn_id, id) as
|
|
315
|
-
| Turn
|
|
316
|
-
| undefined;
|
|
317
|
-
if (!targetTurnFull) {
|
|
318
|
-
return reply.code(404).send({ error: 'Turn not found' });
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const now = new Date().toISOString();
|
|
322
|
-
|
|
323
|
-
// Mark the session as rewound (non-destructive — turns are NOT deleted, just hidden)
|
|
324
|
-
// Save the original external_thread_id only on the first rewind (don't overwrite on subsequent rewinds)
|
|
325
|
-
// Use explicit two-step logic instead of COALESCE to avoid any evaluation-order ambiguity
|
|
326
|
-
if (!session.pre_rewind_thread_id && session.external_thread_id) {
|
|
327
|
-
db.prepare(
|
|
328
|
-
'UPDATE sessions SET rewound_at_turn_id = ?, pre_rewind_thread_id = ?, updated_at = ? WHERE id = ?',
|
|
329
|
-
).run(turn_id, session.external_thread_id, now, id);
|
|
330
|
-
console.log(`[rewind] saved pre_rewind_thread_id=${session.external_thread_id} for session ${id}`);
|
|
331
|
-
} else {
|
|
332
|
-
db.prepare(
|
|
333
|
-
'UPDATE sessions SET rewound_at_turn_id = ?, updated_at = ? WHERE id = ?',
|
|
334
|
-
).run(turn_id, now, id);
|
|
335
|
-
console.log(`[rewind] pre_rewind_thread_id already set (${session.pre_rewind_thread_id}) or external_thread_id is null for session ${id}`);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Fork the provider-native session so the provider has conversation context up to the target turn
|
|
339
|
-
const provider = getProvider(session.runtime_type);
|
|
340
|
-
if (provider.forkSession) {
|
|
341
|
-
try {
|
|
342
|
-
const forkedId = await provider.forkSession(session, targetTurnFull);
|
|
343
|
-
if (forkedId) {
|
|
344
|
-
db.prepare('UPDATE sessions SET external_thread_id = ?, updated_at = ? WHERE id = ?')
|
|
345
|
-
.run(forkedId, now, id);
|
|
346
|
-
console.log(`[rewind] forked to ${forkedId} for session ${id}`);
|
|
347
|
-
} else {
|
|
348
|
-
console.warn(`[rewind] forkSession returned null for session ${id} (external_thread_id=${session.external_thread_id}, upToMessageId=${targetTurnFull.external_turn_id})`);
|
|
349
|
-
}
|
|
350
|
-
} catch (err) {
|
|
351
|
-
console.error(`[rewind] forkSession threw for session ${id}:`, err);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Clear in-memory thread entry (the Claude session ID changed)
|
|
356
|
-
provider.clearThread(id);
|
|
357
|
-
|
|
358
|
-
const updated = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
|
|
359
|
-
return updated;
|
|
360
|
-
},
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
// POST /api/sessions/:id/restore — undo rewind: show all turns again, restore original thread
|
|
364
|
-
fastify.post<{ Params: { id: string } }>('/api/sessions/:id/restore', async (request, reply) => {
|
|
365
|
-
const { id } = request.params;
|
|
366
|
-
const db = getDb();
|
|
367
|
-
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
|
|
368
|
-
if (!session) {
|
|
369
|
-
return reply.code(404).send({ error: 'Session not found' });
|
|
370
|
-
}
|
|
371
|
-
if (!session.rewound_at_turn_id) {
|
|
372
|
-
return reply.code(409).send({ error: 'Session is not rewound' });
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const now = new Date().toISOString();
|
|
376
|
-
// Restore the original external_thread_id and clear rewind markers
|
|
377
|
-
db.prepare(
|
|
378
|
-
'UPDATE sessions SET rewound_at_turn_id = NULL, pre_rewind_thread_id = NULL, external_thread_id = COALESCE(pre_rewind_thread_id, external_thread_id), updated_at = ? WHERE id = ?',
|
|
379
|
-
).run(now, id);
|
|
380
|
-
|
|
381
|
-
// Clear in-memory thread entry (the Claude session ID changed back)
|
|
382
|
-
getProvider(session.runtime_type).clearThread(id);
|
|
383
|
-
|
|
384
|
-
const updated = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
|
|
385
|
-
return updated;
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
// POST /api/sessions/:id/branch — create new session with turns up to and including target turn
|
|
389
|
-
fastify.post<{ Params: { id: string }; Body: { turn_id: string; title?: string } }>(
|
|
390
|
-
'/api/sessions/:id/branch',
|
|
391
|
-
{
|
|
392
|
-
schema: {
|
|
393
|
-
body: {
|
|
394
|
-
type: 'object',
|
|
395
|
-
required: ['turn_id'],
|
|
396
|
-
properties: {
|
|
397
|
-
turn_id: { type: 'string' },
|
|
398
|
-
title: { type: 'string' },
|
|
399
|
-
},
|
|
400
|
-
},
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
async (request, reply) => {
|
|
404
|
-
const { id } = request.params;
|
|
405
|
-
const { turn_id, title } = request.body || {};
|
|
406
|
-
if (!turn_id) {
|
|
407
|
-
return reply.code(400).send({ error: 'turn_id is required' });
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const db = getDb();
|
|
411
|
-
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
|
|
412
|
-
if (!session) {
|
|
413
|
-
return reply.code(404).send({ error: 'Session not found' });
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const targetTurn = db.prepare('SELECT created_at FROM turns WHERE id = ? AND session_id = ?').get(turn_id, id) as
|
|
417
|
-
{ created_at: string } | undefined;
|
|
418
|
-
if (!targetTurn) {
|
|
419
|
-
return reply.code(404).send({ error: 'Turn not found' });
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Create new session
|
|
423
|
-
const newSessionId = uuidv4();
|
|
424
|
-
const now = new Date().toISOString();
|
|
425
|
-
const newTitle = title || `Branch of ${session.title || session.id.slice(0, 8)}`;
|
|
426
|
-
|
|
427
|
-
db.prepare(
|
|
428
|
-
`INSERT INTO sessions (id, title, runtime_type, model, model_reasoning_effort, cwd, external_thread_id, status, is_running, lifecycle, has_unreviewed, created_at, updated_at, runtime_config)
|
|
429
|
-
VALUES (?, ?, ?, ?, ?, ?, NULL, 'active', 0, 'working', 0, ?, ?, ?)`,
|
|
430
|
-
).run(
|
|
431
|
-
newSessionId,
|
|
432
|
-
newTitle,
|
|
433
|
-
session.runtime_type,
|
|
434
|
-
session.model,
|
|
435
|
-
session.model_reasoning_effort,
|
|
436
|
-
session.cwd,
|
|
437
|
-
now,
|
|
438
|
-
now,
|
|
439
|
-
session.runtime_config,
|
|
440
|
-
);
|
|
441
|
-
|
|
442
|
-
// Copy turns up to and including the target turn
|
|
443
|
-
const turnRows = db
|
|
444
|
-
.prepare('SELECT * FROM turns WHERE session_id = ? AND created_at <= ? ORDER BY created_at ASC')
|
|
445
|
-
.all(id, targetTurn.created_at) as TurnRow[];
|
|
446
|
-
|
|
447
|
-
for (const turn of turnRows) {
|
|
448
|
-
const newTurnId = uuidv4();
|
|
449
|
-
db.prepare(
|
|
450
|
-
`INSERT INTO turns (id, session_id, user_prompt, agent_response, thoughts, token_usage, external_turn_id, status, error, attachments, created_at, updated_at)
|
|
451
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
452
|
-
).run(
|
|
453
|
-
newTurnId,
|
|
454
|
-
newSessionId,
|
|
455
|
-
turn.user_prompt,
|
|
456
|
-
turn.agent_response,
|
|
457
|
-
turn.thoughts,
|
|
458
|
-
turn.token_usage,
|
|
459
|
-
turn.external_turn_id,
|
|
460
|
-
turn.status,
|
|
461
|
-
turn.error,
|
|
462
|
-
turn.attachments,
|
|
463
|
-
turn.created_at,
|
|
464
|
-
turn.updated_at,
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
// Copy items for this turn
|
|
468
|
-
const itemRows = db
|
|
469
|
-
.prepare('SELECT * FROM items WHERE turn_id = ? ORDER BY created_at ASC')
|
|
470
|
-
.all(turn.id) as ItemRow[];
|
|
471
|
-
for (const item of itemRows) {
|
|
472
|
-
const newItemId = uuidv4();
|
|
473
|
-
db.prepare(
|
|
474
|
-
`INSERT INTO items (id, turn_id, external_item_id, type, data, status, created_at, updated_at)
|
|
475
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
476
|
-
).run(
|
|
477
|
-
newItemId,
|
|
478
|
-
newTurnId,
|
|
479
|
-
item.external_item_id,
|
|
480
|
-
item.type,
|
|
481
|
-
item.data,
|
|
482
|
-
item.status,
|
|
483
|
-
item.created_at,
|
|
484
|
-
item.updated_at,
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Fork the provider-native session so the provider has conversation context
|
|
490
|
-
const provider = getProvider(session.runtime_type);
|
|
491
|
-
if (provider.forkSession) {
|
|
492
|
-
const targetTurnFull = db.prepare('SELECT * FROM turns WHERE id = ?').get(turn_id) as Turn;
|
|
493
|
-
const forkedId = await provider.forkSession(session, targetTurnFull);
|
|
494
|
-
if (forkedId) {
|
|
495
|
-
db.prepare('UPDATE sessions SET external_thread_id = ?, updated_at = ? WHERE id = ?')
|
|
496
|
-
.run(forkedId, now, newSessionId);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const newSession = db.prepare('SELECT * FROM sessions WHERE id = ?').get(newSessionId) as Session;
|
|
501
|
-
return newSession;
|
|
502
|
-
},
|
|
503
|
-
);
|
|
504
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import Fastify, { type FastifyInstance } from 'fastify';
|
|
3
|
-
|
|
4
|
-
// Mock shared-app-server to return a mock client
|
|
5
|
-
const mockRequest = vi.fn();
|
|
6
|
-
|
|
7
|
-
vi.mock('../codex/shared-app-server.ts', () => ({
|
|
8
|
-
getSharedAppServer: vi.fn(() => ({
|
|
9
|
-
request: mockRequest,
|
|
10
|
-
isClosed: vi.fn(() => false),
|
|
11
|
-
initialize: vi.fn(() => Promise.resolve()),
|
|
12
|
-
close: vi.fn(() => Promise.resolve()),
|
|
13
|
-
})),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
// Mock node:fs for readFileSync used in GET /api/skills/:name
|
|
17
|
-
vi.mock('node:fs', () => ({
|
|
18
|
-
readFileSync: vi.fn(),
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
|
-
import { registerSkillRoutes } from './skills.ts';
|
|
22
|
-
import { readFileSync } from 'node:fs';
|
|
23
|
-
import type { SkillsListResult } from '../codex/app-server-types.ts';
|
|
24
|
-
|
|
25
|
-
const MOCK_SKILLS_RESULT: SkillsListResult = {
|
|
26
|
-
data: [
|
|
27
|
-
{
|
|
28
|
-
cwd: '/tmp',
|
|
29
|
-
skills: [
|
|
30
|
-
{
|
|
31
|
-
name: 'cloudflare',
|
|
32
|
-
description: 'Cloudflare skill',
|
|
33
|
-
path: '/home/.codex/skills/cloudflare/SKILL.md',
|
|
34
|
-
scope: 'user',
|
|
35
|
-
enabled: true,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'imagegen',
|
|
39
|
-
description: 'Image generation skill',
|
|
40
|
-
path: '/home/.codex/skills/.system/imagegen/SKILL.md',
|
|
41
|
-
scope: 'system',
|
|
42
|
-
enabled: true,
|
|
43
|
-
interface: {
|
|
44
|
-
displayName: 'Image Gen',
|
|
45
|
-
shortDescription: 'Generate images',
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
errors: [],
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
describe('Skill routes', () => {
|
|
55
|
-
let app: FastifyInstance;
|
|
56
|
-
|
|
57
|
-
beforeEach(async () => {
|
|
58
|
-
vi.clearAllMocks();
|
|
59
|
-
mockRequest.mockResolvedValue(MOCK_SKILLS_RESULT);
|
|
60
|
-
app = Fastify();
|
|
61
|
-
await registerSkillRoutes(app);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('GET /api/skills', () => {
|
|
65
|
-
it('returns skills from app-server', async () => {
|
|
66
|
-
const res = await app.inject({ method: 'GET', url: '/api/skills' });
|
|
67
|
-
expect(res.statusCode).toBe(200);
|
|
68
|
-
const skills = res.json();
|
|
69
|
-
expect(skills).toHaveLength(2);
|
|
70
|
-
expect(skills[0].name).toBe('cloudflare');
|
|
71
|
-
expect(skills[0].type).toBe('skill');
|
|
72
|
-
expect(skills[0].metadata.scope).toBe('user');
|
|
73
|
-
expect(skills[1].name).toBe('Image Gen');
|
|
74
|
-
expect(skills[1].metadata.scope).toBe('system');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('calls skills/list with cwd from query', async () => {
|
|
78
|
-
await app.inject({ method: 'GET', url: '/api/skills?cwd=/custom/path' });
|
|
79
|
-
expect(mockRequest).toHaveBeenCalledWith('skills/list', { cwds: ['/custom/path'] });
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('defaults cwd to process.cwd()', async () => {
|
|
83
|
-
await app.inject({ method: 'GET', url: '/api/skills' });
|
|
84
|
-
expect(mockRequest).toHaveBeenCalledWith('skills/list', {
|
|
85
|
-
cwds: [process.cwd()],
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('returns empty array when app-server returns no skills', async () => {
|
|
90
|
-
mockRequest.mockResolvedValue({ data: [{ cwd: '/tmp', skills: [], errors: [] }] });
|
|
91
|
-
const res = await app.inject({ method: 'GET', url: '/api/skills' });
|
|
92
|
-
expect(res.statusCode).toBe(200);
|
|
93
|
-
expect(res.json()).toEqual([]);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('GET /api/skills/:name', () => {
|
|
98
|
-
it('returns 404 for unknown skill', async () => {
|
|
99
|
-
mockRequest.mockResolvedValue(MOCK_SKILLS_RESULT);
|
|
100
|
-
const res = await app.inject({ method: 'GET', url: '/api/skills/nonexistent' });
|
|
101
|
-
expect(res.statusCode).toBe(404);
|
|
102
|
-
expect(res.json()).toEqual({ error: 'Skill not found' });
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('returns SKILL.md content for known skill', async () => {
|
|
106
|
-
vi.mocked(readFileSync).mockReturnValue('# Cloudflare\nSkill content here');
|
|
107
|
-
const res = await app.inject({ method: 'GET', url: '/api/skills/cloudflare' });
|
|
108
|
-
expect(res.statusCode).toBe(200);
|
|
109
|
-
expect(res.json().name).toBe('cloudflare');
|
|
110
|
-
expect(res.json().content).toContain('# Cloudflare');
|
|
111
|
-
expect(res.json().path).toBe('/home/.codex/skills/cloudflare/SKILL.md');
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { FastifyInstance } from 'fastify';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import type { Extension } from '@agentpane/shared';
|
|
4
|
-
import { getSharedAppServer } from '../codex/shared-app-server.ts';
|
|
5
|
-
import type { SkillsListResult, AppServerSkill } from '../codex/app-server-types.ts';
|
|
6
|
-
|
|
7
|
-
/** Map an app-server skill to the shared Extension type. */
|
|
8
|
-
function mapSkill(skill: AppServerSkill): Extension {
|
|
9
|
-
return {
|
|
10
|
-
id: skill.name,
|
|
11
|
-
name: skill.interface?.displayName ?? skill.name,
|
|
12
|
-
description: skill.shortDescription ?? skill.interface?.shortDescription ?? skill.description,
|
|
13
|
-
type: 'skill',
|
|
14
|
-
enabled: skill.enabled,
|
|
15
|
-
metadata: {
|
|
16
|
-
scope: skill.scope,
|
|
17
|
-
path: skill.path,
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Register skill routes. */
|
|
23
|
-
export async function registerSkillRoutes(fastify: FastifyInstance): Promise<void> {
|
|
24
|
-
// GET /api/skills — list all skills from the app-server
|
|
25
|
-
fastify.get<{ Querystring: { cwd?: string } }>('/api/skills', async (request) => {
|
|
26
|
-
const cwd = request.query.cwd ?? process.cwd();
|
|
27
|
-
const result = await getSharedAppServer().request<SkillsListResult>('skills/list', { cwds: [cwd] });
|
|
28
|
-
|
|
29
|
-
// Flatten all skills from all cwds in the response
|
|
30
|
-
const allSkills: Extension[] = [];
|
|
31
|
-
for (const entry of result.data) {
|
|
32
|
-
for (const skill of entry.skills) {
|
|
33
|
-
allSkills.push(mapSkill(skill));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return allSkills;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// GET /api/skills/:name — get full SKILL.md content
|
|
40
|
-
fastify.get<{ Params: { name: string }; Querystring: { cwd?: string } }>(
|
|
41
|
-
'/api/skills/:name',
|
|
42
|
-
async (request, reply) => {
|
|
43
|
-
const { name } = request.params;
|
|
44
|
-
const cwd = request.query.cwd ?? process.cwd();
|
|
45
|
-
|
|
46
|
-
// Fetch skills list to find the path for this skill
|
|
47
|
-
const result = await getSharedAppServer().request<SkillsListResult>('skills/list', { cwds: [cwd] });
|
|
48
|
-
|
|
49
|
-
let skillPath: string | null = null;
|
|
50
|
-
for (const entry of result.data) {
|
|
51
|
-
const skill = entry.skills.find((s) => s.name === name);
|
|
52
|
-
if (skill) {
|
|
53
|
-
skillPath = skill.path;
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!skillPath) {
|
|
59
|
-
return reply.code(404).send({ error: 'Skill not found' });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const content = readFileSync(skillPath, 'utf-8');
|
|
64
|
-
return { name, content, path: skillPath };
|
|
65
|
-
} catch {
|
|
66
|
-
return reply.code(500).send({ error: 'Failed to read skill' });
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
);
|
|
70
|
-
}
|