codex-linux 1.0.0
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/.claude/settings.local.json +10 -0
- package/.eslintrc.json +27 -0
- package/.github/workflows/ci.yml +156 -0
- package/.huskyrc +7 -0
- package/.lintstagedrc +13 -0
- package/.prettierrc +12 -0
- package/CLAUDE.md +163 -0
- package/DESIGN_SUPERIOR.md +73 -0
- package/Dockerfile +64 -0
- package/INSTALLATION.md +152 -0
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/assets/skills/code-review/instructions.md +102 -0
- package/assets/skills/code-review/skill.yaml +15 -0
- package/assets/skills/refactoring/instructions.md +149 -0
- package/assets/skills/refactoring/skill.yaml +15 -0
- package/assets/skills/testing/skill.yaml +15 -0
- package/commitlint.config.js +23 -0
- package/dist/main/DatabaseManager.js +763 -0
- package/dist/main/DatabaseManager.js.map +1 -0
- package/dist/main/SettingsManager.js +61 -0
- package/dist/main/SettingsManager.js.map +1 -0
- package/dist/main/agents/AgentOrchestrator.js +787 -0
- package/dist/main/agents/AgentOrchestrator.js.map +1 -0
- package/dist/main/agents/AgentSDK.js +219 -0
- package/dist/main/agents/AgentSDK.js.map +1 -0
- package/dist/main/agents/AgentTools.js +348 -0
- package/dist/main/agents/AgentTools.js.map +1 -0
- package/dist/main/agents/CodeIndex.js +233 -0
- package/dist/main/agents/CodeIndex.js.map +1 -0
- package/dist/main/agents/EmbeddingService.js +80 -0
- package/dist/main/agents/EmbeddingService.js.map +1 -0
- package/dist/main/agents/NativeToolCalling.js +206 -0
- package/dist/main/agents/NativeToolCalling.js.map +1 -0
- package/dist/main/api/APIServer.js +278 -0
- package/dist/main/api/APIServer.js.map +1 -0
- package/dist/main/api/RateLimiter.js +138 -0
- package/dist/main/api/RateLimiter.js.map +1 -0
- package/dist/main/api/WebSocketManager.js +300 -0
- package/dist/main/api/WebSocketManager.js.map +1 -0
- package/dist/main/assistant/ContextOptimizer.js +192 -0
- package/dist/main/assistant/ContextOptimizer.js.map +1 -0
- package/dist/main/assistant/PredictedOutputManager.js +172 -0
- package/dist/main/assistant/PredictedOutputManager.js.map +1 -0
- package/dist/main/assistant/PromptCacheManager.js +193 -0
- package/dist/main/assistant/PromptCacheManager.js.map +1 -0
- package/dist/main/assistant/PromptOptimizer.js +626 -0
- package/dist/main/assistant/PromptOptimizer.js.map +1 -0
- package/dist/main/assistant/SmartCodeAssistant.js +224 -0
- package/dist/main/assistant/SmartCodeAssistant.js.map +1 -0
- package/dist/main/auth/SessionManager.js +300 -0
- package/dist/main/auth/SessionManager.js.map +1 -0
- package/dist/main/automations/AdvancedWebhookSystem.js +212 -0
- package/dist/main/automations/AdvancedWebhookSystem.js.map +1 -0
- package/dist/main/automations/AutomationScheduler.js +269 -0
- package/dist/main/automations/AutomationScheduler.js.map +1 -0
- package/dist/main/automations/BatchProcessingSystem.js +159 -0
- package/dist/main/automations/BatchProcessingSystem.js.map +1 -0
- package/dist/main/automations/BrowserAutomationManager.js +195 -0
- package/dist/main/automations/BrowserAutomationManager.js.map +1 -0
- package/dist/main/automations/GitHubActionsManager.js +129 -0
- package/dist/main/automations/GitHubActionsManager.js.map +1 -0
- package/dist/main/automations/GitLabCIManager.js +122 -0
- package/dist/main/automations/GitLabCIManager.js.map +1 -0
- package/dist/main/automations/PriorityQueueManager.js +240 -0
- package/dist/main/automations/PriorityQueueManager.js.map +1 -0
- package/dist/main/background/BackgroundModeManager.js +117 -0
- package/dist/main/background/BackgroundModeManager.js.map +1 -0
- package/dist/main/backup/BackupManager.js +254 -0
- package/dist/main/backup/BackupManager.js.map +1 -0
- package/dist/main/backup/MigrationManager.js +114 -0
- package/dist/main/backup/MigrationManager.js.map +1 -0
- package/dist/main/commands/SlashCommandManager.js +399 -0
- package/dist/main/commands/SlashCommandManager.js.map +1 -0
- package/dist/main/config/ClaudeMdParser.js +519 -0
- package/dist/main/config/ClaudeMdParser.js.map +1 -0
- package/dist/main/config/CustomizationManager.js +381 -0
- package/dist/main/config/CustomizationManager.js.map +1 -0
- package/dist/main/config/LaunchConfigManager.js +211 -0
- package/dist/main/config/LaunchConfigManager.js.map +1 -0
- package/dist/main/config/SettingsManager.js +166 -0
- package/dist/main/config/SettingsManager.js.map +1 -0
- package/dist/main/connectors/ConnectorManager.js +151 -0
- package/dist/main/connectors/ConnectorManager.js.map +1 -0
- package/dist/main/connectors/DatabaseConnector.js +222 -0
- package/dist/main/connectors/DatabaseConnector.js.map +1 -0
- package/dist/main/cowork/CoworkManager.js +324 -0
- package/dist/main/cowork/CoworkManager.js.map +1 -0
- package/dist/main/evals/AgentEvalFramework.js +538 -0
- package/dist/main/evals/AgentEvalFramework.js.map +1 -0
- package/dist/main/evals/GraderManager.js +285 -0
- package/dist/main/evals/GraderManager.js.map +1 -0
- package/dist/main/git/GitWorktreeManager.js +214 -0
- package/dist/main/git/GitWorktreeManager.js.map +1 -0
- package/dist/main/github/GitHubPRMonitor.js +244 -0
- package/dist/main/github/GitHubPRMonitor.js.map +1 -0
- package/dist/main/ide/ContinueInManager.js +181 -0
- package/dist/main/ide/ContinueInManager.js.map +1 -0
- package/dist/main/ide/IDEIntegration.js +277 -0
- package/dist/main/ide/IDEIntegration.js.map +1 -0
- package/dist/main/integrations/LinearManager.js +252 -0
- package/dist/main/integrations/LinearManager.js.map +1 -0
- package/dist/main/integrations/SlackBotManager.js +247 -0
- package/dist/main/integrations/SlackBotManager.js.map +1 -0
- package/dist/main/lsp/LSPManager.js +394 -0
- package/dist/main/lsp/LSPManager.js.map +1 -0
- package/dist/main/main.js +1087 -0
- package/dist/main/main.js.map +1 -0
- package/dist/main/mcp/MCPConfigurationManager.js +281 -0
- package/dist/main/mcp/MCPConfigurationManager.js.map +1 -0
- package/dist/main/mcp/MCPManager.js +710 -0
- package/dist/main/mcp/MCPManager.js.map +1 -0
- package/dist/main/mcp/MCPRegistry.js +272 -0
- package/dist/main/mcp/MCPRegistry.js.map +1 -0
- package/dist/main/monitoring/ErrorRecoveryManager.js +268 -0
- package/dist/main/monitoring/ErrorRecoveryManager.js.map +1 -0
- package/dist/main/monitoring/ErrorTracker.js +57 -0
- package/dist/main/monitoring/ErrorTracker.js.map +1 -0
- package/dist/main/monitoring/MetricsCollector.js +155 -0
- package/dist/main/monitoring/MetricsCollector.js.map +1 -0
- package/dist/main/monitoring/TraceGradingSystem.js +148 -0
- package/dist/main/monitoring/TraceGradingSystem.js.map +1 -0
- package/dist/main/notifications/NotificationManager.js +67 -0
- package/dist/main/notifications/NotificationManager.js.map +1 -0
- package/dist/main/pair/AIPairProgramming.js +200 -0
- package/dist/main/pair/AIPairProgramming.js.map +1 -0
- package/dist/main/plugins/PluginManager.js +222 -0
- package/dist/main/plugins/PluginManager.js.map +1 -0
- package/dist/main/plugins/PluginMarketplace.js +237 -0
- package/dist/main/plugins/PluginMarketplace.js.map +1 -0
- package/dist/main/preload.js +189 -0
- package/dist/main/preload.js.map +1 -0
- package/dist/main/preview/PreviewSessionManager.js +170 -0
- package/dist/main/preview/PreviewSessionManager.js.map +1 -0
- package/dist/main/providers/AIProviderManager.js +327 -0
- package/dist/main/providers/AIProviderManager.js.map +1 -0
- package/dist/main/providers/FineTuningManager.js +276 -0
- package/dist/main/providers/FineTuningManager.js.map +1 -0
- package/dist/main/providers/FreeModelsProvider.js +1104 -0
- package/dist/main/providers/FreeModelsProvider.js.map +1 -0
- package/dist/main/realtime/RealtimeManager.js +116 -0
- package/dist/main/realtime/RealtimeManager.js.map +1 -0
- package/dist/main/remote/CloudEnvironmentManager.js +232 -0
- package/dist/main/remote/CloudEnvironmentManager.js.map +1 -0
- package/dist/main/remote/RemoteSessionManager.js +255 -0
- package/dist/main/remote/RemoteSessionManager.js.map +1 -0
- package/dist/main/search/DeepResearchManager.js +335 -0
- package/dist/main/search/DeepResearchManager.js.map +1 -0
- package/dist/main/search/WebSearchIntegration.js +147 -0
- package/dist/main/search/WebSearchIntegration.js.map +1 -0
- package/dist/main/security/AdminConsoleManager.js +223 -0
- package/dist/main/security/AdminConsoleManager.js.map +1 -0
- package/dist/main/security/AuditLogger.js +136 -0
- package/dist/main/security/AuditLogger.js.map +1 -0
- package/dist/main/security/PermissionManager.js +144 -0
- package/dist/main/security/PermissionManager.js.map +1 -0
- package/dist/main/security/SSOManager.js +173 -0
- package/dist/main/security/SSOManager.js.map +1 -0
- package/dist/main/security/SecurityManager.js +152 -0
- package/dist/main/security/SecurityManager.js.map +1 -0
- package/dist/main/skills/SkillsManager.js +223 -0
- package/dist/main/skills/SkillsManager.js.map +1 -0
- package/dist/main/ssh/SSHManager.js +65 -0
- package/dist/main/ssh/SSHManager.js.map +1 -0
- package/dist/main/streaming/StreamingManager.js +225 -0
- package/dist/main/streaming/StreamingManager.js.map +1 -0
- package/dist/main/sync/CloudSyncManager.js +422 -0
- package/dist/main/sync/CloudSyncManager.js.map +1 -0
- package/dist/main/types.js +28 -0
- package/dist/main/types.js.map +1 -0
- package/dist/main/verification/AutoVerifyManager.js +235 -0
- package/dist/main/verification/AutoVerifyManager.js.map +1 -0
- package/dist/main/vision/ComputerUseManager.js +376 -0
- package/dist/main/vision/ComputerUseManager.js.map +1 -0
- package/dist/main/vision/ImageVideoGenerationManager.js +401 -0
- package/dist/main/vision/ImageVideoGenerationManager.js.map +1 -0
- package/dist/main/vision/VisionManager.js +172 -0
- package/dist/main/vision/VisionManager.js.map +1 -0
- package/dist/renderer/assets/main-DJlZQBCA.js +304 -0
- package/dist/renderer/assets/main-N33ZXEr8.css +1 -0
- package/dist/renderer/index.html +21 -0
- package/dist/renderer/manifest.json +42 -0
- package/dist/renderer/sw.ts +109 -0
- package/dist/shared/types.js +35 -0
- package/dist/shared/types.js.map +1 -0
- package/docker-compose.yml +65 -0
- package/docs/API.md +307 -0
- package/docs/USER_GUIDE.md +476 -0
- package/examples/plugins/sample-plugin/package.json +41 -0
- package/examples/plugins/sample-plugin/src/index.ts +75 -0
- package/index.html +20 -0
- package/jest.config.js +39 -0
- package/package.json +180 -0
- package/packages/cli/package.json +29 -0
- package/packages/cli/src/commands/agents.ts +199 -0
- package/packages/cli/src/commands/tasks.ts +61 -0
- package/packages/cli/src/index.ts +91 -0
- package/packages/cli/src/utils/api.ts +45 -0
- package/packages/cli/src/utils/config.ts +61 -0
- package/packages/npm-installer/bin/codex-linux +126 -0
- package/packages/npm-installer/lib/download.js +273 -0
- package/packages/npm-installer/package.json +42 -0
- package/packages/vscode-extension/package.json +167 -0
- package/packages/vscode-extension/src/api.ts +68 -0
- package/packages/vscode-extension/src/extension.ts +161 -0
- package/packages/vscode-extension/src/panels/chatPanel.ts +265 -0
- package/packages/vscode-extension/src/panels/createAgentPanel.ts +227 -0
- package/packages/vscode-extension/src/providers/agentsProvider.ts +80 -0
- package/postcss.config.js +6 -0
- package/public/manifest.json +42 -0
- package/public/sw.ts +109 -0
- package/scripts/install-dev.sh +103 -0
- package/scripts/install.sh +275 -0
- package/src/main/DatabaseManager.ts +950 -0
- package/src/main/SettingsManager.ts +63 -0
- package/src/main/agents/AgentOrchestrator.ts +930 -0
- package/src/main/agents/AgentSDK.ts +269 -0
- package/src/main/agents/AgentTools.ts +380 -0
- package/src/main/agents/CodeIndex.ts +240 -0
- package/src/main/agents/EmbeddingService.ts +88 -0
- package/src/main/agents/NativeToolCalling.ts +245 -0
- package/src/main/api/APIServer.ts +316 -0
- package/src/main/api/RateLimiter.ts +165 -0
- package/src/main/api/WebSocketManager.ts +398 -0
- package/src/main/assistant/ContextOptimizer.ts +214 -0
- package/src/main/assistant/PredictedOutputManager.ts +265 -0
- package/src/main/assistant/PromptCacheManager.ts +280 -0
- package/src/main/assistant/PromptOptimizer.ts +746 -0
- package/src/main/assistant/SmartCodeAssistant.ts +234 -0
- package/src/main/auth/SessionManager.ts +415 -0
- package/src/main/automations/AdvancedWebhookSystem.ts +281 -0
- package/src/main/automations/AutomationScheduler.ts +272 -0
- package/src/main/automations/BatchProcessingSystem.ts +207 -0
- package/src/main/automations/BrowserAutomationManager.ts +203 -0
- package/src/main/automations/GitHubActionsManager.ts +151 -0
- package/src/main/automations/GitLabCIManager.ts +206 -0
- package/src/main/automations/PriorityQueueManager.ts +328 -0
- package/src/main/background/BackgroundModeManager.ts +130 -0
- package/src/main/backup/BackupManager.ts +287 -0
- package/src/main/backup/MigrationManager.ts +132 -0
- package/src/main/commands/SlashCommandManager.ts +407 -0
- package/src/main/config/ClaudeMdParser.ts +539 -0
- package/src/main/config/CustomizationManager.ts +493 -0
- package/src/main/config/LaunchConfigManager.ts +212 -0
- package/src/main/config/SettingsManager.ts +163 -0
- package/src/main/connectors/ConnectorManager.ts +175 -0
- package/src/main/connectors/DatabaseConnector.ts +212 -0
- package/src/main/cowork/CoworkManager.ts +431 -0
- package/src/main/evals/AgentEvalFramework.ts +665 -0
- package/src/main/evals/GraderManager.ts +417 -0
- package/src/main/git/GitWorktreeManager.ts +211 -0
- package/src/main/github/GitHubPRMonitor.ts +317 -0
- package/src/main/ide/ContinueInManager.ts +180 -0
- package/src/main/ide/IDEIntegration.ts +288 -0
- package/src/main/integrations/LinearManager.ts +327 -0
- package/src/main/integrations/SlackBotManager.ts +312 -0
- package/src/main/lsp/LSPManager.ts +445 -0
- package/src/main/main.ts +1221 -0
- package/src/main/mcp/MCPConfigurationManager.ts +281 -0
- package/src/main/mcp/MCPManager.ts +799 -0
- package/src/main/mcp/MCPRegistry.ts +273 -0
- package/src/main/monitoring/ErrorRecoveryManager.ts +359 -0
- package/src/main/monitoring/ErrorTracker.ts +60 -0
- package/src/main/monitoring/MetricsCollector.ts +196 -0
- package/src/main/monitoring/TraceGradingSystem.ts +196 -0
- package/src/main/notifications/NotificationManager.ts +96 -0
- package/src/main/pair/AIPairProgramming.ts +290 -0
- package/src/main/plugins/PluginManager.ts +266 -0
- package/src/main/plugins/PluginMarketplace.ts +318 -0
- package/src/main/preload.ts +215 -0
- package/src/main/preview/PreviewSessionManager.ts +186 -0
- package/src/main/providers/AIProviderManager.ts +394 -0
- package/src/main/providers/FineTuningManager.ts +390 -0
- package/src/main/providers/FreeModelsProvider.ts +1156 -0
- package/src/main/realtime/RealtimeManager.ts +147 -0
- package/src/main/remote/CloudEnvironmentManager.ts +253 -0
- package/src/main/remote/RemoteSessionManager.ts +323 -0
- package/src/main/search/DeepResearchManager.ts +458 -0
- package/src/main/search/WebSearchIntegration.ts +203 -0
- package/src/main/security/AdminConsoleManager.ts +244 -0
- package/src/main/security/AuditLogger.ts +143 -0
- package/src/main/security/PermissionManager.ts +184 -0
- package/src/main/security/SSOManager.ts +241 -0
- package/src/main/security/SecurityManager.ts +139 -0
- package/src/main/skills/SkillsManager.ts +218 -0
- package/src/main/ssh/SSHManager.ts +86 -0
- package/src/main/streaming/StreamingManager.ts +306 -0
- package/src/main/sync/CloudSyncManager.ts +532 -0
- package/src/main/verification/AutoVerifyManager.ts +285 -0
- package/src/main/vision/ComputerUseManager.ts +475 -0
- package/src/main/vision/ImageVideoGenerationManager.ts +526 -0
- package/src/main/vision/VisionManager.ts +186 -0
- package/src/renderer/App.tsx +314 -0
- package/src/renderer/components/AdvancedSettingsPanel.tsx +225 -0
- package/src/renderer/components/AgentPanel.tsx +760 -0
- package/src/renderer/components/AppPreview.tsx +220 -0
- package/src/renderer/components/AuditTrailPanel.tsx +148 -0
- package/src/renderer/components/AutomationPanel.tsx +220 -0
- package/src/renderer/components/ChatInterface.tsx +595 -0
- package/src/renderer/components/ChatTab.tsx +296 -0
- package/src/renderer/components/CodeEditor.tsx +257 -0
- package/src/renderer/components/CodeReviewPanel.tsx +256 -0
- package/src/renderer/components/CodeWorkspace.tsx +192 -0
- package/src/renderer/components/CodebaseDashboard.tsx +295 -0
- package/src/renderer/components/ComputerUsePanel.tsx +262 -0
- package/src/renderer/components/ConnectorsPanel.tsx +471 -0
- package/src/renderer/components/ContextMenu.tsx +155 -0
- package/src/renderer/components/ContextUsageDisplay.tsx +248 -0
- package/src/renderer/components/CoworkPanel.tsx +415 -0
- package/src/renderer/components/DiffViewer.tsx +452 -0
- package/src/renderer/components/ErrorBoundary.tsx +273 -0
- package/src/renderer/components/ExtendedThinkingToggle.tsx +244 -0
- package/src/renderer/components/FileAttachments.tsx +247 -0
- package/src/renderer/components/FileExplorer.tsx +242 -0
- package/src/renderer/components/FileExplorerPanel.tsx +302 -0
- package/src/renderer/components/GitPanel.tsx +154 -0
- package/src/renderer/components/Header.tsx +113 -0
- package/src/renderer/components/MCPPanel.tsx +326 -0
- package/src/renderer/components/MentionAutocomplete.tsx +239 -0
- package/src/renderer/components/PermissionPanel.tsx +159 -0
- package/src/renderer/components/PermissionSelector.tsx +203 -0
- package/src/renderer/components/PluginMarketplace.tsx +325 -0
- package/src/renderer/components/PromptOptimizerPanel.tsx +399 -0
- package/src/renderer/components/SearchPanel.tsx +173 -0
- package/src/renderer/components/SearchReplace.tsx +284 -0
- package/src/renderer/components/SessionSidebar.tsx +367 -0
- package/src/renderer/components/SettingsPanel.tsx +426 -0
- package/src/renderer/components/Sidebar.tsx +100 -0
- package/src/renderer/components/SkillsPanel.tsx +245 -0
- package/src/renderer/components/SplitPane.tsx +173 -0
- package/src/renderer/components/Terminal.tsx +190 -0
- package/src/renderer/components/VoiceCommand.tsx +129 -0
- package/src/renderer/components/WorktreePanel.tsx +163 -0
- package/src/renderer/components/ui/AriaComponents.tsx +193 -0
- package/src/renderer/components/ui/Button.tsx +68 -0
- package/src/renderer/components/ui/Card.tsx +102 -0
- package/src/renderer/components/ui/Input.tsx +44 -0
- package/src/renderer/components/ui/Skeleton.tsx +55 -0
- package/src/renderer/components/ui/VirtualList.tsx +196 -0
- package/src/renderer/i18n/I18nProvider.tsx +101 -0
- package/src/renderer/i18n/de.ts +161 -0
- package/src/renderer/i18n/en.ts +163 -0
- package/src/renderer/i18n/es.ts +161 -0
- package/src/renderer/i18n/fr.ts +161 -0
- package/src/renderer/i18n/index.ts +44 -0
- package/src/renderer/index.css +129 -0
- package/src/renderer/lib/accessibility.tsx +287 -0
- package/src/renderer/lib/hooks.ts +304 -0
- package/src/renderer/lib/utils.ts +6 -0
- package/src/renderer/main.tsx +25 -0
- package/src/renderer/styles/minimalist.css +539 -0
- package/src/renderer/sw.ts +180 -0
- package/src/renderer/types.d.ts +138 -0
- package/src/shared/types.ts +813 -0
- package/supabase/schema.sql +234 -0
- package/tailwind.config.js +78 -0
- package/tests/e2e/package.json +15 -0
- package/tests/e2e/playwright.config.ts +31 -0
- package/tests/e2e/specs/app.spec.ts +194 -0
- package/tests/setup.ts +99 -0
- package/tests/unit/AgentOrchestrator.test.ts +274 -0
- package/tests/unit/DatabaseManager.test.ts +262 -0
- package/tests/unit/GitWorktreeManager.test.ts +150 -0
- package/tests/unit/SecurityManager.test.ts +110 -0
- package/tsconfig.main.json +22 -0
- package/tsconfig.renderer.json +27 -0
- package/vite.config.ts +28 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
import log from 'electron-log';
|
|
6
|
+
import { Agent, AgentMessage, AgentTask, CodeChange, Automation, Project, ChangeStatus, Checkpoint } from '../shared/types';
|
|
7
|
+
|
|
8
|
+
const DB_DIR = path.join(os.homedir(), '.config', 'codex');
|
|
9
|
+
const DB_PATH = path.join(DB_DIR, 'codex.db');
|
|
10
|
+
|
|
11
|
+
function safeJsonParse<T>(str: string | null | undefined, defaultValue: T): T {
|
|
12
|
+
if (!str) return defaultValue;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(str) as T;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
log.error('JSON parse error:', error);
|
|
17
|
+
return defaultValue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class DatabaseManager {
|
|
22
|
+
private db: Database.Database | null = null;
|
|
23
|
+
|
|
24
|
+
async initialize(): Promise<void> {
|
|
25
|
+
try {
|
|
26
|
+
// Ensure directory exists
|
|
27
|
+
await fs.promises.mkdir(DB_DIR, { recursive: true });
|
|
28
|
+
|
|
29
|
+
this.db = new Database(DB_PATH);
|
|
30
|
+
|
|
31
|
+
// Enable WAL mode for better concurrency
|
|
32
|
+
this.db.pragma('journal_mode = WAL');
|
|
33
|
+
|
|
34
|
+
// Create tables
|
|
35
|
+
this.createTables();
|
|
36
|
+
|
|
37
|
+
// Reset any "running" queue items from previous sessions
|
|
38
|
+
this.db.prepare("UPDATE agent_queue_items SET status = 'pending' WHERE status = 'running'").run();
|
|
39
|
+
|
|
40
|
+
log.info(`Database initialized at ${DB_PATH}`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
log.error('Failed to initialize database:', error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async close(): Promise<void> {
|
|
48
|
+
if (this.db) {
|
|
49
|
+
this.db.pragma('wal_checkpoint(TRUNCATE)');
|
|
50
|
+
this.db.close();
|
|
51
|
+
this.db = null;
|
|
52
|
+
log.info('Database connection closed');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private createTables(): void {
|
|
57
|
+
if (!this.db) return;
|
|
58
|
+
|
|
59
|
+
// Agents table
|
|
60
|
+
this.db.exec(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
62
|
+
id TEXT PRIMARY KEY,
|
|
63
|
+
name TEXT NOT NULL,
|
|
64
|
+
status TEXT NOT NULL,
|
|
65
|
+
project_path TEXT NOT NULL,
|
|
66
|
+
worktree_name TEXT NOT NULL,
|
|
67
|
+
provider_id TEXT NOT NULL,
|
|
68
|
+
model TEXT NOT NULL,
|
|
69
|
+
skills TEXT NOT NULL,
|
|
70
|
+
permission_mode TEXT NOT NULL DEFAULT 'ask',
|
|
71
|
+
created_at INTEGER NOT NULL,
|
|
72
|
+
updated_at INTEGER NOT NULL,
|
|
73
|
+
last_active_at INTEGER,
|
|
74
|
+
metadata TEXT
|
|
75
|
+
)
|
|
76
|
+
`);
|
|
77
|
+
|
|
78
|
+
// Agent queue items table
|
|
79
|
+
this.db.exec(`
|
|
80
|
+
CREATE TABLE IF NOT EXISTS agent_queue_items (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
agent_id TEXT NOT NULL,
|
|
83
|
+
type TEXT NOT NULL,
|
|
84
|
+
content TEXT NOT NULL,
|
|
85
|
+
status TEXT NOT NULL,
|
|
86
|
+
position INTEGER NOT NULL,
|
|
87
|
+
created_at INTEGER NOT NULL,
|
|
88
|
+
started_at INTEGER,
|
|
89
|
+
completed_at INTEGER,
|
|
90
|
+
error TEXT,
|
|
91
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
92
|
+
)
|
|
93
|
+
`);
|
|
94
|
+
|
|
95
|
+
// Agent messages table
|
|
96
|
+
this.db.exec(`
|
|
97
|
+
CREATE TABLE IF NOT EXISTS agent_messages (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
agent_id TEXT NOT NULL,
|
|
100
|
+
role TEXT NOT NULL,
|
|
101
|
+
content TEXT NOT NULL,
|
|
102
|
+
timestamp INTEGER NOT NULL,
|
|
103
|
+
metadata TEXT,
|
|
104
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
105
|
+
)
|
|
106
|
+
`);
|
|
107
|
+
|
|
108
|
+
// Agent tasks table
|
|
109
|
+
this.db.exec(`
|
|
110
|
+
CREATE TABLE IF NOT EXISTS agent_tasks (
|
|
111
|
+
id TEXT PRIMARY KEY,
|
|
112
|
+
agent_id TEXT NOT NULL,
|
|
113
|
+
description TEXT NOT NULL,
|
|
114
|
+
status TEXT NOT NULL,
|
|
115
|
+
progress REAL NOT NULL,
|
|
116
|
+
started_at INTEGER NOT NULL,
|
|
117
|
+
completed_at INTEGER,
|
|
118
|
+
result TEXT,
|
|
119
|
+
error TEXT,
|
|
120
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
121
|
+
)
|
|
122
|
+
`);
|
|
123
|
+
|
|
124
|
+
// Code changes table
|
|
125
|
+
this.db.exec(`
|
|
126
|
+
CREATE TABLE IF NOT EXISTS code_changes (
|
|
127
|
+
id TEXT PRIMARY KEY,
|
|
128
|
+
file_path TEXT NOT NULL,
|
|
129
|
+
original_content TEXT,
|
|
130
|
+
new_content TEXT,
|
|
131
|
+
diff TEXT NOT NULL,
|
|
132
|
+
agent_id TEXT NOT NULL,
|
|
133
|
+
task_id TEXT NOT NULL,
|
|
134
|
+
status TEXT NOT NULL,
|
|
135
|
+
created_at INTEGER NOT NULL,
|
|
136
|
+
reviewed_at INTEGER,
|
|
137
|
+
reviewed_by TEXT,
|
|
138
|
+
comment TEXT
|
|
139
|
+
)
|
|
140
|
+
`);
|
|
141
|
+
|
|
142
|
+
// Automations table
|
|
143
|
+
this.db.exec(`
|
|
144
|
+
CREATE TABLE IF NOT EXISTS automations (
|
|
145
|
+
id TEXT PRIMARY KEY,
|
|
146
|
+
name TEXT NOT NULL,
|
|
147
|
+
description TEXT,
|
|
148
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
149
|
+
trigger_type TEXT NOT NULL,
|
|
150
|
+
trigger_config TEXT NOT NULL,
|
|
151
|
+
actions TEXT NOT NULL,
|
|
152
|
+
created_at INTEGER NOT NULL,
|
|
153
|
+
updated_at INTEGER NOT NULL,
|
|
154
|
+
last_run_at INTEGER,
|
|
155
|
+
run_count INTEGER NOT NULL DEFAULT 0
|
|
156
|
+
)
|
|
157
|
+
`);
|
|
158
|
+
|
|
159
|
+
// Projects table
|
|
160
|
+
this.db.exec(`
|
|
161
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
162
|
+
id TEXT PRIMARY KEY,
|
|
163
|
+
name TEXT NOT NULL,
|
|
164
|
+
path TEXT NOT NULL UNIQUE,
|
|
165
|
+
git_remote TEXT,
|
|
166
|
+
created_at INTEGER NOT NULL,
|
|
167
|
+
updated_at INTEGER NOT NULL
|
|
168
|
+
)
|
|
169
|
+
`);
|
|
170
|
+
|
|
171
|
+
// Cowork sessions table
|
|
172
|
+
this.db.exec(`
|
|
173
|
+
CREATE TABLE IF NOT EXISTS cowork_sessions (
|
|
174
|
+
id TEXT PRIMARY KEY,
|
|
175
|
+
name TEXT NOT NULL,
|
|
176
|
+
agent_id TEXT NOT NULL,
|
|
177
|
+
status TEXT NOT NULL,
|
|
178
|
+
objective TEXT NOT NULL,
|
|
179
|
+
progress REAL NOT NULL DEFAULT 0,
|
|
180
|
+
created_at INTEGER NOT NULL,
|
|
181
|
+
updated_at INTEGER NOT NULL,
|
|
182
|
+
completed_at INTEGER,
|
|
183
|
+
logs TEXT NOT NULL DEFAULT '[]',
|
|
184
|
+
deliverables TEXT NOT NULL DEFAULT '[]',
|
|
185
|
+
auto_approve INTEGER NOT NULL DEFAULT 0,
|
|
186
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
187
|
+
)
|
|
188
|
+
`);
|
|
189
|
+
|
|
190
|
+
// Checkpoints table
|
|
191
|
+
this.db.exec(`
|
|
192
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
193
|
+
id TEXT PRIMARY KEY,
|
|
194
|
+
agent_id TEXT NOT NULL,
|
|
195
|
+
change_id TEXT NOT NULL,
|
|
196
|
+
file_path TEXT NOT NULL,
|
|
197
|
+
content TEXT NOT NULL,
|
|
198
|
+
created_at INTEGER NOT NULL,
|
|
199
|
+
restored_at INTEGER
|
|
200
|
+
)
|
|
201
|
+
`);
|
|
202
|
+
|
|
203
|
+
// Create indexes
|
|
204
|
+
this.db.exec(`
|
|
205
|
+
CREATE INDEX IF NOT EXISTS idx_messages_agent ON agent_messages(agent_id);
|
|
206
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_agent ON agent_tasks(agent_id);
|
|
207
|
+
CREATE INDEX IF NOT EXISTS idx_changes_agent ON code_changes(agent_id);
|
|
208
|
+
CREATE INDEX IF NOT EXISTS idx_automations_enabled ON automations(enabled);
|
|
209
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_agent ON checkpoints(agent_id);
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_queue_agent ON agent_queue_items(agent_id);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_queue_agent_pos ON agent_queue_items(agent_id, position);
|
|
212
|
+
`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Agent queue
|
|
216
|
+
async listAgentQueueItems(agentId: string): Promise<Array<{ id: string; agentId: string; type: 'message' | 'task'; content: string; status: 'pending' | 'running'; position: number; createdAt: Date }>> {
|
|
217
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
218
|
+
|
|
219
|
+
const rows = this.db.prepare(
|
|
220
|
+
"SELECT * FROM agent_queue_items WHERE agent_id = ? AND status IN ('pending','running') ORDER BY position ASC"
|
|
221
|
+
).all(agentId) as any[];
|
|
222
|
+
|
|
223
|
+
return rows.map(r => ({
|
|
224
|
+
id: r.id,
|
|
225
|
+
agentId: r.agent_id,
|
|
226
|
+
type: r.type,
|
|
227
|
+
content: r.content,
|
|
228
|
+
status: r.status,
|
|
229
|
+
position: r.position,
|
|
230
|
+
createdAt: new Date(r.created_at)
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async enqueueAgentQueueItem(agentId: string, type: 'message' | 'task', content: string): Promise<{ id: string }> {
|
|
235
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
236
|
+
|
|
237
|
+
const trimmed = content.trim();
|
|
238
|
+
if (!trimmed) throw new Error('Queue item content cannot be empty');
|
|
239
|
+
|
|
240
|
+
const maxPosRow = this.db.prepare('SELECT MAX(position) as maxPos FROM agent_queue_items WHERE agent_id = ?').get(agentId) as any;
|
|
241
|
+
const nextPos = (maxPosRow?.maxPos ?? -1) + 1;
|
|
242
|
+
const id = `q_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
243
|
+
|
|
244
|
+
this.db.prepare(
|
|
245
|
+
'INSERT INTO agent_queue_items (id, agent_id, type, content, status, position, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
|
246
|
+
).run(id, agentId, type, trimmed, 'pending', nextPos, Date.now());
|
|
247
|
+
|
|
248
|
+
return { id };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async deleteAgentQueueItem(agentId: string, itemId: string): Promise<void> {
|
|
252
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
253
|
+
|
|
254
|
+
const tx = this.db.transaction(() => {
|
|
255
|
+
this.db!.prepare('DELETE FROM agent_queue_items WHERE id = ? AND agent_id = ?').run(itemId, agentId);
|
|
256
|
+
|
|
257
|
+
const rows = this.db!.prepare('SELECT id FROM agent_queue_items WHERE agent_id = ? ORDER BY position ASC').all(agentId) as any[];
|
|
258
|
+
const update = this.db!.prepare('UPDATE agent_queue_items SET position = ? WHERE id = ?');
|
|
259
|
+
rows.forEach((r, idx) => update.run(idx, r.id));
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
tx();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async moveAgentQueueItemUp(agentId: string, itemId: string): Promise<void> {
|
|
266
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
267
|
+
|
|
268
|
+
const row = this.db.prepare('SELECT id, position FROM agent_queue_items WHERE id = ? AND agent_id = ?').get(itemId, agentId) as any;
|
|
269
|
+
if (!row) return;
|
|
270
|
+
if (row.position <= 0) return;
|
|
271
|
+
|
|
272
|
+
const prev = this.db.prepare('SELECT id, position FROM agent_queue_items WHERE agent_id = ? AND position = ?').get(agentId, row.position - 1) as any;
|
|
273
|
+
if (!prev) return;
|
|
274
|
+
|
|
275
|
+
const tx = this.db.transaction(() => {
|
|
276
|
+
this.db!.prepare('UPDATE agent_queue_items SET position = ? WHERE id = ?').run(row.position - 1, row.id);
|
|
277
|
+
this.db!.prepare('UPDATE agent_queue_items SET position = ? WHERE id = ?').run(prev.position + 1, prev.id);
|
|
278
|
+
});
|
|
279
|
+
tx();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async claimNextAgentQueueItem(agentId: string): Promise<{ id: string; type: 'message' | 'task'; content: string } | null> {
|
|
283
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
284
|
+
|
|
285
|
+
const tx = this.db.transaction(() => {
|
|
286
|
+
const next = this.db!.prepare(
|
|
287
|
+
"SELECT * FROM agent_queue_items WHERE agent_id = ? AND status = 'pending' ORDER BY position ASC LIMIT 1"
|
|
288
|
+
).get(agentId) as any;
|
|
289
|
+
if (!next) return null;
|
|
290
|
+
|
|
291
|
+
this.db!.prepare(
|
|
292
|
+
"UPDATE agent_queue_items SET status = 'running', started_at = ? WHERE id = ?"
|
|
293
|
+
).run(Date.now(), next.id);
|
|
294
|
+
|
|
295
|
+
return { id: next.id, type: next.type, content: next.content } as { id: string; type: 'message' | 'task'; content: string };
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return tx();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async completeAgentQueueItem(agentId: string, itemId: string, outcome: 'completed' | 'failed', error?: string): Promise<void> {
|
|
302
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
303
|
+
|
|
304
|
+
// Mark as completed/failed with outcome and error
|
|
305
|
+
this.db.prepare(
|
|
306
|
+
"UPDATE agent_queue_items SET status = ?, completed_at = ?, error = ? WHERE id = ? AND agent_id = ?"
|
|
307
|
+
).run(outcome, Date.now(), error || null, itemId, agentId);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async getQueueHistory(agentId: string, limit: number = 50): Promise<Array<{ id: string; type: 'message' | 'task'; content: string; status: string; createdAt: Date; completedAt?: Date; error?: string }>> {
|
|
311
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
312
|
+
|
|
313
|
+
const rows = this.db.prepare(
|
|
314
|
+
"SELECT * FROM agent_queue_items WHERE agent_id = ? AND status IN ('completed','failed') ORDER BY completed_at DESC LIMIT ?"
|
|
315
|
+
).all(agentId, limit) as any[];
|
|
316
|
+
|
|
317
|
+
return rows.map(r => ({
|
|
318
|
+
id: r.id,
|
|
319
|
+
type: r.type,
|
|
320
|
+
content: r.content,
|
|
321
|
+
status: r.status,
|
|
322
|
+
createdAt: new Date(r.created_at),
|
|
323
|
+
completedAt: r.completed_at ? new Date(r.completed_at) : undefined,
|
|
324
|
+
error: r.error || undefined
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Checkpoints
|
|
329
|
+
async listCheckpoints(agentId?: string): Promise<Checkpoint[]> {
|
|
330
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
331
|
+
|
|
332
|
+
let query = 'SELECT * FROM checkpoints';
|
|
333
|
+
const params: any[] = [];
|
|
334
|
+
|
|
335
|
+
if (agentId) {
|
|
336
|
+
query += ' WHERE agent_id = ?';
|
|
337
|
+
params.push(agentId);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
query += ' ORDER BY created_at DESC';
|
|
341
|
+
|
|
342
|
+
const rows = this.db.prepare(query).all(...params) as any[];
|
|
343
|
+
return rows.map(row => ({
|
|
344
|
+
id: row.id,
|
|
345
|
+
agentId: row.agent_id,
|
|
346
|
+
changeId: row.change_id,
|
|
347
|
+
filePath: row.file_path,
|
|
348
|
+
content: row.content,
|
|
349
|
+
createdAt: new Date(row.created_at),
|
|
350
|
+
restoredAt: row.restored_at ? new Date(row.restored_at) : undefined
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async restoreCheckpoint(checkpointId: string): Promise<void> {
|
|
355
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
356
|
+
|
|
357
|
+
const checkpointRow = this.db.prepare('SELECT * FROM checkpoints WHERE id = ?').get(checkpointId) as any;
|
|
358
|
+
if (!checkpointRow) {
|
|
359
|
+
throw new Error(`Checkpoint ${checkpointId} not found`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const agentRow = this.db.prepare('SELECT * FROM agents WHERE id = ?').get(checkpointRow.agent_id) as any;
|
|
363
|
+
if (!agentRow) {
|
|
364
|
+
throw new Error(`Agent ${checkpointRow.agent_id} not found for checkpoint ${checkpointId}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const agentMetadata = safeJsonParse(agentRow.metadata, {} as Record<string, any>);
|
|
368
|
+
const worktreePath: string | undefined = agentMetadata.worktreePath || undefined;
|
|
369
|
+
if (!worktreePath || typeof worktreePath !== 'string') {
|
|
370
|
+
throw new Error(`Worktree path not available for agent ${agentRow.id}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const relativeFilePath = String(checkpointRow.file_path || '').trim();
|
|
374
|
+
if (!relativeFilePath) {
|
|
375
|
+
throw new Error(`Invalid file path for checkpoint ${checkpointId}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const targetPath = path.resolve(worktreePath, relativeFilePath);
|
|
379
|
+
const resolvedRoot = path.resolve(worktreePath);
|
|
380
|
+
if (!targetPath.startsWith(resolvedRoot + path.sep) && targetPath !== resolvedRoot) {
|
|
381
|
+
throw new Error('Refusing to write outside of worktree root');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
385
|
+
await fs.promises.writeFile(targetPath, checkpointRow.content || '', 'utf-8');
|
|
386
|
+
|
|
387
|
+
this.db.prepare('UPDATE checkpoints SET restored_at = ? WHERE id = ?').run(Date.now(), checkpointId);
|
|
388
|
+
log.info(`Restored checkpoint ${checkpointId} to ${relativeFilePath}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async restoreLastCheckpoint(agentId: string): Promise<void> {
|
|
392
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
393
|
+
|
|
394
|
+
const row = this.db.prepare(
|
|
395
|
+
'SELECT * FROM checkpoints WHERE agent_id = ? AND restored_at IS NULL ORDER BY created_at DESC LIMIT 1'
|
|
396
|
+
).get(agentId) as any;
|
|
397
|
+
|
|
398
|
+
if (!row) {
|
|
399
|
+
throw new Error('No checkpoint available');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
await this.restoreCheckpoint(row.id);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Agent operations
|
|
406
|
+
async createAgent(agent: Agent): Promise<void> {
|
|
407
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
408
|
+
|
|
409
|
+
const stmt = this.db.prepare(`
|
|
410
|
+
INSERT INTO agents (
|
|
411
|
+
id, name, status, project_path, worktree_name, provider_id, model,
|
|
412
|
+
skills, created_at, updated_at, last_active_at, metadata
|
|
413
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
414
|
+
`);
|
|
415
|
+
|
|
416
|
+
stmt.run(
|
|
417
|
+
agent.id,
|
|
418
|
+
agent.name,
|
|
419
|
+
agent.status,
|
|
420
|
+
agent.projectPath,
|
|
421
|
+
agent.worktreeName,
|
|
422
|
+
agent.providerId,
|
|
423
|
+
agent.model,
|
|
424
|
+
JSON.stringify(agent.skills),
|
|
425
|
+
agent.createdAt.getTime(),
|
|
426
|
+
agent.updatedAt.getTime(),
|
|
427
|
+
agent.lastActiveAt?.getTime() || null,
|
|
428
|
+
JSON.stringify(agent.metadata)
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async updateAgent(agent: Agent): Promise<void> {
|
|
433
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
434
|
+
|
|
435
|
+
const stmt = this.db.prepare(`
|
|
436
|
+
UPDATE agents SET
|
|
437
|
+
name = ?,
|
|
438
|
+
status = ?,
|
|
439
|
+
provider_id = ?,
|
|
440
|
+
model = ?,
|
|
441
|
+
skills = ?,
|
|
442
|
+
updated_at = ?,
|
|
443
|
+
last_active_at = ?,
|
|
444
|
+
metadata = ?
|
|
445
|
+
WHERE id = ?
|
|
446
|
+
`);
|
|
447
|
+
|
|
448
|
+
stmt.run(
|
|
449
|
+
agent.name,
|
|
450
|
+
agent.status,
|
|
451
|
+
agent.providerId,
|
|
452
|
+
agent.model,
|
|
453
|
+
JSON.stringify(agent.skills),
|
|
454
|
+
agent.updatedAt.getTime(),
|
|
455
|
+
agent.lastActiveAt?.getTime() || null,
|
|
456
|
+
JSON.stringify(agent.metadata),
|
|
457
|
+
agent.id
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async deleteAgent(agentId: string): Promise<void> {
|
|
462
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
463
|
+
|
|
464
|
+
const stmt = this.db.prepare('DELETE FROM agents WHERE id = ?');
|
|
465
|
+
stmt.run(agentId);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async getAllAgents(): Promise<Agent[]> {
|
|
469
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
470
|
+
|
|
471
|
+
const stmt = this.db.prepare('SELECT * FROM agents ORDER BY created_at DESC');
|
|
472
|
+
const rows = stmt.all() as any[];
|
|
473
|
+
|
|
474
|
+
return rows.map(row => this.mapAgentRow(row));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async getAgent(agentId: string): Promise<Agent | null> {
|
|
478
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
479
|
+
|
|
480
|
+
const stmt = this.db.prepare('SELECT * FROM agents WHERE id = ?');
|
|
481
|
+
const row = stmt.get(agentId) as any;
|
|
482
|
+
|
|
483
|
+
if (!row) return null;
|
|
484
|
+
|
|
485
|
+
const agent = this.mapAgentRow(row);
|
|
486
|
+
|
|
487
|
+
// Load messages
|
|
488
|
+
agent.messages = await this.getAgentMessages(agentId);
|
|
489
|
+
|
|
490
|
+
// Load tasks
|
|
491
|
+
agent.tasks = await this.getAgentTasks(agentId);
|
|
492
|
+
|
|
493
|
+
return agent;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Agent messages
|
|
497
|
+
async addAgentMessage(message: AgentMessage, agentId: string): Promise<void> {
|
|
498
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
499
|
+
|
|
500
|
+
const stmt = this.db.prepare(`
|
|
501
|
+
INSERT INTO agent_messages (id, agent_id, role, content, timestamp, metadata)
|
|
502
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
503
|
+
`);
|
|
504
|
+
|
|
505
|
+
stmt.run(
|
|
506
|
+
message.id,
|
|
507
|
+
agentId,
|
|
508
|
+
message.role,
|
|
509
|
+
message.content,
|
|
510
|
+
message.timestamp.getTime(),
|
|
511
|
+
JSON.stringify(message.metadata)
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async getAgentMessages(agentId: string): Promise<AgentMessage[]> {
|
|
516
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
517
|
+
|
|
518
|
+
const stmt = this.db.prepare(`
|
|
519
|
+
SELECT * FROM agent_messages WHERE agent_id = ? ORDER BY timestamp ASC
|
|
520
|
+
`);
|
|
521
|
+
const rows = stmt.all(agentId) as any[];
|
|
522
|
+
|
|
523
|
+
return rows.map(row => ({
|
|
524
|
+
id: row.id,
|
|
525
|
+
role: row.role,
|
|
526
|
+
content: row.content,
|
|
527
|
+
timestamp: new Date(row.timestamp),
|
|
528
|
+
metadata: safeJsonParse(row.metadata, undefined as Record<string, any> | undefined)
|
|
529
|
+
}));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Agent tasks
|
|
533
|
+
async addAgentTask(task: AgentTask, agentId: string): Promise<void> {
|
|
534
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
535
|
+
|
|
536
|
+
const stmt = this.db.prepare(`
|
|
537
|
+
INSERT INTO agent_tasks (
|
|
538
|
+
id, agent_id, description, status, progress, started_at, completed_at, result, error
|
|
539
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
540
|
+
`);
|
|
541
|
+
|
|
542
|
+
stmt.run(
|
|
543
|
+
task.id,
|
|
544
|
+
agentId,
|
|
545
|
+
task.description,
|
|
546
|
+
task.status,
|
|
547
|
+
task.progress,
|
|
548
|
+
task.startedAt.getTime(),
|
|
549
|
+
task.completedAt?.getTime() || null,
|
|
550
|
+
task.result || null,
|
|
551
|
+
task.error || null
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async updateAgentTask(task: AgentTask): Promise<void> {
|
|
556
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
557
|
+
|
|
558
|
+
const stmt = this.db.prepare(`
|
|
559
|
+
UPDATE agent_tasks SET
|
|
560
|
+
status = ?,
|
|
561
|
+
progress = ?,
|
|
562
|
+
completed_at = ?,
|
|
563
|
+
result = ?,
|
|
564
|
+
error = ?
|
|
565
|
+
WHERE id = ?
|
|
566
|
+
`);
|
|
567
|
+
|
|
568
|
+
stmt.run(
|
|
569
|
+
task.status,
|
|
570
|
+
task.progress,
|
|
571
|
+
task.completedAt?.getTime() || null,
|
|
572
|
+
task.result || null,
|
|
573
|
+
task.error || null,
|
|
574
|
+
task.id
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async getAgentTasks(agentId: string): Promise<AgentTask[]> {
|
|
579
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
580
|
+
|
|
581
|
+
const stmt = this.db.prepare(`
|
|
582
|
+
SELECT * FROM agent_tasks WHERE agent_id = ? ORDER BY started_at DESC
|
|
583
|
+
`);
|
|
584
|
+
const rows = stmt.all(agentId) as any[];
|
|
585
|
+
|
|
586
|
+
return rows.map(row => ({
|
|
587
|
+
id: row.id,
|
|
588
|
+
description: row.description,
|
|
589
|
+
status: row.status,
|
|
590
|
+
progress: row.progress,
|
|
591
|
+
startedAt: new Date(row.started_at),
|
|
592
|
+
completedAt: row.completed_at ? new Date(row.completed_at) : undefined,
|
|
593
|
+
result: row.result || undefined,
|
|
594
|
+
error: row.error || undefined
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Code changes
|
|
599
|
+
async createCodeChange(change: CodeChange): Promise<void> {
|
|
600
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
601
|
+
|
|
602
|
+
const stmt = this.db.prepare(`
|
|
603
|
+
INSERT INTO code_changes (
|
|
604
|
+
id, file_path, original_content, new_content, diff,
|
|
605
|
+
agent_id, task_id, status, created_at
|
|
606
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
607
|
+
`);
|
|
608
|
+
|
|
609
|
+
stmt.run(
|
|
610
|
+
change.id,
|
|
611
|
+
change.filePath,
|
|
612
|
+
change.originalContent,
|
|
613
|
+
change.newContent,
|
|
614
|
+
change.diff,
|
|
615
|
+
change.agentId,
|
|
616
|
+
change.taskId,
|
|
617
|
+
change.status,
|
|
618
|
+
change.createdAt.getTime()
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async updateCodeChange(change: CodeChange): Promise<void> {
|
|
623
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
624
|
+
|
|
625
|
+
const stmt = this.db.prepare(`
|
|
626
|
+
UPDATE code_changes SET
|
|
627
|
+
status = ?,
|
|
628
|
+
reviewed_at = ?,
|
|
629
|
+
reviewed_by = ?,
|
|
630
|
+
comment = ?
|
|
631
|
+
WHERE id = ?
|
|
632
|
+
`);
|
|
633
|
+
|
|
634
|
+
stmt.run(
|
|
635
|
+
change.status,
|
|
636
|
+
change.reviewedAt?.getTime() || null,
|
|
637
|
+
change.reviewedBy || null,
|
|
638
|
+
change.comment || null,
|
|
639
|
+
change.id
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Code changes operations
|
|
644
|
+
async getCodeChanges(agentId?: string): Promise<CodeChange[]> {
|
|
645
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
646
|
+
|
|
647
|
+
let query = 'SELECT * FROM code_changes';
|
|
648
|
+
const params: any[] = [];
|
|
649
|
+
|
|
650
|
+
if (agentId) {
|
|
651
|
+
query += ' WHERE agent_id = ?';
|
|
652
|
+
params.push(agentId);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
query += ' ORDER BY created_at DESC';
|
|
656
|
+
|
|
657
|
+
const stmt = this.db.prepare(query);
|
|
658
|
+
const rows = stmt.all(...params) as any[];
|
|
659
|
+
|
|
660
|
+
return rows.map(row => ({
|
|
661
|
+
id: row.id,
|
|
662
|
+
filePath: row.file_path,
|
|
663
|
+
originalContent: row.original_content,
|
|
664
|
+
newContent: row.new_content,
|
|
665
|
+
diff: row.diff,
|
|
666
|
+
agentId: row.agent_id,
|
|
667
|
+
taskId: row.task_id,
|
|
668
|
+
status: row.status,
|
|
669
|
+
createdAt: new Date(row.created_at),
|
|
670
|
+
reviewedAt: row.reviewed_at ? new Date(row.reviewed_at) : undefined,
|
|
671
|
+
reviewedBy: row.reviewed_by || undefined,
|
|
672
|
+
comment: row.comment || undefined
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async approveCodeChange(changeId: string): Promise<void> {
|
|
677
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
678
|
+
|
|
679
|
+
const stmt = this.db.prepare(`
|
|
680
|
+
UPDATE code_changes SET
|
|
681
|
+
status = ?,
|
|
682
|
+
reviewed_at = ?,
|
|
683
|
+
reviewed_by = ?
|
|
684
|
+
WHERE id = ?
|
|
685
|
+
`);
|
|
686
|
+
|
|
687
|
+
stmt.run(
|
|
688
|
+
ChangeStatus.APPROVED,
|
|
689
|
+
Date.now(),
|
|
690
|
+
'user', // In real app, would get current user
|
|
691
|
+
changeId
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async rejectCodeChange(changeId: string, comment?: string): Promise<void> {
|
|
696
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
697
|
+
|
|
698
|
+
const stmt = this.db.prepare(`
|
|
699
|
+
UPDATE code_changes SET
|
|
700
|
+
status = ?,
|
|
701
|
+
reviewed_at = ?,
|
|
702
|
+
reviewed_by = ?,
|
|
703
|
+
comment = ?
|
|
704
|
+
WHERE id = ?
|
|
705
|
+
`);
|
|
706
|
+
|
|
707
|
+
stmt.run(
|
|
708
|
+
ChangeStatus.REJECTED,
|
|
709
|
+
Date.now(),
|
|
710
|
+
'user',
|
|
711
|
+
comment || null,
|
|
712
|
+
changeId
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async applyCodeChange(changeId: string): Promise<void> {
|
|
717
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
718
|
+
|
|
719
|
+
const changeRow = this.db.prepare('SELECT * FROM code_changes WHERE id = ?').get(changeId) as any;
|
|
720
|
+
if (!changeRow) {
|
|
721
|
+
throw new Error(`Code change ${changeId} not found`);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (changeRow.status !== ChangeStatus.APPROVED) {
|
|
725
|
+
throw new Error(`Code change ${changeId} must be approved before applying`);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const agentRow = this.db.prepare('SELECT * FROM agents WHERE id = ?').get(changeRow.agent_id) as any;
|
|
729
|
+
if (!agentRow) {
|
|
730
|
+
throw new Error(`Agent ${changeRow.agent_id} not found for code change ${changeId}`);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const agentMetadata = safeJsonParse(agentRow.metadata, {} as Record<string, any>);
|
|
734
|
+
const worktreePath: string | undefined = agentMetadata.worktreePath || undefined;
|
|
735
|
+
|
|
736
|
+
if (!worktreePath || typeof worktreePath !== 'string') {
|
|
737
|
+
throw new Error(`Worktree path not available for agent ${agentRow.id}`);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const relativeFilePath = String(changeRow.file_path || '').trim();
|
|
741
|
+
if (!relativeFilePath) {
|
|
742
|
+
throw new Error(`Invalid file path for code change ${changeId}`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const targetPath = path.resolve(worktreePath, relativeFilePath);
|
|
746
|
+
const resolvedRoot = path.resolve(worktreePath);
|
|
747
|
+
if (!targetPath.startsWith(resolvedRoot + path.sep) && targetPath !== resolvedRoot) {
|
|
748
|
+
throw new Error('Refusing to write outside of worktree root');
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
let currentContent = '';
|
|
752
|
+
try {
|
|
753
|
+
currentContent = await fs.promises.readFile(targetPath, 'utf-8');
|
|
754
|
+
} catch {
|
|
755
|
+
currentContent = '';
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const checkpointId = `cp_${changeId}_${Date.now()}`;
|
|
759
|
+
this.db.prepare(
|
|
760
|
+
'INSERT INTO checkpoints (id, agent_id, change_id, file_path, content, created_at) VALUES (?, ?, ?, ?, ?, ?)'
|
|
761
|
+
).run(
|
|
762
|
+
checkpointId,
|
|
763
|
+
changeRow.agent_id,
|
|
764
|
+
changeId,
|
|
765
|
+
relativeFilePath,
|
|
766
|
+
currentContent,
|
|
767
|
+
Date.now()
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
771
|
+
await fs.promises.writeFile(targetPath, changeRow.new_content || '', 'utf-8');
|
|
772
|
+
|
|
773
|
+
this.db.prepare('UPDATE code_changes SET status = ? WHERE id = ?').run(ChangeStatus.APPLIED, changeId);
|
|
774
|
+
log.info(`Applied code change ${changeId} to ${relativeFilePath}`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Cowork sessions
|
|
778
|
+
async getCoworkSessions(): Promise<any[]> {
|
|
779
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
780
|
+
|
|
781
|
+
const stmt = this.db.prepare('SELECT * FROM cowork_sessions ORDER BY created_at DESC');
|
|
782
|
+
const rows = stmt.all() as any[];
|
|
783
|
+
|
|
784
|
+
return rows.map(row => ({
|
|
785
|
+
id: row.id,
|
|
786
|
+
name: row.name,
|
|
787
|
+
agentId: row.agent_id,
|
|
788
|
+
status: row.status,
|
|
789
|
+
objective: row.objective,
|
|
790
|
+
progress: row.progress,
|
|
791
|
+
createdAt: new Date(row.created_at),
|
|
792
|
+
updatedAt: new Date(row.updated_at),
|
|
793
|
+
completedAt: row.completed_at ? new Date(row.completed_at) : undefined,
|
|
794
|
+
logs: safeJsonParse(row.logs, [] as string[]),
|
|
795
|
+
deliverables: safeJsonParse(row.deliverables, [] as string[]),
|
|
796
|
+
autoApprove: Boolean(row.auto_approve)
|
|
797
|
+
}));
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
async saveCoworkSession(session: any): Promise<void> {
|
|
801
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
802
|
+
|
|
803
|
+
const stmt = this.db.prepare(`
|
|
804
|
+
INSERT OR REPLACE INTO cowork_sessions (
|
|
805
|
+
id, name, agent_id, status, objective, progress,
|
|
806
|
+
created_at, updated_at, completed_at, logs, deliverables, auto_approve
|
|
807
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
808
|
+
`);
|
|
809
|
+
|
|
810
|
+
stmt.run(
|
|
811
|
+
session.id,
|
|
812
|
+
session.name,
|
|
813
|
+
session.agentId,
|
|
814
|
+
session.status,
|
|
815
|
+
session.objective,
|
|
816
|
+
session.progress,
|
|
817
|
+
session.createdAt.getTime(),
|
|
818
|
+
session.updatedAt.getTime(),
|
|
819
|
+
session.completedAt?.getTime() || null,
|
|
820
|
+
JSON.stringify(session.logs),
|
|
821
|
+
JSON.stringify(session.deliverables),
|
|
822
|
+
session.autoApprove ? 1 : 0
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
private mapAgentRow(row: any): Agent {
|
|
827
|
+
return {
|
|
828
|
+
id: row.id,
|
|
829
|
+
name: row.name,
|
|
830
|
+
status: row.status,
|
|
831
|
+
projectPath: row.project_path,
|
|
832
|
+
worktreeName: row.worktree_name,
|
|
833
|
+
providerId: row.provider_id,
|
|
834
|
+
model: row.model,
|
|
835
|
+
skills: safeJsonParse(row.skills, [] as string[]),
|
|
836
|
+
permissionMode: row.permission_mode || 'ask',
|
|
837
|
+
createdAt: new Date(row.created_at),
|
|
838
|
+
updatedAt: new Date(row.updated_at),
|
|
839
|
+
lastActiveAt: row.last_active_at ? new Date(row.last_active_at) : null,
|
|
840
|
+
messages: [],
|
|
841
|
+
tasks: [],
|
|
842
|
+
metadata: safeJsonParse(row.metadata, {} as Record<string, any>)
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Project operations
|
|
847
|
+
async createProject(project: Project): Promise<void> {
|
|
848
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
849
|
+
|
|
850
|
+
const stmt = this.db.prepare(`
|
|
851
|
+
INSERT INTO projects (id, name, path, git_remote, created_at, updated_at)
|
|
852
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
853
|
+
`);
|
|
854
|
+
|
|
855
|
+
stmt.run(
|
|
856
|
+
project.id,
|
|
857
|
+
project.name,
|
|
858
|
+
project.path,
|
|
859
|
+
project.gitRemote || null,
|
|
860
|
+
project.createdAt.getTime(),
|
|
861
|
+
project.updatedAt.getTime()
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async updateProject(project: Project): Promise<void> {
|
|
866
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
867
|
+
|
|
868
|
+
const stmt = this.db.prepare(`
|
|
869
|
+
UPDATE projects SET
|
|
870
|
+
name = ?,
|
|
871
|
+
path = ?,
|
|
872
|
+
git_remote = ?,
|
|
873
|
+
updated_at = ?
|
|
874
|
+
WHERE id = ?
|
|
875
|
+
`);
|
|
876
|
+
|
|
877
|
+
stmt.run(
|
|
878
|
+
project.name,
|
|
879
|
+
project.path,
|
|
880
|
+
project.gitRemote || null,
|
|
881
|
+
project.updatedAt.getTime(),
|
|
882
|
+
project.id
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
async getProject(id: string): Promise<Project | null> {
|
|
887
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
888
|
+
|
|
889
|
+
const stmt = this.db.prepare('SELECT * FROM projects WHERE id = ?');
|
|
890
|
+
const row = stmt.get(id) as any;
|
|
891
|
+
|
|
892
|
+
if (!row) return null;
|
|
893
|
+
|
|
894
|
+
return {
|
|
895
|
+
id: row.id,
|
|
896
|
+
name: row.name,
|
|
897
|
+
path: row.path,
|
|
898
|
+
gitRemote: row.git_remote || undefined,
|
|
899
|
+
agents: [],
|
|
900
|
+
worktrees: [],
|
|
901
|
+
createdAt: new Date(row.created_at),
|
|
902
|
+
updatedAt: new Date(row.updated_at)
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async getProjectByPath(projectPath: string): Promise<Project | null> {
|
|
907
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
908
|
+
|
|
909
|
+
const stmt = this.db.prepare('SELECT * FROM projects WHERE path = ?');
|
|
910
|
+
const row = stmt.get(projectPath) as any;
|
|
911
|
+
|
|
912
|
+
if (!row) return null;
|
|
913
|
+
|
|
914
|
+
return {
|
|
915
|
+
id: row.id,
|
|
916
|
+
name: row.name,
|
|
917
|
+
path: row.path,
|
|
918
|
+
gitRemote: row.git_remote || undefined,
|
|
919
|
+
agents: [],
|
|
920
|
+
worktrees: [],
|
|
921
|
+
createdAt: new Date(row.created_at),
|
|
922
|
+
updatedAt: new Date(row.updated_at)
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async listProjects(): Promise<Project[]> {
|
|
927
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
928
|
+
|
|
929
|
+
const stmt = this.db.prepare('SELECT * FROM projects ORDER BY updated_at DESC');
|
|
930
|
+
const rows = stmt.all() as any[];
|
|
931
|
+
|
|
932
|
+
return rows.map(row => ({
|
|
933
|
+
id: row.id,
|
|
934
|
+
name: row.name,
|
|
935
|
+
path: row.path,
|
|
936
|
+
gitRemote: row.git_remote || undefined,
|
|
937
|
+
agents: [],
|
|
938
|
+
worktrees: [],
|
|
939
|
+
createdAt: new Date(row.created_at),
|
|
940
|
+
updatedAt: new Date(row.updated_at)
|
|
941
|
+
}));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async deleteProject(id: string): Promise<void> {
|
|
945
|
+
if (!this.db) throw new Error('Database not initialized');
|
|
946
|
+
|
|
947
|
+
const stmt = this.db.prepare('DELETE FROM projects WHERE id = ?');
|
|
948
|
+
stmt.run(id);
|
|
949
|
+
}
|
|
950
|
+
}
|