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,760 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { Agent, AIProvider, Skill, CodeChange, ChangeStatus } from '../../shared/types';
3
+ import {
4
+ Bot,
5
+ Plus,
6
+ Play,
7
+ Pause,
8
+ Square,
9
+ Trash2,
10
+ MessageSquare,
11
+ MoreVertical
12
+ } from 'lucide-react';
13
+ import DiffViewer from './DiffViewer';
14
+
15
+ interface AgentPanelProps {
16
+ agents: Agent[];
17
+ providers: AIProvider[];
18
+ skills: Skill[];
19
+ onCreateAgent: (config: any) => Promise<Agent>;
20
+ onDeleteAgent: (agentId: string) => void;
21
+ }
22
+
23
+ export const AgentPanel: React.FC<AgentPanelProps> = ({
24
+ agents,
25
+ providers,
26
+ skills,
27
+ onCreateAgent,
28
+ onDeleteAgent
29
+ }) => {
30
+ const [showCreateModal, setShowCreateModal] = useState(false);
31
+ const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
32
+ const [changes, setChanges] = useState<CodeChange[]>([]);
33
+ const [changesLoading, setChangesLoading] = useState(false);
34
+ const [queueItems, setQueueItems] = useState<Array<{ id: string; type: 'message' | 'task'; content: string; status?: 'pending' | 'running'; position?: number; createdAt?: number }>>([]);
35
+ const [queueHistory, setQueueHistory] = useState<Array<{ id: string; type: 'message' | 'task'; content: string; status: string; createdAt: Date; completedAt?: Date; error?: string }>>([]);
36
+ const [showQueueHistory, setShowQueueHistory] = useState(false);
37
+ const [inFlightByAgent, setInFlightByAgent] = useState<Record<string, boolean>>({});
38
+ const [claimedByAgent, setClaimedByAgent] = useState<Record<string, { id: string; type: 'message' | 'task'; content: string } | null>>({});
39
+ const [newQueueType, setNewQueueType] = useState<'message' | 'task'>('task');
40
+ const [newQueueContent, setNewQueueContent] = useState('');
41
+ const [newAgentConfig, setNewAgentConfig] = useState({
42
+ name: '',
43
+ projectPath: '',
44
+ providerId: providers[0]?.id || '',
45
+ model: '',
46
+ skills: [] as string[]
47
+ });
48
+
49
+ const selectedAgentChanges = useMemo(() => {
50
+ if (!selectedAgent) return [];
51
+ return changes.filter(c => c.agentId === selectedAgent.id);
52
+ }, [changes, selectedAgent]);
53
+
54
+ const refreshChanges = async (agentId: string) => {
55
+ setChangesLoading(true);
56
+ try {
57
+ const list = await window.electronAPI.changes.list(agentId);
58
+ setChanges(list as CodeChange[]);
59
+ } catch (error) {
60
+ console.error('Failed to load code changes:', error);
61
+ } finally {
62
+ setChangesLoading(false);
63
+ }
64
+ };
65
+
66
+ const refreshQueue = async (agentId: string) => {
67
+ try {
68
+ const list = await window.electronAPI.queue.list(agentId);
69
+ setQueueItems(list as any);
70
+ } catch (error) {
71
+ console.error('Failed to load queue:', error);
72
+ }
73
+ };
74
+
75
+ const refreshQueueHistory = async (agentId: string) => {
76
+ try {
77
+ const history = await window.electronAPI.queue.history(agentId, 20);
78
+ setQueueHistory(history as any);
79
+ } catch (error) {
80
+ console.error('Failed to load queue history:', error);
81
+ }
82
+ };
83
+
84
+ useEffect(() => {
85
+ if (!selectedAgent) return;
86
+ void refreshChanges(selectedAgent.id);
87
+ void refreshQueue(selectedAgent.id);
88
+ void refreshQueueHistory(selectedAgent.id);
89
+ }, [selectedAgent?.id]);
90
+
91
+ useEffect(() => {
92
+ const onTaskStarted = (event: any, payload: { agentId: string }) => {
93
+ setInFlightByAgent(prev => ({ ...prev, [payload.agentId]: true }));
94
+ };
95
+
96
+ const onTaskCompleted = async (event: any, payload: { agentId: string }) => {
97
+ const claimed = claimedByAgent[payload.agentId];
98
+ if (claimed) {
99
+ try {
100
+ await window.electronAPI.queue.complete(payload.agentId, claimed.id, 'completed');
101
+ await refreshQueue(payload.agentId);
102
+ } catch (error) {
103
+ console.error('Failed to complete queued item:', error);
104
+ }
105
+ setClaimedByAgent(prev => ({ ...prev, [payload.agentId]: null }));
106
+ }
107
+ setInFlightByAgent(prev => ({ ...prev, [payload.agentId]: false }));
108
+ };
109
+
110
+ const onTaskFailed = async (event: any, payload: { agentId: string; error?: any }) => {
111
+ const claimed = claimedByAgent[payload.agentId];
112
+ if (claimed) {
113
+ try {
114
+ await window.electronAPI.queue.complete(payload.agentId, claimed.id, 'failed', payload.error ? String(payload.error) : undefined);
115
+ await refreshQueue(payload.agentId);
116
+ } catch (error) {
117
+ console.error('Failed to fail queued item:', error);
118
+ }
119
+ setClaimedByAgent(prev => ({ ...prev, [payload.agentId]: null }));
120
+ }
121
+ setInFlightByAgent(prev => ({ ...prev, [payload.agentId]: false }));
122
+ };
123
+
124
+ window.electronAPI.on('agent:taskStarted', onTaskStarted);
125
+ window.electronAPI.on('agent:taskCompleted', onTaskCompleted);
126
+ window.electronAPI.on('agent:taskFailed', onTaskFailed);
127
+
128
+ return () => {
129
+ window.electronAPI.removeListener('agent:taskStarted', onTaskStarted);
130
+ window.electronAPI.removeListener('agent:taskCompleted', onTaskCompleted);
131
+ window.electronAPI.removeListener('agent:taskFailed', onTaskFailed);
132
+ };
133
+ }, [claimedByAgent]);
134
+
135
+ useEffect(() => {
136
+ if (!selectedAgent) return;
137
+ const agentId = selectedAgent.id;
138
+ const inFlight = Boolean(inFlightByAgent[agentId]);
139
+
140
+ if (inFlight) return;
141
+ if (claimedByAgent[agentId]) return;
142
+
143
+ const hasPending = queueItems.some(i => (i.status || 'pending') === 'pending');
144
+ if (!hasPending) return;
145
+
146
+ setInFlightByAgent(prev => ({ ...prev, [agentId]: true }));
147
+
148
+ (async () => {
149
+ try {
150
+ const claimed = await window.electronAPI.queue.claimNext(agentId);
151
+ if (!claimed) {
152
+ setInFlightByAgent(prev => ({ ...prev, [agentId]: false }));
153
+ return;
154
+ }
155
+
156
+ setClaimedByAgent(prev => ({ ...prev, [agentId]: claimed }));
157
+ await refreshQueue(agentId);
158
+
159
+ if (claimed.type === 'message') {
160
+ await window.electronAPI.agent.sendMessage(agentId, claimed.content);
161
+ await window.electronAPI.queue.complete(agentId, claimed.id, 'completed');
162
+ setClaimedByAgent(prev => ({ ...prev, [agentId]: null }));
163
+ await refreshQueue(agentId);
164
+ setInFlightByAgent(prev => ({ ...prev, [agentId]: false }));
165
+ } else {
166
+ await window.electronAPI.agent.executeTask(agentId, claimed.content);
167
+ // keep inFlight until taskCompleted/taskFailed
168
+ }
169
+ } catch (error) {
170
+ console.error('Failed to dispatch queued item:', error);
171
+ const claimed = claimedByAgent[agentId];
172
+ if (claimed) {
173
+ try {
174
+ await window.electronAPI.queue.complete(agentId, claimed.id, 'failed', error instanceof Error ? error.message : String(error));
175
+ } catch {
176
+ // ignore
177
+ }
178
+ setClaimedByAgent(prev => ({ ...prev, [agentId]: null }));
179
+ }
180
+ setInFlightByAgent(prev => ({ ...prev, [agentId]: false }));
181
+ await refreshQueue(agentId);
182
+ await window.electronAPI.notification.show({
183
+ title: 'Queue dispatch failed',
184
+ body: error instanceof Error ? error.message : String(error)
185
+ });
186
+ }
187
+ })();
188
+ }, [queueItems, inFlightByAgent, claimedByAgent, selectedAgent?.id]);
189
+
190
+ useEffect(() => {
191
+ const handler = (event: any, payload: { agentId: string; changeId: string }) => {
192
+ if (!selectedAgent) return;
193
+ if (payload.agentId !== selectedAgent.id) return;
194
+ void refreshChanges(selectedAgent.id);
195
+ };
196
+
197
+ window.electronAPI.on('changes:created', handler);
198
+ return () => {
199
+ window.electronAPI.removeListener('changes:created', handler);
200
+ };
201
+ }, [selectedAgent?.id]);
202
+
203
+ const handleCreateAgent = async () => {
204
+ try {
205
+ await onCreateAgent(newAgentConfig);
206
+ setShowCreateModal(false);
207
+ setNewAgentConfig({
208
+ name: '',
209
+ projectPath: '',
210
+ providerId: providers[0]?.id || '',
211
+ model: '',
212
+ skills: []
213
+ });
214
+ } catch (error) {
215
+ console.error('Failed to create agent:', error);
216
+ }
217
+ };
218
+
219
+ const handleSendMessage = async (agentId: string, message: string) => {
220
+ try {
221
+ await window.electronAPI.agent.sendMessage(agentId, message);
222
+ } catch (error) {
223
+ console.error('Failed to send message:', error);
224
+ }
225
+ };
226
+
227
+ const handleExecuteTask = async (agentId: string, task: string) => {
228
+ try {
229
+ await window.electronAPI.agent.executeTask(agentId, task);
230
+ } catch (error) {
231
+ console.error('Failed to execute task:', error);
232
+ }
233
+ };
234
+
235
+ const handleApproveChange = async (changeId: string) => {
236
+ if (!selectedAgent) return;
237
+ try {
238
+ await window.electronAPI.changes.approve(changeId);
239
+ await refreshChanges(selectedAgent.id);
240
+ await window.electronAPI.notification.show({
241
+ title: 'Change approved',
242
+ body: 'The change is approved and ready to apply.'
243
+ });
244
+ } catch (error) {
245
+ console.error('Failed to approve change:', error);
246
+ await window.electronAPI.notification.show({
247
+ title: 'Approve failed',
248
+ body: error instanceof Error ? error.message : String(error)
249
+ });
250
+ }
251
+ };
252
+
253
+ const handleRejectChange = async (changeId: string, comment?: string) => {
254
+ if (!selectedAgent) return;
255
+ try {
256
+ const finalComment =
257
+ typeof comment === 'string'
258
+ ? comment
259
+ : (window.prompt('Rejection comment (optional):') ?? undefined);
260
+
261
+ await window.electronAPI.changes.reject(changeId, finalComment);
262
+ await refreshChanges(selectedAgent.id);
263
+ await window.electronAPI.notification.show({
264
+ title: 'Change rejected',
265
+ body: 'The change was rejected.'
266
+ });
267
+ } catch (error) {
268
+ console.error('Failed to reject change:', error);
269
+ await window.electronAPI.notification.show({
270
+ title: 'Reject failed',
271
+ body: error instanceof Error ? error.message : String(error)
272
+ });
273
+ }
274
+ };
275
+
276
+ const handleApplyChange = async (changeId: string) => {
277
+ if (!selectedAgent) return;
278
+ try {
279
+ await window.electronAPI.changes.apply(changeId);
280
+ await refreshChanges(selectedAgent.id);
281
+ await window.electronAPI.notification.show({
282
+ title: 'Change applied',
283
+ body: 'The change was written to the worktree.'
284
+ });
285
+ } catch (error) {
286
+ console.error('Failed to apply change:', error);
287
+ await window.electronAPI.notification.show({
288
+ title: 'Apply failed',
289
+ body: error instanceof Error ? error.message : String(error)
290
+ });
291
+ }
292
+ };
293
+
294
+ const getStatusIcon = (status: string) => {
295
+ switch (status) {
296
+ case 'running':
297
+ return <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />;
298
+ case 'paused':
299
+ return <div className="w-2 h-2 bg-yellow-500 rounded-full" />;
300
+ case 'error':
301
+ return <div className="w-2 h-2 bg-red-500 rounded-full" />;
302
+ default:
303
+ return <div className="w-2 h-2 bg-gray-400 rounded-full" />;
304
+ }
305
+ };
306
+
307
+ return (
308
+ <div className="h-full flex">
309
+ {/* Agents List */}
310
+ <div className="w-80 border-r border-border bg-card flex flex-col">
311
+ <div className="p-4 border-b border-border flex items-center justify-between">
312
+ <h2 className="font-semibold">Active Agents</h2>
313
+ <button
314
+ onClick={() => setShowCreateModal(true)}
315
+ className="p-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
316
+ >
317
+ <Plus className="w-4 h-4" />
318
+ </button>
319
+ </div>
320
+
321
+ <div className="flex-1 overflow-auto p-2 space-y-2">
322
+ {agents.map(agent => (
323
+ <div
324
+ key={agent.id}
325
+ onClick={() => setSelectedAgent(agent)}
326
+ className={`p-3 rounded-lg cursor-pointer transition-colors ${
327
+ selectedAgent?.id === agent.id
328
+ ? 'bg-primary/10 border border-primary/30'
329
+ : 'hover:bg-muted border border-transparent'
330
+ }`}
331
+ >
332
+ <div className="flex items-start justify-between">
333
+ <div className="flex items-center gap-2">
334
+ <Bot className="w-4 h-4 text-muted-foreground" />
335
+ <span className="font-medium text-sm">{agent.name}</span>
336
+ </div>
337
+ {getStatusIcon(agent.status)}
338
+ </div>
339
+ <div className="mt-2 text-xs text-muted-foreground">
340
+ <div className="truncate">{agent.projectPath}</div>
341
+ <div className="mt-1">{agent.model}</div>
342
+ </div>
343
+ </div>
344
+ ))}
345
+
346
+ {agents.length === 0 && (
347
+ <div className="text-center py-8 text-muted-foreground">
348
+ <Bot className="w-12 h-12 mx-auto mb-2 opacity-50" />
349
+ <p className="text-sm">No agents yet</p>
350
+ <p className="text-xs mt-1">Create your first agent to get started</p>
351
+ </div>
352
+ )}
353
+ </div>
354
+ </div>
355
+
356
+ {/* Agent Detail View */}
357
+ <div className="flex-1 flex flex-col">
358
+ {selectedAgent ? (
359
+ <>
360
+ <div className="p-4 border-b border-border flex items-center justify-between">
361
+ <div>
362
+ <h2 className="text-lg font-semibold">{selectedAgent.name}</h2>
363
+ <p className="text-sm text-muted-foreground">
364
+ {selectedAgent.projectPath} • {selectedAgent.model}
365
+ </p>
366
+ </div>
367
+ <div className="flex items-center gap-2">
368
+ <button
369
+ onClick={() => handleSendMessage(selectedAgent.id, 'Hello!')}
370
+ className="px-3 py-1.5 text-sm bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
371
+ >
372
+ <MessageSquare className="w-4 h-4 inline mr-1" />
373
+ Chat
374
+ </button>
375
+ <button
376
+ onClick={() => onDeleteAgent(selectedAgent.id)}
377
+ className="p-2 text-destructive hover:bg-destructive/10 rounded-md"
378
+ >
379
+ <Trash2 className="w-4 h-4" />
380
+ </button>
381
+ </div>
382
+ </div>
383
+
384
+ <div className="flex-1 overflow-auto p-4">
385
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
386
+ <div className="space-y-4">
387
+ <div className="bg-card border border-border rounded-lg p-3">
388
+ <div className="flex items-center justify-between">
389
+ <div>
390
+ <h3 className="text-sm font-medium">Queue</h3>
391
+ <p className="text-xs text-muted-foreground">Stack work while the agent is busy</p>
392
+ </div>
393
+ <span className="text-xs text-muted-foreground">
394
+ {queueItems.length} queued
395
+ {inFlightByAgent[selectedAgent.id] ? ' • running' : ''}
396
+ </span>
397
+ </div>
398
+
399
+ <div className="mt-3 flex gap-2">
400
+ <select
401
+ value={newQueueType}
402
+ onChange={e => setNewQueueType(e.target.value as any)}
403
+ className="px-2 py-2 bg-background border border-input rounded-md text-sm"
404
+ >
405
+ <option value="task">Task</option>
406
+ <option value="message">Message</option>
407
+ </select>
408
+ <input
409
+ value={newQueueContent}
410
+ onChange={e => setNewQueueContent(e.target.value)}
411
+ placeholder={newQueueType === 'task' ? 'Describe the task...' : 'Type a message...'}
412
+ className="flex-1 px-3 py-2 bg-background border border-input rounded-md text-sm"
413
+ />
414
+ <button
415
+ onClick={() => {
416
+ if (!newQueueContent.trim() || !selectedAgent) return;
417
+ const agentId = selectedAgent.id;
418
+ (async () => {
419
+ try {
420
+ await window.electronAPI.queue.enqueue(agentId, newQueueType, newQueueContent.trim());
421
+ setNewQueueContent('');
422
+ await refreshQueue(agentId);
423
+ } catch (error) {
424
+ await window.electronAPI.notification.show({
425
+ title: 'Enqueue failed',
426
+ body: error instanceof Error ? error.message : String(error)
427
+ });
428
+ }
429
+ })();
430
+ }}
431
+ className="px-3 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 text-sm"
432
+ >
433
+ Add
434
+ </button>
435
+ </div>
436
+
437
+ <div className="mt-3 space-y-2 max-h-40 overflow-auto">
438
+ {queueItems.map((item, idx) => (
439
+ <div key={item.id} className="flex items-center gap-2 p-2 bg-muted rounded-md">
440
+ <span className="text-xs px-2 py-0.5 rounded-full bg-background border border-border">
441
+ {item.type}
442
+ </span>
443
+ <span className="text-sm flex-1 truncate">{item.content}</span>
444
+ <button
445
+ onClick={() => {
446
+ const agentId = selectedAgent.id;
447
+ (async () => {
448
+ await window.electronAPI.queue.delete(agentId, item.id);
449
+ await refreshQueue(agentId);
450
+ })();
451
+ }}
452
+ className="text-xs text-destructive hover:underline"
453
+ >
454
+ Remove
455
+ </button>
456
+ <button
457
+ disabled={idx === 0}
458
+ onClick={() => {
459
+ const agentId = selectedAgent.id;
460
+ (async () => {
461
+ await window.electronAPI.queue.moveUp(agentId, item.id);
462
+ await refreshQueue(agentId);
463
+ })();
464
+ }}
465
+ className="text-xs text-muted-foreground disabled:opacity-50"
466
+ >
467
+ Up
468
+ </button>
469
+ </div>
470
+ ))}
471
+ {queueItems.length === 0 && (
472
+ <div className="text-xs text-muted-foreground">No queued items</div>
473
+ )}
474
+ </div>
475
+
476
+ {/* Queue History */}
477
+ <div className="mt-3">
478
+ <button
479
+ onClick={() => setShowQueueHistory(!showQueueHistory)}
480
+ className="text-xs text-muted-foreground hover:text-foreground"
481
+ >
482
+ {showQueueHistory ? 'Hide' : 'Show'} history ({queueHistory.length})
483
+ </button>
484
+ {showQueueHistory && (
485
+ <div className="mt-2 space-y-1 max-h-32 overflow-auto">
486
+ {queueHistory.map(item => (
487
+ <div key={item.id} className="flex items-center gap-2 p-2 bg-muted/50 rounded-md text-xs">
488
+ <span className={`px-1.5 py-0.5 rounded-full ${
489
+ item.status === 'completed' ? 'bg-green-500/10 text-green-500' : 'bg-red-500/10 text-red-500'
490
+ }`}>
491
+ {item.status}
492
+ </span>
493
+ <span className="text-muted-foreground">{item.type}</span>
494
+ <span className="flex-1 truncate">{item.content}</span>
495
+ {item.error && (
496
+ <span className="text-red-500 truncate" title={item.error}>⚠️</span>
497
+ )}
498
+ </div>
499
+ ))}
500
+ {queueHistory.length === 0 && (
501
+ <div className="text-xs text-muted-foreground">No completed items yet</div>
502
+ )}
503
+ </div>
504
+ )}
505
+ </div>
506
+ </div>
507
+
508
+ <div>
509
+ <h3 className="text-sm font-medium mb-2">Status</h3>
510
+ <div className="flex items-center gap-2">
511
+ <span className={`px-2 py-1 rounded-full text-xs font-medium ${
512
+ selectedAgent.status === 'running' ? 'bg-green-500/10 text-green-500' :
513
+ selectedAgent.status === 'paused' ? 'bg-yellow-500/10 text-yellow-500' :
514
+ selectedAgent.status === 'error' ? 'bg-red-500/10 text-red-500' :
515
+ 'bg-gray-500/10 text-gray-500'
516
+ }`}>
517
+ {selectedAgent.status}
518
+ </span>
519
+ </div>
520
+ </div>
521
+
522
+ <div>
523
+ <h3 className="text-sm font-medium mb-2">Skills</h3>
524
+ <div className="flex flex-wrap gap-2">
525
+ {selectedAgent.skills.map(skillId => {
526
+ const skill = skills.find(s => s.id === skillId);
527
+ return (
528
+ <span
529
+ key={skillId}
530
+ className="px-2 py-1 bg-muted rounded-md text-xs"
531
+ >
532
+ {skill?.name || skillId}
533
+ </span>
534
+ );
535
+ })}
536
+ {selectedAgent.skills.length === 0 && (
537
+ <span className="text-sm text-muted-foreground">No skills applied</span>
538
+ )}
539
+ </div>
540
+ </div>
541
+
542
+ <div>
543
+ <h3 className="text-sm font-medium mb-2">Recent Tasks</h3>
544
+ <div className="space-y-2">
545
+ {selectedAgent.tasks.slice(-5).map(task => (
546
+ <div
547
+ key={task.id}
548
+ className="p-3 bg-muted rounded-md"
549
+ >
550
+ <div className="flex items-center justify-between">
551
+ <span className="text-sm font-medium">{task.description}</span>
552
+ <span className={`text-xs ${
553
+ task.status === 'completed' ? 'text-green-500' :
554
+ task.status === 'failed' ? 'text-red-500' :
555
+ task.status === 'running' ? 'text-blue-500' :
556
+ 'text-muted-foreground'
557
+ }`}>
558
+ {task.status}
559
+ </span>
560
+ </div>
561
+ {task.status === 'running' && (
562
+ <div className="mt-2 h-1 bg-muted-foreground/20 rounded-full overflow-hidden">
563
+ <div
564
+ className="h-full bg-primary transition-all"
565
+ style={{ width: `${task.progress}%` }}
566
+ />
567
+ </div>
568
+ )}
569
+ </div>
570
+ ))}
571
+ {selectedAgent.tasks.length === 0 && (
572
+ <span className="text-sm text-muted-foreground">No tasks yet</span>
573
+ )}
574
+ </div>
575
+ </div>
576
+ </div>
577
+
578
+ <div className="bg-card border border-border rounded-lg overflow-hidden min-h-[420px]">
579
+ <div className="p-3 border-b border-border flex items-center justify-between">
580
+ <div>
581
+ <h3 className="text-sm font-medium">Changes</h3>
582
+ <p className="text-xs text-muted-foreground">
583
+ Review and apply changes generated by the agent
584
+ </p>
585
+ </div>
586
+ <button
587
+ onClick={async () => {
588
+ if (!selectedAgent) return;
589
+ try {
590
+ const checkpoints = await window.electronAPI.checkpoints.list(selectedAgent.id);
591
+ const lastPending = (checkpoints as any[]).find(c => !c.restoredAt);
592
+ const restoredFilePath = lastPending?.filePath as string | undefined;
593
+
594
+ await window.electronAPI.checkpoints.restoreLast(selectedAgent.id);
595
+ await refreshChanges(selectedAgent.id);
596
+ await window.electronAPI.notification.show({
597
+ title: 'Checkpoint restored',
598
+ body: restoredFilePath
599
+ ? `Rolled back: ${restoredFilePath}`
600
+ : 'Last applied change was rolled back.'
601
+ });
602
+ } catch (error) {
603
+ await window.electronAPI.notification.show({
604
+ title: 'Rollback failed',
605
+ body: error instanceof Error ? error.message : String(error)
606
+ });
607
+ }
608
+ }}
609
+ className="px-3 py-1.5 text-xs bg-muted hover:bg-muted/80 rounded-md"
610
+ >
611
+ Undo last apply
612
+ </button>
613
+ {changesLoading && (
614
+ <span className="text-xs text-muted-foreground">Loading...</span>
615
+ )}
616
+ </div>
617
+
618
+ <div className="h-[520px]">
619
+ <DiffViewer
620
+ changes={selectedAgentChanges.filter(c => c.status !== ChangeStatus.APPLIED)}
621
+ onApprove={handleApproveChange}
622
+ onReject={handleRejectChange}
623
+ onApply={handleApplyChange}
624
+ />
625
+ </div>
626
+ </div>
627
+ </div>
628
+ </div>
629
+ </>
630
+ ) : (
631
+ <div className="flex-1 flex items-center justify-center text-muted-foreground">
632
+ <div className="text-center">
633
+ <Bot className="w-16 h-16 mx-auto mb-4 opacity-30" />
634
+ <p>Select an agent to view details</p>
635
+ </div>
636
+ </div>
637
+ )}
638
+ </div>
639
+
640
+ {/* Create Agent Modal */}
641
+ {showCreateModal && (
642
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
643
+ <div className="bg-card border border-border rounded-lg p-6 w-[500px] max-w-[90vw]">
644
+ <h2 className="text-lg font-semibold mb-4">Create New Agent</h2>
645
+
646
+ <div className="space-y-4">
647
+ <div>
648
+ <label className="block text-sm font-medium mb-1">Name</label>
649
+ <input
650
+ type="text"
651
+ value={newAgentConfig.name}
652
+ onChange={e => setNewAgentConfig({ ...newAgentConfig, name: e.target.value })}
653
+ className="w-full px-3 py-2 bg-background border border-input rounded-md"
654
+ placeholder="My Coding Agent"
655
+ />
656
+ </div>
657
+
658
+ <div>
659
+ <label className="block text-sm font-medium mb-1">Project Path</label>
660
+ <div className="flex gap-2">
661
+ <input
662
+ type="text"
663
+ value={newAgentConfig.projectPath}
664
+ onChange={e => setNewAgentConfig({ ...newAgentConfig, projectPath: e.target.value })}
665
+ className="flex-1 px-3 py-2 bg-background border border-input rounded-md"
666
+ placeholder="/path/to/project"
667
+ />
668
+ <button
669
+ onClick={async () => {
670
+ const path = await window.electronAPI.dialog.selectFolder();
671
+ if (path) setNewAgentConfig({ ...newAgentConfig, projectPath: path });
672
+ }}
673
+ className="px-3 py-2 bg-muted rounded-md hover:bg-muted/80"
674
+ >
675
+ Browse
676
+ </button>
677
+ </div>
678
+ </div>
679
+
680
+ <div>
681
+ <label className="block text-sm font-medium mb-1">Provider</label>
682
+ <select
683
+ value={newAgentConfig.providerId}
684
+ onChange={e => {
685
+ const provider = providers.find(p => p.id === e.target.value);
686
+ setNewAgentConfig({
687
+ ...newAgentConfig,
688
+ providerId: e.target.value,
689
+ model: provider?.models[0]?.id || ''
690
+ });
691
+ }}
692
+ className="w-full px-3 py-2 bg-background border border-input rounded-md"
693
+ >
694
+ {providers.map(provider => (
695
+ <option key={provider.id} value={provider.id}>{provider.name}</option>
696
+ ))}
697
+ </select>
698
+ </div>
699
+
700
+ <div>
701
+ <label className="block text-sm font-medium mb-1">Model</label>
702
+ <select
703
+ value={newAgentConfig.model}
704
+ onChange={e => setNewAgentConfig({ ...newAgentConfig, model: e.target.value })}
705
+ className="w-full px-3 py-2 bg-background border border-input rounded-md"
706
+ >
707
+ {providers
708
+ .find(p => p.id === newAgentConfig.providerId)
709
+ ?.models.map(model => (
710
+ <option key={model.id} value={model.id}>{model.name}</option>
711
+ ))}
712
+ </select>
713
+ </div>
714
+
715
+ <div>
716
+ <label className="block text-sm font-medium mb-1">Skills</label>
717
+ <div className="flex flex-wrap gap-2">
718
+ {skills.map(skill => (
719
+ <button
720
+ key={skill.id}
721
+ onClick={() => {
722
+ const newSkills = newAgentConfig.skills.includes(skill.id)
723
+ ? newAgentConfig.skills.filter(id => id !== skill.id)
724
+ : [...newAgentConfig.skills, skill.id];
725
+ setNewAgentConfig({ ...newAgentConfig, skills: newSkills });
726
+ }}
727
+ className={`px-3 py-1.5 rounded-md text-sm transition-colors ${
728
+ newAgentConfig.skills.includes(skill.id)
729
+ ? 'bg-primary text-primary-foreground'
730
+ : 'bg-muted hover:bg-muted/80'
731
+ }`}
732
+ >
733
+ {skill.name}
734
+ </button>
735
+ ))}
736
+ </div>
737
+ </div>
738
+ </div>
739
+
740
+ <div className="flex justify-end gap-2 mt-6">
741
+ <button
742
+ onClick={() => setShowCreateModal(false)}
743
+ className="px-4 py-2 text-muted-foreground hover:text-foreground"
744
+ >
745
+ Cancel
746
+ </button>
747
+ <button
748
+ onClick={handleCreateAgent}
749
+ disabled={!newAgentConfig.name || !newAgentConfig.projectPath}
750
+ className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50"
751
+ >
752
+ Create Agent
753
+ </button>
754
+ </div>
755
+ </div>
756
+ </div>
757
+ )}
758
+ </div>
759
+ );
760
+ };