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.
Files changed (367) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.eslintrc.json +27 -0
  3. package/.github/workflows/ci.yml +156 -0
  4. package/.huskyrc +7 -0
  5. package/.lintstagedrc +13 -0
  6. package/.prettierrc +12 -0
  7. package/CLAUDE.md +163 -0
  8. package/DESIGN_SUPERIOR.md +73 -0
  9. package/Dockerfile +64 -0
  10. package/INSTALLATION.md +152 -0
  11. package/LICENSE +21 -0
  12. package/README.md +245 -0
  13. package/assets/skills/code-review/instructions.md +102 -0
  14. package/assets/skills/code-review/skill.yaml +15 -0
  15. package/assets/skills/refactoring/instructions.md +149 -0
  16. package/assets/skills/refactoring/skill.yaml +15 -0
  17. package/assets/skills/testing/skill.yaml +15 -0
  18. package/commitlint.config.js +23 -0
  19. package/dist/main/DatabaseManager.js +763 -0
  20. package/dist/main/DatabaseManager.js.map +1 -0
  21. package/dist/main/SettingsManager.js +61 -0
  22. package/dist/main/SettingsManager.js.map +1 -0
  23. package/dist/main/agents/AgentOrchestrator.js +787 -0
  24. package/dist/main/agents/AgentOrchestrator.js.map +1 -0
  25. package/dist/main/agents/AgentSDK.js +219 -0
  26. package/dist/main/agents/AgentSDK.js.map +1 -0
  27. package/dist/main/agents/AgentTools.js +348 -0
  28. package/dist/main/agents/AgentTools.js.map +1 -0
  29. package/dist/main/agents/CodeIndex.js +233 -0
  30. package/dist/main/agents/CodeIndex.js.map +1 -0
  31. package/dist/main/agents/EmbeddingService.js +80 -0
  32. package/dist/main/agents/EmbeddingService.js.map +1 -0
  33. package/dist/main/agents/NativeToolCalling.js +206 -0
  34. package/dist/main/agents/NativeToolCalling.js.map +1 -0
  35. package/dist/main/api/APIServer.js +278 -0
  36. package/dist/main/api/APIServer.js.map +1 -0
  37. package/dist/main/api/RateLimiter.js +138 -0
  38. package/dist/main/api/RateLimiter.js.map +1 -0
  39. package/dist/main/api/WebSocketManager.js +300 -0
  40. package/dist/main/api/WebSocketManager.js.map +1 -0
  41. package/dist/main/assistant/ContextOptimizer.js +192 -0
  42. package/dist/main/assistant/ContextOptimizer.js.map +1 -0
  43. package/dist/main/assistant/PredictedOutputManager.js +172 -0
  44. package/dist/main/assistant/PredictedOutputManager.js.map +1 -0
  45. package/dist/main/assistant/PromptCacheManager.js +193 -0
  46. package/dist/main/assistant/PromptCacheManager.js.map +1 -0
  47. package/dist/main/assistant/PromptOptimizer.js +626 -0
  48. package/dist/main/assistant/PromptOptimizer.js.map +1 -0
  49. package/dist/main/assistant/SmartCodeAssistant.js +224 -0
  50. package/dist/main/assistant/SmartCodeAssistant.js.map +1 -0
  51. package/dist/main/auth/SessionManager.js +300 -0
  52. package/dist/main/auth/SessionManager.js.map +1 -0
  53. package/dist/main/automations/AdvancedWebhookSystem.js +212 -0
  54. package/dist/main/automations/AdvancedWebhookSystem.js.map +1 -0
  55. package/dist/main/automations/AutomationScheduler.js +269 -0
  56. package/dist/main/automations/AutomationScheduler.js.map +1 -0
  57. package/dist/main/automations/BatchProcessingSystem.js +159 -0
  58. package/dist/main/automations/BatchProcessingSystem.js.map +1 -0
  59. package/dist/main/automations/BrowserAutomationManager.js +195 -0
  60. package/dist/main/automations/BrowserAutomationManager.js.map +1 -0
  61. package/dist/main/automations/GitHubActionsManager.js +129 -0
  62. package/dist/main/automations/GitHubActionsManager.js.map +1 -0
  63. package/dist/main/automations/GitLabCIManager.js +122 -0
  64. package/dist/main/automations/GitLabCIManager.js.map +1 -0
  65. package/dist/main/automations/PriorityQueueManager.js +240 -0
  66. package/dist/main/automations/PriorityQueueManager.js.map +1 -0
  67. package/dist/main/background/BackgroundModeManager.js +117 -0
  68. package/dist/main/background/BackgroundModeManager.js.map +1 -0
  69. package/dist/main/backup/BackupManager.js +254 -0
  70. package/dist/main/backup/BackupManager.js.map +1 -0
  71. package/dist/main/backup/MigrationManager.js +114 -0
  72. package/dist/main/backup/MigrationManager.js.map +1 -0
  73. package/dist/main/commands/SlashCommandManager.js +399 -0
  74. package/dist/main/commands/SlashCommandManager.js.map +1 -0
  75. package/dist/main/config/ClaudeMdParser.js +519 -0
  76. package/dist/main/config/ClaudeMdParser.js.map +1 -0
  77. package/dist/main/config/CustomizationManager.js +381 -0
  78. package/dist/main/config/CustomizationManager.js.map +1 -0
  79. package/dist/main/config/LaunchConfigManager.js +211 -0
  80. package/dist/main/config/LaunchConfigManager.js.map +1 -0
  81. package/dist/main/config/SettingsManager.js +166 -0
  82. package/dist/main/config/SettingsManager.js.map +1 -0
  83. package/dist/main/connectors/ConnectorManager.js +151 -0
  84. package/dist/main/connectors/ConnectorManager.js.map +1 -0
  85. package/dist/main/connectors/DatabaseConnector.js +222 -0
  86. package/dist/main/connectors/DatabaseConnector.js.map +1 -0
  87. package/dist/main/cowork/CoworkManager.js +324 -0
  88. package/dist/main/cowork/CoworkManager.js.map +1 -0
  89. package/dist/main/evals/AgentEvalFramework.js +538 -0
  90. package/dist/main/evals/AgentEvalFramework.js.map +1 -0
  91. package/dist/main/evals/GraderManager.js +285 -0
  92. package/dist/main/evals/GraderManager.js.map +1 -0
  93. package/dist/main/git/GitWorktreeManager.js +214 -0
  94. package/dist/main/git/GitWorktreeManager.js.map +1 -0
  95. package/dist/main/github/GitHubPRMonitor.js +244 -0
  96. package/dist/main/github/GitHubPRMonitor.js.map +1 -0
  97. package/dist/main/ide/ContinueInManager.js +181 -0
  98. package/dist/main/ide/ContinueInManager.js.map +1 -0
  99. package/dist/main/ide/IDEIntegration.js +277 -0
  100. package/dist/main/ide/IDEIntegration.js.map +1 -0
  101. package/dist/main/integrations/LinearManager.js +252 -0
  102. package/dist/main/integrations/LinearManager.js.map +1 -0
  103. package/dist/main/integrations/SlackBotManager.js +247 -0
  104. package/dist/main/integrations/SlackBotManager.js.map +1 -0
  105. package/dist/main/lsp/LSPManager.js +394 -0
  106. package/dist/main/lsp/LSPManager.js.map +1 -0
  107. package/dist/main/main.js +1087 -0
  108. package/dist/main/main.js.map +1 -0
  109. package/dist/main/mcp/MCPConfigurationManager.js +281 -0
  110. package/dist/main/mcp/MCPConfigurationManager.js.map +1 -0
  111. package/dist/main/mcp/MCPManager.js +710 -0
  112. package/dist/main/mcp/MCPManager.js.map +1 -0
  113. package/dist/main/mcp/MCPRegistry.js +272 -0
  114. package/dist/main/mcp/MCPRegistry.js.map +1 -0
  115. package/dist/main/monitoring/ErrorRecoveryManager.js +268 -0
  116. package/dist/main/monitoring/ErrorRecoveryManager.js.map +1 -0
  117. package/dist/main/monitoring/ErrorTracker.js +57 -0
  118. package/dist/main/monitoring/ErrorTracker.js.map +1 -0
  119. package/dist/main/monitoring/MetricsCollector.js +155 -0
  120. package/dist/main/monitoring/MetricsCollector.js.map +1 -0
  121. package/dist/main/monitoring/TraceGradingSystem.js +148 -0
  122. package/dist/main/monitoring/TraceGradingSystem.js.map +1 -0
  123. package/dist/main/notifications/NotificationManager.js +67 -0
  124. package/dist/main/notifications/NotificationManager.js.map +1 -0
  125. package/dist/main/pair/AIPairProgramming.js +200 -0
  126. package/dist/main/pair/AIPairProgramming.js.map +1 -0
  127. package/dist/main/plugins/PluginManager.js +222 -0
  128. package/dist/main/plugins/PluginManager.js.map +1 -0
  129. package/dist/main/plugins/PluginMarketplace.js +237 -0
  130. package/dist/main/plugins/PluginMarketplace.js.map +1 -0
  131. package/dist/main/preload.js +189 -0
  132. package/dist/main/preload.js.map +1 -0
  133. package/dist/main/preview/PreviewSessionManager.js +170 -0
  134. package/dist/main/preview/PreviewSessionManager.js.map +1 -0
  135. package/dist/main/providers/AIProviderManager.js +327 -0
  136. package/dist/main/providers/AIProviderManager.js.map +1 -0
  137. package/dist/main/providers/FineTuningManager.js +276 -0
  138. package/dist/main/providers/FineTuningManager.js.map +1 -0
  139. package/dist/main/providers/FreeModelsProvider.js +1104 -0
  140. package/dist/main/providers/FreeModelsProvider.js.map +1 -0
  141. package/dist/main/realtime/RealtimeManager.js +116 -0
  142. package/dist/main/realtime/RealtimeManager.js.map +1 -0
  143. package/dist/main/remote/CloudEnvironmentManager.js +232 -0
  144. package/dist/main/remote/CloudEnvironmentManager.js.map +1 -0
  145. package/dist/main/remote/RemoteSessionManager.js +255 -0
  146. package/dist/main/remote/RemoteSessionManager.js.map +1 -0
  147. package/dist/main/search/DeepResearchManager.js +335 -0
  148. package/dist/main/search/DeepResearchManager.js.map +1 -0
  149. package/dist/main/search/WebSearchIntegration.js +147 -0
  150. package/dist/main/search/WebSearchIntegration.js.map +1 -0
  151. package/dist/main/security/AdminConsoleManager.js +223 -0
  152. package/dist/main/security/AdminConsoleManager.js.map +1 -0
  153. package/dist/main/security/AuditLogger.js +136 -0
  154. package/dist/main/security/AuditLogger.js.map +1 -0
  155. package/dist/main/security/PermissionManager.js +144 -0
  156. package/dist/main/security/PermissionManager.js.map +1 -0
  157. package/dist/main/security/SSOManager.js +173 -0
  158. package/dist/main/security/SSOManager.js.map +1 -0
  159. package/dist/main/security/SecurityManager.js +152 -0
  160. package/dist/main/security/SecurityManager.js.map +1 -0
  161. package/dist/main/skills/SkillsManager.js +223 -0
  162. package/dist/main/skills/SkillsManager.js.map +1 -0
  163. package/dist/main/ssh/SSHManager.js +65 -0
  164. package/dist/main/ssh/SSHManager.js.map +1 -0
  165. package/dist/main/streaming/StreamingManager.js +225 -0
  166. package/dist/main/streaming/StreamingManager.js.map +1 -0
  167. package/dist/main/sync/CloudSyncManager.js +422 -0
  168. package/dist/main/sync/CloudSyncManager.js.map +1 -0
  169. package/dist/main/types.js +28 -0
  170. package/dist/main/types.js.map +1 -0
  171. package/dist/main/verification/AutoVerifyManager.js +235 -0
  172. package/dist/main/verification/AutoVerifyManager.js.map +1 -0
  173. package/dist/main/vision/ComputerUseManager.js +376 -0
  174. package/dist/main/vision/ComputerUseManager.js.map +1 -0
  175. package/dist/main/vision/ImageVideoGenerationManager.js +401 -0
  176. package/dist/main/vision/ImageVideoGenerationManager.js.map +1 -0
  177. package/dist/main/vision/VisionManager.js +172 -0
  178. package/dist/main/vision/VisionManager.js.map +1 -0
  179. package/dist/renderer/assets/main-DJlZQBCA.js +304 -0
  180. package/dist/renderer/assets/main-N33ZXEr8.css +1 -0
  181. package/dist/renderer/index.html +21 -0
  182. package/dist/renderer/manifest.json +42 -0
  183. package/dist/renderer/sw.ts +109 -0
  184. package/dist/shared/types.js +35 -0
  185. package/dist/shared/types.js.map +1 -0
  186. package/docker-compose.yml +65 -0
  187. package/docs/API.md +307 -0
  188. package/docs/USER_GUIDE.md +476 -0
  189. package/examples/plugins/sample-plugin/package.json +41 -0
  190. package/examples/plugins/sample-plugin/src/index.ts +75 -0
  191. package/index.html +20 -0
  192. package/jest.config.js +39 -0
  193. package/package.json +180 -0
  194. package/packages/cli/package.json +29 -0
  195. package/packages/cli/src/commands/agents.ts +199 -0
  196. package/packages/cli/src/commands/tasks.ts +61 -0
  197. package/packages/cli/src/index.ts +91 -0
  198. package/packages/cli/src/utils/api.ts +45 -0
  199. package/packages/cli/src/utils/config.ts +61 -0
  200. package/packages/npm-installer/bin/codex-linux +126 -0
  201. package/packages/npm-installer/lib/download.js +273 -0
  202. package/packages/npm-installer/package.json +42 -0
  203. package/packages/vscode-extension/package.json +167 -0
  204. package/packages/vscode-extension/src/api.ts +68 -0
  205. package/packages/vscode-extension/src/extension.ts +161 -0
  206. package/packages/vscode-extension/src/panels/chatPanel.ts +265 -0
  207. package/packages/vscode-extension/src/panels/createAgentPanel.ts +227 -0
  208. package/packages/vscode-extension/src/providers/agentsProvider.ts +80 -0
  209. package/postcss.config.js +6 -0
  210. package/public/manifest.json +42 -0
  211. package/public/sw.ts +109 -0
  212. package/scripts/install-dev.sh +103 -0
  213. package/scripts/install.sh +275 -0
  214. package/src/main/DatabaseManager.ts +950 -0
  215. package/src/main/SettingsManager.ts +63 -0
  216. package/src/main/agents/AgentOrchestrator.ts +930 -0
  217. package/src/main/agents/AgentSDK.ts +269 -0
  218. package/src/main/agents/AgentTools.ts +380 -0
  219. package/src/main/agents/CodeIndex.ts +240 -0
  220. package/src/main/agents/EmbeddingService.ts +88 -0
  221. package/src/main/agents/NativeToolCalling.ts +245 -0
  222. package/src/main/api/APIServer.ts +316 -0
  223. package/src/main/api/RateLimiter.ts +165 -0
  224. package/src/main/api/WebSocketManager.ts +398 -0
  225. package/src/main/assistant/ContextOptimizer.ts +214 -0
  226. package/src/main/assistant/PredictedOutputManager.ts +265 -0
  227. package/src/main/assistant/PromptCacheManager.ts +280 -0
  228. package/src/main/assistant/PromptOptimizer.ts +746 -0
  229. package/src/main/assistant/SmartCodeAssistant.ts +234 -0
  230. package/src/main/auth/SessionManager.ts +415 -0
  231. package/src/main/automations/AdvancedWebhookSystem.ts +281 -0
  232. package/src/main/automations/AutomationScheduler.ts +272 -0
  233. package/src/main/automations/BatchProcessingSystem.ts +207 -0
  234. package/src/main/automations/BrowserAutomationManager.ts +203 -0
  235. package/src/main/automations/GitHubActionsManager.ts +151 -0
  236. package/src/main/automations/GitLabCIManager.ts +206 -0
  237. package/src/main/automations/PriorityQueueManager.ts +328 -0
  238. package/src/main/background/BackgroundModeManager.ts +130 -0
  239. package/src/main/backup/BackupManager.ts +287 -0
  240. package/src/main/backup/MigrationManager.ts +132 -0
  241. package/src/main/commands/SlashCommandManager.ts +407 -0
  242. package/src/main/config/ClaudeMdParser.ts +539 -0
  243. package/src/main/config/CustomizationManager.ts +493 -0
  244. package/src/main/config/LaunchConfigManager.ts +212 -0
  245. package/src/main/config/SettingsManager.ts +163 -0
  246. package/src/main/connectors/ConnectorManager.ts +175 -0
  247. package/src/main/connectors/DatabaseConnector.ts +212 -0
  248. package/src/main/cowork/CoworkManager.ts +431 -0
  249. package/src/main/evals/AgentEvalFramework.ts +665 -0
  250. package/src/main/evals/GraderManager.ts +417 -0
  251. package/src/main/git/GitWorktreeManager.ts +211 -0
  252. package/src/main/github/GitHubPRMonitor.ts +317 -0
  253. package/src/main/ide/ContinueInManager.ts +180 -0
  254. package/src/main/ide/IDEIntegration.ts +288 -0
  255. package/src/main/integrations/LinearManager.ts +327 -0
  256. package/src/main/integrations/SlackBotManager.ts +312 -0
  257. package/src/main/lsp/LSPManager.ts +445 -0
  258. package/src/main/main.ts +1221 -0
  259. package/src/main/mcp/MCPConfigurationManager.ts +281 -0
  260. package/src/main/mcp/MCPManager.ts +799 -0
  261. package/src/main/mcp/MCPRegistry.ts +273 -0
  262. package/src/main/monitoring/ErrorRecoveryManager.ts +359 -0
  263. package/src/main/monitoring/ErrorTracker.ts +60 -0
  264. package/src/main/monitoring/MetricsCollector.ts +196 -0
  265. package/src/main/monitoring/TraceGradingSystem.ts +196 -0
  266. package/src/main/notifications/NotificationManager.ts +96 -0
  267. package/src/main/pair/AIPairProgramming.ts +290 -0
  268. package/src/main/plugins/PluginManager.ts +266 -0
  269. package/src/main/plugins/PluginMarketplace.ts +318 -0
  270. package/src/main/preload.ts +215 -0
  271. package/src/main/preview/PreviewSessionManager.ts +186 -0
  272. package/src/main/providers/AIProviderManager.ts +394 -0
  273. package/src/main/providers/FineTuningManager.ts +390 -0
  274. package/src/main/providers/FreeModelsProvider.ts +1156 -0
  275. package/src/main/realtime/RealtimeManager.ts +147 -0
  276. package/src/main/remote/CloudEnvironmentManager.ts +253 -0
  277. package/src/main/remote/RemoteSessionManager.ts +323 -0
  278. package/src/main/search/DeepResearchManager.ts +458 -0
  279. package/src/main/search/WebSearchIntegration.ts +203 -0
  280. package/src/main/security/AdminConsoleManager.ts +244 -0
  281. package/src/main/security/AuditLogger.ts +143 -0
  282. package/src/main/security/PermissionManager.ts +184 -0
  283. package/src/main/security/SSOManager.ts +241 -0
  284. package/src/main/security/SecurityManager.ts +139 -0
  285. package/src/main/skills/SkillsManager.ts +218 -0
  286. package/src/main/ssh/SSHManager.ts +86 -0
  287. package/src/main/streaming/StreamingManager.ts +306 -0
  288. package/src/main/sync/CloudSyncManager.ts +532 -0
  289. package/src/main/verification/AutoVerifyManager.ts +285 -0
  290. package/src/main/vision/ComputerUseManager.ts +475 -0
  291. package/src/main/vision/ImageVideoGenerationManager.ts +526 -0
  292. package/src/main/vision/VisionManager.ts +186 -0
  293. package/src/renderer/App.tsx +314 -0
  294. package/src/renderer/components/AdvancedSettingsPanel.tsx +225 -0
  295. package/src/renderer/components/AgentPanel.tsx +760 -0
  296. package/src/renderer/components/AppPreview.tsx +220 -0
  297. package/src/renderer/components/AuditTrailPanel.tsx +148 -0
  298. package/src/renderer/components/AutomationPanel.tsx +220 -0
  299. package/src/renderer/components/ChatInterface.tsx +595 -0
  300. package/src/renderer/components/ChatTab.tsx +296 -0
  301. package/src/renderer/components/CodeEditor.tsx +257 -0
  302. package/src/renderer/components/CodeReviewPanel.tsx +256 -0
  303. package/src/renderer/components/CodeWorkspace.tsx +192 -0
  304. package/src/renderer/components/CodebaseDashboard.tsx +295 -0
  305. package/src/renderer/components/ComputerUsePanel.tsx +262 -0
  306. package/src/renderer/components/ConnectorsPanel.tsx +471 -0
  307. package/src/renderer/components/ContextMenu.tsx +155 -0
  308. package/src/renderer/components/ContextUsageDisplay.tsx +248 -0
  309. package/src/renderer/components/CoworkPanel.tsx +415 -0
  310. package/src/renderer/components/DiffViewer.tsx +452 -0
  311. package/src/renderer/components/ErrorBoundary.tsx +273 -0
  312. package/src/renderer/components/ExtendedThinkingToggle.tsx +244 -0
  313. package/src/renderer/components/FileAttachments.tsx +247 -0
  314. package/src/renderer/components/FileExplorer.tsx +242 -0
  315. package/src/renderer/components/FileExplorerPanel.tsx +302 -0
  316. package/src/renderer/components/GitPanel.tsx +154 -0
  317. package/src/renderer/components/Header.tsx +113 -0
  318. package/src/renderer/components/MCPPanel.tsx +326 -0
  319. package/src/renderer/components/MentionAutocomplete.tsx +239 -0
  320. package/src/renderer/components/PermissionPanel.tsx +159 -0
  321. package/src/renderer/components/PermissionSelector.tsx +203 -0
  322. package/src/renderer/components/PluginMarketplace.tsx +325 -0
  323. package/src/renderer/components/PromptOptimizerPanel.tsx +399 -0
  324. package/src/renderer/components/SearchPanel.tsx +173 -0
  325. package/src/renderer/components/SearchReplace.tsx +284 -0
  326. package/src/renderer/components/SessionSidebar.tsx +367 -0
  327. package/src/renderer/components/SettingsPanel.tsx +426 -0
  328. package/src/renderer/components/Sidebar.tsx +100 -0
  329. package/src/renderer/components/SkillsPanel.tsx +245 -0
  330. package/src/renderer/components/SplitPane.tsx +173 -0
  331. package/src/renderer/components/Terminal.tsx +190 -0
  332. package/src/renderer/components/VoiceCommand.tsx +129 -0
  333. package/src/renderer/components/WorktreePanel.tsx +163 -0
  334. package/src/renderer/components/ui/AriaComponents.tsx +193 -0
  335. package/src/renderer/components/ui/Button.tsx +68 -0
  336. package/src/renderer/components/ui/Card.tsx +102 -0
  337. package/src/renderer/components/ui/Input.tsx +44 -0
  338. package/src/renderer/components/ui/Skeleton.tsx +55 -0
  339. package/src/renderer/components/ui/VirtualList.tsx +196 -0
  340. package/src/renderer/i18n/I18nProvider.tsx +101 -0
  341. package/src/renderer/i18n/de.ts +161 -0
  342. package/src/renderer/i18n/en.ts +163 -0
  343. package/src/renderer/i18n/es.ts +161 -0
  344. package/src/renderer/i18n/fr.ts +161 -0
  345. package/src/renderer/i18n/index.ts +44 -0
  346. package/src/renderer/index.css +129 -0
  347. package/src/renderer/lib/accessibility.tsx +287 -0
  348. package/src/renderer/lib/hooks.ts +304 -0
  349. package/src/renderer/lib/utils.ts +6 -0
  350. package/src/renderer/main.tsx +25 -0
  351. package/src/renderer/styles/minimalist.css +539 -0
  352. package/src/renderer/sw.ts +180 -0
  353. package/src/renderer/types.d.ts +138 -0
  354. package/src/shared/types.ts +813 -0
  355. package/supabase/schema.sql +234 -0
  356. package/tailwind.config.js +78 -0
  357. package/tests/e2e/package.json +15 -0
  358. package/tests/e2e/playwright.config.ts +31 -0
  359. package/tests/e2e/specs/app.spec.ts +194 -0
  360. package/tests/setup.ts +99 -0
  361. package/tests/unit/AgentOrchestrator.test.ts +274 -0
  362. package/tests/unit/DatabaseManager.test.ts +262 -0
  363. package/tests/unit/GitWorktreeManager.test.ts +150 -0
  364. package/tests/unit/SecurityManager.test.ts +110 -0
  365. package/tsconfig.main.json +22 -0
  366. package/tsconfig.renderer.json +27 -0
  367. 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
+ }