From 416f373f63025f57cb17a7584a0c6fc167d55035 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Wed, 17 Dec 2025 23:22:02 -0600 Subject: [PATCH 1/5] ## CHANGES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added Sentry crash reporting for error tracking and debugging ๐Ÿ› - Implemented opt-out privacy setting for anonymous crash reports ๐Ÿ”’ - Enhanced agent spawning with generic config options for session continuity ๐Ÿ”ง - Fixed tab creation null safety checks across the codebase ๐Ÿ›ก๏ธ - Added Edit Agent action to Quick Actions modal for faster access โšก - Enabled bookmark toggle directly from Quick Actions menu ๐Ÿ“Œ - Improved batch processing to run in read-only mode by default ๐Ÿ“ - Cleaned up queued items display by removing redundant tab indicators ๐Ÿงน - Strengthened null checks in tab helper functions for stability ๐Ÿ’ช - Updated version to 0.9.1 with comprehensive bug fixes and improvements ๐Ÿš€ --- package-lock.json | 912 +++++++++++++++++- package.json | 3 +- .../components/QuickActionsModal.test.tsx | 4 +- src/main/agent-detector.ts | 1 + src/main/index.ts | 27 + src/main/ipc/handlers/process.ts | 5 +- src/renderer/App.tsx | 76 +- src/renderer/components/QueuedItemsList.tsx | 12 +- src/renderer/components/QuickActionsModal.tsx | 13 +- src/renderer/components/SettingsModal.tsx | 15 +- src/renderer/hooks/useAgentExecution.ts | 11 +- .../hooks/useAgentSessionManagement.ts | 5 +- src/renderer/hooks/useMainKeyboardHandler.ts | 14 +- src/renderer/hooks/useRemoteIntegration.ts | 1 + src/renderer/hooks/useSettings.ts | 18 + src/renderer/main.tsx | 7 + src/renderer/utils/tabHelpers.ts | 26 +- 17 files changed, 1075 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18fc2f1d5..4b09bf24b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "maestro", - "version": "0.8.7", + "version": "0.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "maestro", - "version": "0.8.7", + "version": "0.9.1", "hasInstallScript": true, "license": "AGPL 3.0", "dependencies": { @@ -16,6 +16,7 @@ "@fastify/rate-limit": "^9.1.0", "@fastify/static": "^7.0.4", "@fastify/websocket": "^9.0.0", + "@sentry/electron": "^7.5.0", "@tanstack/react-virtual": "^3.13.13", "@types/dompurify": "^3.0.5", "adm-zip": "^0.5.16", @@ -131,6 +132,23 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@apm-js-collab/code-transformer": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz", + "integrity": "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==", + "license": "Apache-2.0" + }, + "node_modules/@apm-js-collab/tracing-hooks": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.1.tgz", + "integrity": "sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==", + "license": "Apache-2.0", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.8.0", + "debug": "^4.4.1", + "module-details-from-path": "^1.0.4" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", @@ -1937,6 +1955,505 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", + "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", + "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.55.0.tgz", + "integrity": "sha512-5ULoU8p+tWcQw5PDYZn8rySptGSLZHNX/7srqo2TioPnAAcvTy6sQFQXsNPrAnyRRtYGMetXVyZUy5OaX1+IfA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.52.0.tgz", + "integrity": "sha512-GXPxfNB5szMbV3I9b7kNWSmQBoBzw7MT0ui6iU/p+NIzVx3a06Ri2cdQO7tG9EKb4aKSLmfX9Cw5cKxXqX6Ohg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.26.0.tgz", + "integrity": "sha512-P2BgnFfTOarZ5OKPmYfbXfDFjQ4P9WkQ1Jji7yH5/WwB6Wm/knynAoA1rxbjWcDlYupFkyT0M1j6XLzDzy0aCA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.57.0.tgz", + "integrity": "sha512-HAdx/o58+8tSR5iW+ru4PHnEejyKrAy9fYFhlEI81o10nYxrGahnMAHWiSjhDC7UQSY3I4gjcPgSKQz4rm/asg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.28.0.tgz", + "integrity": "sha512-FFvg8fq53RRXVBRHZViP+EMxMR03tqzEGpuq55lHNbVPyFklSVfQBN50syPhK5UYYwaStx0eyCtHtbRreusc5g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.52.0.tgz", + "integrity": "sha512-ISkNcv5CM2IwvsMVL31Tl61/p2Zm2I2NAsYq5SSBgOsOndT0TjnptjufYVScCnD5ZLD1tpl4T3GEYULLYOdIdQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.56.0.tgz", + "integrity": "sha512-IPvNk8AFoVzTAM0Z399t34VDmGDgwT6rIqCUug8P9oAGerl2/PEIYMPOl/rerPGu+q8gSWdmbFSjgg7PDVRd3Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.55.0.tgz", + "integrity": "sha512-prqAkRf9e4eEpy4G3UcR32prKE8NLNlA90TdEU1UsghOTg0jUvs40Jz8LQWFEs5NbLbXHYGzB4CYVkCI8eWEVQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.208.0.tgz", + "integrity": "sha512-rhmK46DRWEbQQB77RxmVXGyjs6783crXCnFjYQj+4tDH/Kpv9Rbg3h2kaNyp5Vz2emF1f9HOQQvZoHzwMWOFZQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/instrumentation": "0.208.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.56.0.tgz", + "integrity": "sha512-XSWeqsd3rKSsT3WBz/JKJDcZD4QYElZEa0xVdX8f9dh4h4QgXhKRLorVsVkK3uXFbC2sZKAS2Ds+YolGwD83Dg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/redis-common": "^0.38.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.18.0.tgz", + "integrity": "sha512-KCL/1HnZN5zkUMgPyOxfGjLjbXjpd4odDToy+7c+UsthIzVLFf99LnfIBE8YSSrYE4+uS7OwJMhvhg3tWjqMBg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.53.0.tgz", + "integrity": "sha512-xngn5cH2mVXFmiT1XfQ1aHqq1m4xb5wvU6j9lSgLlihJ1bXzsO543cpDwjrZm2nMrlpddBf55w8+bfS4qDh60g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.57.0.tgz", + "integrity": "sha512-3JS8PU/D5E3q295mwloU2v7c7/m+DyCqdu62BIzWt+3u9utjxC9QS7v6WmUNuoDN3RM+Q+D1Gpj13ERo+m7CGg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.53.0.tgz", + "integrity": "sha512-LDwWz5cPkWWr0HBIuZUjslyvijljTwmwiItpMTHujaULZCxcYE9eU44Qf/pbVC8TulT0IhZi+RoGvHKXvNhysw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.61.0.tgz", + "integrity": "sha512-OV3i2DSoY5M/pmLk+68xr5RvkHU8DRB3DKMzYJdwDdcxeLs62tLbkmRyqJZsYf3Ht7j11rq35pHOWLuLzXL7pQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.55.0.tgz", + "integrity": "sha512-5afj0HfF6aM6Nlqgu6/PPHFk8QBfIe3+zF9FGpX76jWPS0/dujoEYn82/XcLSaW5LPUDW8sni+YeK0vTBNri+w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.54.0.tgz", + "integrity": "sha512-bqC1YhnwAeWmRzy1/Xf9cDqxNG2d/JDkaxnqF5N6iJKN1eVWI+vg7NfDkf52/Nggp3tl1jcC++ptC61BD6738A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@types/mysql": "2.15.27" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.55.0.tgz", + "integrity": "sha512-0cs8whQG55aIi20gnK8B7cco6OK6N+enNhW0p5284MvqJ5EPi+I1YlWsWXgzv/V2HFirEejkvKiI4Iw21OqDWg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.61.0.tgz", + "integrity": "sha512-UeV7KeTnRSM7ECHa3YscoklhUtTQPs6V6qYpG283AB7xpnPGCUCUfECFT9jFg6/iZOQTt3FHkB1wGTJCNZEvPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.6" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.57.0.tgz", + "integrity": "sha512-bCxTHQFXzrU3eU1LZnOZQ3s5LURxQPDlU3/upBzlWY77qOI1GZuGofazj3jtzjctMJeBEJhNwIFEgRPBX1kp/Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.27.0.tgz", + "integrity": "sha512-jRtyUJNZppPBjPae4ZjIQ2eqJbcRaRfJkr0lQLHFmOU/no5A6e9s1OHLd5XZyZoBJ/ymngZitanyRRA5cniseA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.208.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.19.0.tgz", + "integrity": "sha512-Pst/RhR61A2OoZQZkn6OLpdVpXp6qn3Y92wXa6umfJe9rV640r4bc6SWvw4pPN6DiQqPu2c8gnSSZPDtC6JlpQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/semantic-conventions": "^1.24.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", + "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", + "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -1969,6 +2486,18 @@ "node": ">=18" } }, + "node_modules/@prisma/instrumentation": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.19.0.tgz", + "integrity": "sha512-QcuYy25pkXM8BJ37wVFBO7Zh34nyRV1GOb2n3lPkkbRYfl4hWl3PTcImP41P0KrzVXfa/45p6eVCos27x3exIg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": ">=0.52.0 <1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.8" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -2284,6 +2813,204 @@ "win32" ] }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.29.0.tgz", + "integrity": "sha512-M3kycMY6f3KY9a8jDYac+yG0E3ZgWVWSxlOEC5MhYyX+g7mqxkwrb3LFQyuxSm/m+CCgMTCaPOOaB2twXP6EQg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.29.0.tgz", + "integrity": "sha512-Y7IRsNeS99cEONu1mZWZc3HvbjNnu59Hgymm0swFFKbdgbCgdT6l85kn2oLsuq4Ew8Dw/pL/Sgpwsl9UgYFpUg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.29.0.tgz", + "integrity": "sha512-45NVw9PwB9TQ8z+xJ6G6Za+wmQ1RTA35heBSzR6U4bknj8LmA04k2iwnobvxCBEQXeLfcJEO1vFgagMoqMZMBw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.29.0", + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.29.0.tgz", + "integrity": "sha512-typY4JrpAQQGPuSyd/BD8+nNCbvTV2UVvKzr+iKgI0m1qc4Dz8tHZ4Nfais2Z8eYn/pL1kqVQN5ERTmJoYFdIw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.29.0", + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.29.0.tgz", + "integrity": "sha512-XdbyIR6F4qoR9Z1JCWTgunVcTJjS9p2Th+v4wYs4ME+ZdLC4tuKKmRgYg3YdSIWCn1CBfIgdI6wqETSf7H6Njw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.29.0", + "@sentry-internal/feedback": "10.29.0", + "@sentry-internal/replay": "10.29.0", + "@sentry-internal/replay-canvas": "10.29.0", + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/core": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.29.0.tgz", + "integrity": "sha512-olQ2DU9dA/Bwsz3PtA9KNXRMqBWRQSkPw+MxwWEoU1K1qtiM9L0j6lbEFb5iSY3d7WYD5MB+1d5COugjSBrHtw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/electron": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-7.5.0.tgz", + "integrity": "sha512-88t/YsB5iO75faKdd7lIuJkwp9FGKgFlkDuaSJhsJiVcjlywkn8CwUbctAbS0gu6Suc0raHCF4ULvGyksKAoww==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.29.0", + "@sentry/core": "10.29.0", + "@sentry/node": "10.29.0" + }, + "peerDependencies": { + "@sentry/node-native": "10.29.0" + }, + "peerDependenciesMeta": { + "@sentry/node-native": { + "optional": true + } + } + }, + "node_modules/@sentry/node": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.29.0.tgz", + "integrity": "sha512-9j8VzV06VCj+H8tlxpfa7BNN4HzH5exv68WOufdMTXzzWLOXnzrdNDoYplm1G2S3LMvWsc1SVI3a8A0yBY7oWg==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^2.2.0", + "@opentelemetry/core": "^2.2.0", + "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/instrumentation-amqplib": "0.55.0", + "@opentelemetry/instrumentation-connect": "0.52.0", + "@opentelemetry/instrumentation-dataloader": "0.26.0", + "@opentelemetry/instrumentation-express": "0.57.0", + "@opentelemetry/instrumentation-fs": "0.28.0", + "@opentelemetry/instrumentation-generic-pool": "0.52.0", + "@opentelemetry/instrumentation-graphql": "0.56.0", + "@opentelemetry/instrumentation-hapi": "0.55.0", + "@opentelemetry/instrumentation-http": "0.208.0", + "@opentelemetry/instrumentation-ioredis": "0.56.0", + "@opentelemetry/instrumentation-kafkajs": "0.18.0", + "@opentelemetry/instrumentation-knex": "0.53.0", + "@opentelemetry/instrumentation-koa": "0.57.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.53.0", + "@opentelemetry/instrumentation-mongodb": "0.61.0", + "@opentelemetry/instrumentation-mongoose": "0.55.0", + "@opentelemetry/instrumentation-mysql": "0.54.0", + "@opentelemetry/instrumentation-mysql2": "0.55.0", + "@opentelemetry/instrumentation-pg": "0.61.0", + "@opentelemetry/instrumentation-redis": "0.57.0", + "@opentelemetry/instrumentation-tedious": "0.27.0", + "@opentelemetry/instrumentation-undici": "0.19.0", + "@opentelemetry/resources": "^2.2.0", + "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/semantic-conventions": "^1.37.0", + "@prisma/instrumentation": "6.19.0", + "@sentry/core": "10.29.0", + "@sentry/node-core": "10.29.0", + "@sentry/opentelemetry": "10.29.0", + "import-in-the-middle": "^2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.29.0.tgz", + "integrity": "sha512-f/Y0okHhPPb5HnYNBqCivJ2YuXtSadvcIx16dzU5mHQxZhgGednUCPEX7rsvPcd4HneQz12HKLqxbAmNu+b3FA==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/tracing-hooks": "^0.3.1", + "@sentry/core": "10.29.0", + "@sentry/opentelemetry": "10.29.0", + "import-in-the-middle": "^2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/resources": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/semantic-conventions": "^1.37.0" + } + }, + "node_modules/@sentry/node/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.29.0.tgz", + "integrity": "sha512-5QvtAwS73HlI/+OTF1poAFELzsc0se+PHmMsXGGrOeNBvjCr3ZE8qvke09aeMn7uRImf3Nc9J6i2KtSHJnbKPA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/semantic-conventions": "^1.37.0" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -2533,6 +3260,15 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -2883,16 +3619,44 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.19.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, "node_modules/@types/plist": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", @@ -2979,6 +3743,15 @@ "@types/node": "*" } }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3210,6 +3983,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3217,6 +3991,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/adm-zip": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", @@ -4624,6 +5407,12 @@ "node": ">=8" } }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -7447,6 +8236,12 @@ "node": ">= 0.6" } }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -8281,6 +9076,18 @@ ], "license": "BSD-3-Clause" }, + "node_modules/import-in-the-middle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz", + "integrity": "sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -10711,6 +11518,12 @@ "obliterator": "^2.0.1" } }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11276,6 +12089,37 @@ "dev": true, "license": "MIT" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11643,6 +12487,45 @@ "dev": true, "license": "MIT" }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -12349,6 +13232,19 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -13788,7 +14684,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unified": { @@ -15502,6 +16397,15 @@ "dev": true, "license": "MIT" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index cdaac9112..f90b66ed4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maestro", - "version": "0.9.0", + "version": "0.9.1", "description": "Run AI coding agents autonomously for days.", "main": "dist/main/index.js", "author": { @@ -150,6 +150,7 @@ "@fastify/rate-limit": "^9.1.0", "@fastify/static": "^7.0.4", "@fastify/websocket": "^9.0.0", + "@sentry/electron": "^7.5.0", "@tanstack/react-virtual": "^3.13.13", "@types/dompurify": "^3.0.5", "adm-zip": "^0.5.16", diff --git a/src/__tests__/renderer/components/QuickActionsModal.test.tsx b/src/__tests__/renderer/components/QuickActionsModal.test.tsx index ab8e7b6a5..6a8382bf8 100644 --- a/src/__tests__/renderer/components/QuickActionsModal.test.tsx +++ b/src/__tests__/renderer/components/QuickActionsModal.test.tsx @@ -785,10 +785,12 @@ describe('QuickActionsModal', () => { }); render(); + // Filter to just sessions so we can reliably test Cmd+0 const input = screen.getByPlaceholderText('Type a command or jump to agent...'); + fireEvent.change(input, { target: { value: 'Session' } }); fireEvent.keyDown(input, { key: '0', metaKey: true }); - // Should trigger the 10th item + // Should trigger the 10th session (Session 9 due to alphabetical sorting) expect(props.setActiveSessionId).toHaveBeenCalled(); }); }); diff --git a/src/main/agent-detector.ts b/src/main/agent-detector.ts index 86f596fa3..c40111436 100644 --- a/src/main/agent-detector.ts +++ b/src/main/agent-detector.ts @@ -62,6 +62,7 @@ const AGENT_DEFINITIONS: Omit ['--resume', sessionId], // Resume with session ID + readOnlyArgs: ['--permission-mode', 'plan'], // Read-only/plan mode }, { id: 'codex', diff --git a/src/main/index.ts b/src/main/index.ts index a40de6fef..db9612eb9 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,6 +3,7 @@ import path from 'path'; import os from 'os'; import fs from 'fs/promises'; import fsSync from 'fs'; +import * as Sentry from '@sentry/electron/main'; import { ProcessManager } from './process-manager'; import { WebServer } from './web-server'; import { AgentDetector } from './agent-detector'; @@ -17,6 +18,32 @@ import { initializeOutputParsers } from './parsers'; import { DEMO_MODE, DEMO_DATA_PATH } from './constants'; import { initAutoUpdater } from './auto-updater'; +// Initialize Sentry for crash reporting (before app.ready) +// Check if crash reporting is enabled (default: true for opt-out behavior) +const crashReportingStore = new Store<{ crashReportingEnabled: boolean }>({ + name: 'maestro-settings', +}); +const crashReportingEnabled = crashReportingStore.get('crashReportingEnabled', true); + +if (crashReportingEnabled) { + Sentry.init({ + dsn: 'https://2303c5f787f910863d83ed5d27ce8ed2@o4510554134740992.ingest.us.sentry.io/4510554135789568', + // Set release version for better debugging + release: app.getVersion(), + // Only send errors, not performance data + tracesSampleRate: 0, + // Filter out sensitive data + beforeSend(event) { + // Remove any potential sensitive data from the event + if (event.user) { + delete event.user.ip_address; + delete event.user.email; + } + return event; + }, + }); +} + // Demo mode: use a separate data directory for fresh demos if (DEMO_MODE) { app.setPath('userData', DEMO_DATA_PATH); diff --git a/src/main/ipc/handlers/process.ts b/src/main/ipc/handlers/process.ts index 23398be8f..0de390131 100644 --- a/src/main/ipc/handlers/process.ts +++ b/src/main/ipc/handlers/process.ts @@ -94,7 +94,10 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void agentId: agent?.id, agentCommand: agent?.command, agentPath: agent?.path, - hasAgentSessionId: !!config.agentSessionId + hasAgentSessionId: !!config.agentSessionId, + hasPrompt: !!config.prompt, + promptLength: config.prompt?.length, + promptValue: config.prompt, }); let finalArgs = [...config.args]; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 0cfb7bc99..92ab40f17 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -179,6 +179,7 @@ export default function MaestroConsole() { audioFeedbackCommand, setAudioFeedbackCommand, toastDuration, setToastDuration, checkForUpdatesOnStartup, setCheckForUpdatesOnStartup, + crashReportingEnabled, setCrashReportingEnabled, shortcuts, setShortcuts, customAICommands, setCustomAICommands, globalStats, updateGlobalStats, @@ -1671,12 +1672,9 @@ export default function MaestroConsole() { // Create a new tab in the session to start fresh setSessions(prev => prev.map(s => { if (s.id !== sessionId) return s; - const newTab = createTab(); - return { - ...s, - aiTabs: [...s.aiTabs, newTab], - activeTabId: newTab.id, - }; + const result = createTab(s); + if (!result) return s; + return result.session; })); // Focus the input after creating new tab @@ -3434,8 +3432,8 @@ export default function MaestroConsole() { return; } - // Build spawn args with resume if we have an agent session ID - // Use the ACTIVE TAB's agentSessionId (not the deprecated session-level one) + // Get the ACTIVE TAB's agentSessionId for session continuity + // (not the deprecated session-level one) const activeTab = getActiveTab(session); const tabAgentSessionId = activeTab?.agentSessionId; const isReadOnly = activeTab?.readOnlyMode; @@ -3451,14 +3449,9 @@ export default function MaestroConsole() { ) : [...agent.args]; - if (tabAgentSessionId) { - spawnArgs.push('--resume', tabAgentSessionId); - } - - // Add read-only/plan mode if the active tab has readOnlyMode enabled - if (isReadOnly) { - spawnArgs.push('--permission-mode', 'plan'); - } + // Note: agentSessionId and readOnlyMode are passed to spawn() config below. + // The main process uses agent-specific argument builders (resumeArgs, readOnlyArgs) + // to construct the correct CLI args for each agent type. // Include tab ID in targetSessionId for proper output routing const targetSessionId = `${sessionId}-ai-${activeTab?.id || 'default'}`; @@ -3526,7 +3519,10 @@ export default function MaestroConsole() { cwd: session.cwd, command: commandToUse, args: spawnArgs, - prompt: promptToSend + prompt: promptToSend, + // Generic spawn options - main process builds agent-specific args + agentSessionId: tabAgentSessionId, + readOnlyMode: isReadOnly, }); console.log(`[Remote] ${session.toolType} spawn initiated successfully`); @@ -3618,8 +3614,8 @@ export default function MaestroConsole() { const agent = await window.maestro.agents.get(session.toolType); if (!agent) throw new Error(`Agent not found for toolType: ${session.toolType}`); - // Build spawn args with resume if we have a session ID - // Use the TARGET TAB's agentSessionId (not the active tab or deprecated session-level one) + // Get the TARGET TAB's agentSessionId for session continuity + // (not the active tab or deprecated session-level one) const tabAgentSessionId = targetTab?.agentSessionId; const isReadOnly = item.readOnlyMode || targetTab?.readOnlyMode; @@ -3634,15 +3630,9 @@ export default function MaestroConsole() { ) : [...(agent.args || [])]; - if (tabAgentSessionId) { - spawnArgs.push('--resume', tabAgentSessionId); - } - - // Add read-only/plan mode if the queued item was from a read-only tab - // or if the target tab currently has readOnlyMode enabled - if (isReadOnly) { - spawnArgs.push('--permission-mode', 'plan'); - } + // Note: agentSessionId and readOnlyMode are passed to spawn() config below. + // The main process uses agent-specific argument builders (resumeArgs, readOnlyArgs) + // to construct the correct CLI args for each agent type. const commandToUse = agent.path || agent.command; @@ -3656,6 +3646,18 @@ export default function MaestroConsole() { // If user sends only an image without text, inject the default image-only prompt const effectivePrompt = isImageOnlyMessage ? DEFAULT_IMAGE_ONLY_PROMPT : item.text!; + console.log('[processQueuedItem] Spawning agent with queued message:', { + sessionId: targetSessionId, + toolType: session.toolType, + prompt: effectivePrompt, + promptLength: effectivePrompt?.length, + hasAgentSessionId: !!tabAgentSessionId, + agentSessionId: tabAgentSessionId, + isReadOnly, + argsLength: spawnArgs.length, + args: spawnArgs, + }); + await window.maestro.process.spawn({ sessionId: targetSessionId, toolType: session.toolType, @@ -3663,7 +3665,10 @@ export default function MaestroConsole() { command: commandToUse, args: spawnArgs, prompt: effectivePrompt, - images: hasImages ? item.images : undefined + images: hasImages ? item.images : undefined, + // Generic spawn options - main process builds agent-specific args + agentSessionId: tabAgentSessionId, + readOnlyMode: isReadOnly, }); } else if (item.type === 'command' && item.command) { // Process a slash command - find the matching custom AI command @@ -3711,7 +3716,10 @@ export default function MaestroConsole() { cwd: session.cwd, command: commandToUse, args: spawnArgs, - prompt: substitutedPrompt + prompt: substitutedPrompt, + // Generic spawn options - main process builds agent-specific args + agentSessionId: tabAgentSessionId, + readOnlyMode: isReadOnly, }); } else { // Unknown command - add error log @@ -4775,6 +4783,10 @@ export default function MaestroConsole() { setTourOpen(true); }} setFuzzyFileSearchOpen={setFuzzyFileSearchOpen} + onEditAgent={(session) => { + setEditAgentSession(session); + setEditAgentModalOpen(true); + }} /> )} {lightboxImage && ( @@ -5179,6 +5191,7 @@ export default function MaestroConsole() { setSessions(prev => prev.map(s => { if (s.id !== activeSession.id) return s; const result = createTab(s, { saveToHistory: defaultSaveToHistory }); + if (!result) return s; return result.session; })); setActiveAgentSessionId(null); @@ -5377,6 +5390,7 @@ export default function MaestroConsole() { setSessions(prev => prev.map(s => { if (s.id !== activeSession.id) return s; const result = createTab(s, { saveToHistory: defaultSaveToHistory }); + if (!result) return s; return result.session; })); }} @@ -5963,6 +5977,8 @@ export default function MaestroConsole() { setToastDuration={setToastDuration} checkForUpdatesOnStartup={checkForUpdatesOnStartup} setCheckForUpdatesOnStartup={setCheckForUpdatesOnStartup} + crashReportingEnabled={crashReportingEnabled} + setCrashReportingEnabled={setCrashReportingEnabled} customAICommands={customAICommands} setCustomAICommands={setCustomAICommands} initialTab={settingsTab} diff --git a/src/renderer/components/QueuedItemsList.tsx b/src/renderer/components/QueuedItemsList.tsx index ef9f6ab41..de681f045 100644 --- a/src/renderer/components/QueuedItemsList.tsx +++ b/src/renderer/components/QueuedItemsList.tsx @@ -16,7 +16,7 @@ interface QueuedItemsListProps { /** * QueuedItemsList displays the execution queue with: * - Queued message separator with count - * - Individual queued items (commands/messages) with tab indicators + * - Individual queued items (commands/messages) * - Long message expand/collapse functionality * - Image attachment indicators * - Remove button with confirmation modal @@ -117,16 +117,6 @@ export const QueuedItemsList = memo(({ - {/* Tab indicator */} - {item.tabName && ( -
- โ†’ {item.tabName} -
- )} - {/* Item content */}
void; startTour?: () => void; setFuzzyFileSearchOpen?: (open: boolean) => void; + onEditAgent?: (session: Session) => void; } export function QuickActionsModal(props: QuickActionsModalProps) { @@ -82,7 +83,7 @@ export function QuickActionsModal(props: QuickActionsModalProps) { setShortcutsHelpOpen, setAboutModalOpen, setLogViewerOpen, setProcessMonitorOpen, setAgentSessionsOpen, setActiveAgentSessionId, setGitDiffPreview, setGitLogOpen, onRenameTab, onToggleReadOnlyMode, onOpenTabSwitcher, tabShortcuts, isAiMode, setPlaygroundOpen, onRefreshGitFileState, - onDebugReleaseQueuedItem, markdownEditMode, onToggleMarkdownEditMode, setUpdateCheckModalOpen, openWizard, wizardGoToStep, setDebugWizardModalOpen, startTour, setFuzzyFileSearchOpen + onDebugReleaseQueuedItem, markdownEditMode, onToggleMarkdownEditMode, setUpdateCheckModalOpen, openWizard, wizardGoToStep, setDebugWizardModalOpen, startTour, setFuzzyFileSearchOpen, onEditAgent } = props; const [search, setSearch] = useState(''); @@ -204,6 +205,16 @@ export function QuickActionsModal(props: QuickActionsModalProps) { setRenameInstanceModalOpen(true); setQuickActionOpen(false); } }] : []), + ...(activeSession && onEditAgent ? [{ id: 'editAgent', label: `Edit Agent: ${activeSession.name}`, action: () => { + onEditAgent(activeSession); + setQuickActionOpen(false); + } }] : []), + ...(activeSession ? [{ id: 'toggleBookmark', label: activeSession.bookmarked ? `Unbookmark: ${activeSession.name}` : `Bookmark: ${activeSession.name}`, action: () => { + setSessions(prev => prev.map(s => + s.id === activeSessionId ? { ...s, bookmarked: !s.bookmarked } : s + )); + setQuickActionOpen(false); + } }] : []), ...(activeSession?.groupId ? [{ id: 'renameGroup', label: 'Rename Group', diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index e6bf78cd0..704bd7f60 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef, memo } from 'react'; -import { X, Key, Moon, Sun, Keyboard, Check, Terminal, Bell, Cpu, Settings, Palette, Sparkles, History, Download } from 'lucide-react'; +import { X, Key, Moon, Sun, Keyboard, Check, Terminal, Bell, Cpu, Settings, Palette, Sparkles, History, Download, Bug } from 'lucide-react'; import type { AgentConfig, Theme, ThemeColors, ThemeId, Shortcut, ShellInfo, CustomAICommand } from '../types'; import { CustomThemeBuilder } from './CustomThemeBuilder'; import { useLayerStack } from '../contexts/LayerStackContext'; @@ -70,6 +70,8 @@ interface SettingsModalProps { setToastDuration: (value: number) => void; checkForUpdatesOnStartup: boolean; setCheckForUpdatesOnStartup: (value: boolean) => void; + crashReportingEnabled: boolean; + setCrashReportingEnabled: (value: boolean) => void; customAICommands: CustomAICommand[]; setCustomAICommands: (commands: CustomAICommand[]) => void; initialTab?: 'general' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands'; @@ -871,6 +873,17 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro onChange={props.setCheckForUpdatesOnStartup} theme={theme} /> + + {/* Crash Reporting */} +
)} diff --git a/src/renderer/hooks/useAgentExecution.ts b/src/renderer/hooks/useAgentExecution.ts index 63b23b86f..939da18a2 100644 --- a/src/renderer/hooks/useAgentExecution.ts +++ b/src/renderer/hooks/useAgentExecution.ts @@ -299,17 +299,16 @@ export function useAgentExecution( // Spawn the agent for batch processing // Use effectiveCwd which may be a worktree path for parallel execution const commandToUse = agent.path || agent.command; - // Only add Claude-specific permission-mode flag for Claude Code - const spawnArgs = session.toolType === 'claude-code' - ? [...(agent.args || []), '--permission-mode', 'plan'] - : [...(agent.args || [])]; + // Batch processing runs in read-only mode (plan mode) to prevent unintended writes + // The main process uses agent-specific readOnlyArgs builders for correct CLI args window.maestro.process.spawn({ sessionId: targetSessionId, toolType: session.toolType, cwd: effectiveCwd, command: commandToUse, - args: spawnArgs, - prompt + args: agent.args || [], + prompt, + readOnlyMode: true, // Batch operations run in read-only/plan mode }).catch(() => { cleanup(); resolve({ success: false }); diff --git a/src/renderer/hooks/useAgentSessionManagement.ts b/src/renderer/hooks/useAgentSessionManagement.ts index 66b485dea..9a39a514c 100644 --- a/src/renderer/hooks/useAgentSessionManagement.ts +++ b/src/renderer/hooks/useAgentSessionManagement.ts @@ -261,15 +261,16 @@ export function useAgentSessionManagement( if (s.id !== activeSession.id) return s; // Create tab from the CURRENT session state (not stale closure value) - const { session: updatedSession } = createTab(s, { + const result = createTab(s, { agentSessionId, logs: messages, name, starred: isStarred, saveToHistory: defaultSaveToHistory }); + if (!result) return s; - return { ...updatedSession, inputMode: 'ai' }; + return { ...result.session, inputMode: 'ai' }; })); setActiveAgentSessionId(agentSessionId); } catch (error) { diff --git a/src/renderer/hooks/useMainKeyboardHandler.ts b/src/renderer/hooks/useMainKeyboardHandler.ts index 7db12f511..e041ee282 100644 --- a/src/renderer/hooks/useMainKeyboardHandler.ts +++ b/src/renderer/hooks/useMainKeyboardHandler.ts @@ -308,12 +308,14 @@ export function useMainKeyboardHandler(): UseMainKeyboardHandlerReturn { if (ctx.isTabShortcut(e, 'newTab')) { e.preventDefault(); const result = ctx.createTab(ctx.activeSession, { saveToHistory: ctx.defaultSaveToHistory }); - ctx.setSessions((prev: Session[]) => prev.map((s: Session) => - s.id === ctx.activeSession!.id ? result.session : s - )); - // Auto-focus the input so user can start typing immediately - ctx.setActiveFocus('main'); - setTimeout(() => ctx.inputRef.current?.focus(), 50); + if (result) { + ctx.setSessions((prev: Session[]) => prev.map((s: Session) => + s.id === ctx.activeSession!.id ? result.session : s + )); + // Auto-focus the input so user can start typing immediately + ctx.setActiveFocus('main'); + setTimeout(() => ctx.inputRef.current?.focus(), 50); + } } if (ctx.isTabShortcut(e, 'closeTab')) { e.preventDefault(); diff --git a/src/renderer/hooks/useRemoteIntegration.ts b/src/renderer/hooks/useRemoteIntegration.ts index 124dde76a..e984c4ec3 100644 --- a/src/renderer/hooks/useRemoteIntegration.ts +++ b/src/renderer/hooks/useRemoteIntegration.ts @@ -265,6 +265,7 @@ export function useRemoteIntegration(deps: UseRemoteIntegrationDeps): UseRemoteI // Use createTab helper const result = createTab(s, { saveToHistory: defaultSaveToHistory }); + if (!result) return s; newTabId = result.tab.id; return result.session; })); diff --git a/src/renderer/hooks/useSettings.ts b/src/renderer/hooks/useSettings.ts index 0948d3b82..a5dc6fd3d 100644 --- a/src/renderer/hooks/useSettings.ts +++ b/src/renderer/hooks/useSettings.ts @@ -152,6 +152,10 @@ export interface UseSettingsReturn { checkForUpdatesOnStartup: boolean; setCheckForUpdatesOnStartup: (value: boolean) => void; + // Crash reporting settings + crashReportingEnabled: boolean; + setCrashReportingEnabled: (value: boolean) => void; + // Log Viewer settings logViewerSelectedLevels: string[]; setLogViewerSelectedLevels: (value: string[]) => void; @@ -272,6 +276,9 @@ export function useSettings(): UseSettingsReturn { // Update Config const [checkForUpdatesOnStartup, setCheckForUpdatesOnStartupState] = useState(true); // Default: on + // Crash Reporting Config + const [crashReportingEnabled, setCrashReportingEnabledState] = useState(true); // Default: on (opt-out) + // Log Viewer Config const [logViewerSelectedLevels, setLogViewerSelectedLevelsState] = useState(['debug', 'info', 'warn', 'error', 'toast']); @@ -453,6 +460,11 @@ export function useSettings(): UseSettingsReturn { window.maestro.settings.set('checkForUpdatesOnStartup', value); }, []); + const setCrashReportingEnabled = useCallback((value: boolean) => { + setCrashReportingEnabledState(value); + window.maestro.settings.set('crashReportingEnabled', value); + }, []); + const setLogViewerSelectedLevels = useCallback((value: string[]) => { setLogViewerSelectedLevelsState(value); window.maestro.settings.set('logViewerSelectedLevels', value); @@ -887,6 +899,7 @@ export function useSettings(): UseSettingsReturn { const savedAudioFeedbackCommand = await window.maestro.settings.get('audioFeedbackCommand'); const savedToastDuration = await window.maestro.settings.get('toastDuration'); const savedCheckForUpdatesOnStartup = await window.maestro.settings.get('checkForUpdatesOnStartup'); + const savedCrashReportingEnabled = await window.maestro.settings.get('crashReportingEnabled'); const savedLogViewerSelectedLevels = await window.maestro.settings.get('logViewerSelectedLevels'); const savedCustomAICommands = await window.maestro.settings.get('customAICommands'); const savedGlobalStats = await window.maestro.settings.get('globalStats'); @@ -929,6 +942,7 @@ export function useSettings(): UseSettingsReturn { if (savedAudioFeedbackCommand !== undefined) setAudioFeedbackCommandState(savedAudioFeedbackCommand); if (savedToastDuration !== undefined) setToastDurationState(savedToastDuration); if (savedCheckForUpdatesOnStartup !== undefined) setCheckForUpdatesOnStartupState(savedCheckForUpdatesOnStartup); + if (savedCrashReportingEnabled !== undefined) setCrashReportingEnabledState(savedCrashReportingEnabled); if (savedLogViewerSelectedLevels !== undefined) setLogViewerSelectedLevelsState(savedLogViewerSelectedLevels); // Merge saved shortcuts with defaults (in case new shortcuts were added) @@ -1113,6 +1127,8 @@ export function useSettings(): UseSettingsReturn { setToastDuration, checkForUpdatesOnStartup, setCheckForUpdatesOnStartup, + crashReportingEnabled, + setCrashReportingEnabled, logViewerSelectedLevels, setLogViewerSelectedLevels, shortcuts, @@ -1184,6 +1200,7 @@ export function useSettings(): UseSettingsReturn { audioFeedbackCommand, toastDuration, checkForUpdatesOnStartup, + crashReportingEnabled, logViewerSelectedLevels, shortcuts, customAICommands, @@ -1223,6 +1240,7 @@ export function useSettings(): UseSettingsReturn { setAudioFeedbackCommand, setToastDuration, setCheckForUpdatesOnStartup, + setCrashReportingEnabled, setLogViewerSelectedLevels, setShortcuts, setCustomAICommands, diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx index 1e101bcb5..0e5b08d7f 100644 --- a/src/renderer/main.tsx +++ b/src/renderer/main.tsx @@ -1,5 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import * as Sentry from '@sentry/electron/renderer'; import MaestroConsole from './App'; import { ErrorBoundary } from './components/ErrorBoundary'; import { LayerStackProvider } from './contexts/LayerStackContext'; @@ -8,6 +9,12 @@ import { WizardProvider } from './components/Wizard'; import { logger } from './utils/logger'; import './index.css'; +// Initialize Sentry for the renderer process +// The main process handles the enabled/disabled check and initializes Sentry there +// Renderer Sentry will automatically connect to main process Sentry +// We initialize unconditionally here - if main process didn't init, this is a no-op +Sentry.init({}); + // Set up global error handlers for uncaught exceptions in renderer process window.addEventListener('error', (event: ErrorEvent) => { logger.error( diff --git a/src/renderer/utils/tabHelpers.ts b/src/renderer/utils/tabHelpers.ts index d4d4390dd..f7c312537 100644 --- a/src/renderer/utils/tabHelpers.ts +++ b/src/renderer/utils/tabHelpers.ts @@ -34,7 +34,7 @@ function hasDraft(tab: AITab): boolean { * const unreadTabs = getNavigableTabs(session, true); */ export function getNavigableTabs(session: Session, showUnreadOnly = false): AITab[] { - if (!session.aiTabs || session.aiTabs.length === 0) { + if (!session || !session.aiTabs || session.aiTabs.length === 0) { return []; } @@ -54,7 +54,7 @@ export function getNavigableTabs(session: Session, showUnreadOnly = false): AITa * @returns The active AITab or undefined if no tabs exist */ export function getActiveTab(session: Session): AITab | undefined { - if (!session.aiTabs || session.aiTabs.length === 0) { + if (!session || !session.aiTabs || session.aiTabs.length === 0) { return undefined; } @@ -106,7 +106,11 @@ export interface CreateTabResult { * logs: existingLogs * }); */ -export function createTab(session: Session, options: CreateTabOptions = {}): CreateTabResult { +export function createTab(session: Session, options: CreateTabOptions = {}): CreateTabResult | null { + if (!session) { + return null; + } + const { agentSessionId = null, logs = [], @@ -170,7 +174,7 @@ export interface CloseTabResult { * } */ export function closeTab(session: Session, tabId: string): CloseTabResult | null { - if (!session.aiTabs || session.aiTabs.length === 0) { + if (!session || !session.aiTabs || session.aiTabs.length === 0) { return null; } @@ -345,8 +349,8 @@ export interface SetActiveTabResult { * } */ export function setActiveTab(session: Session, tabId: string): SetActiveTabResult | null { - // Validate that the tab exists - if (!session.aiTabs || session.aiTabs.length === 0) { + // Validate that the session and tab exists + if (!session || !session.aiTabs || session.aiTabs.length === 0) { return null; } @@ -388,7 +392,7 @@ export function setActiveTab(session: Session, tabId: string): SetActiveTabResul * } */ export function getWriteModeTab(session: Session): AITab | undefined { - if (!session.aiTabs || session.aiTabs.length === 0) { + if (!session || !session.aiTabs || session.aiTabs.length === 0) { return undefined; } @@ -416,7 +420,7 @@ export function getWriteModeTab(session: Session): AITab | undefined { * } */ export function getBusyTabs(session: Session): AITab[] { - if (!session.aiTabs || session.aiTabs.length === 0) { + if (!session || !session.aiTabs || session.aiTabs.length === 0) { return []; } @@ -439,7 +443,7 @@ export function getBusyTabs(session: Session): AITab[] { * } */ export function navigateToNextTab(session: Session, showUnreadOnly = false): SetActiveTabResult | null { - if (!session.aiTabs || session.aiTabs.length < 2) { + if (!session || !session.aiTabs || session.aiTabs.length < 2) { return null; } @@ -498,7 +502,7 @@ export function navigateToNextTab(session: Session, showUnreadOnly = false): Set * } */ export function navigateToPrevTab(session: Session, showUnreadOnly = false): SetActiveTabResult | null { - if (!session.aiTabs || session.aiTabs.length < 2) { + if (!session || !session.aiTabs || session.aiTabs.length < 2) { return null; } @@ -559,7 +563,7 @@ export function navigateToPrevTab(session: Session, showUnreadOnly = false): Set * } */ export function navigateToTabByIndex(session: Session, index: number, showUnreadOnly = false): SetActiveTabResult | null { - if (!session.aiTabs || session.aiTabs.length === 0) { + if (!session || !session.aiTabs || session.aiTabs.length === 0) { return null; } From f532d8036d100e35f4757b747f2ac43b916a963d Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Wed, 17 Dec 2025 23:23:27 -0600 Subject: [PATCH 2/5] # CHANGES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed test suite by removing obsolete tab indicator test case ๐Ÿงน - Cleaned up TerminalOutput component test coverage for better reliability โœ… - Streamlined execution queue testing by eliminating redundant tab checks ๐ŸŽฏ - Enhanced test maintainability by removing deprecated tabName functionality ๐Ÿ”ง - Improved test suite performance with focused queue item assertions โšก - Simplified component testing structure for clearer test intentions ๐Ÿ“‹ - Removed legacy tab-related test logic from execution queue tests ๐Ÿ—‘๏ธ - Optimized test file by cutting unnecessary tab indicator verification ๐Ÿš€ - Strengthened test suite by focusing on core queue functionality ๐Ÿ’ช - Modernized test implementation by dropping outdated tab display checks ๐Ÿ†• --- .../renderer/components/TerminalOutput.test.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/__tests__/renderer/components/TerminalOutput.test.tsx b/src/__tests__/renderer/components/TerminalOutput.test.tsx index a67aff3c0..7e7735b34 100644 --- a/src/__tests__/renderer/components/TerminalOutput.test.tsx +++ b/src/__tests__/renderer/components/TerminalOutput.test.tsx @@ -607,19 +607,6 @@ describe('TerminalOutput', () => { expect(screen.getByText('/history')).toBeInTheDocument(); }); - it('shows tab indicator for queued items with tabName', () => { - const session = createDefaultSession({ - executionQueue: [ - { id: 'q1', type: 'message', text: 'Message for tab', tabId: 'tab-1', tabName: 'Tab 1' }, - ], - }); - - const props = createDefaultProps({ session }); - render(); - - expect(screen.getByText('โ†’ Tab 1')).toBeInTheDocument(); - }); - it('shows remove button for queued items', () => { const session = createDefaultSession({ executionQueue: [ From 16fbe638bb21b296db1f7b61b5b0747d19b0dc87 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Wed, 17 Dec 2025 23:42:15 -0600 Subject: [PATCH 3/5] ## CHANGES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed defaultAgent prop from NewInstanceModal component ๐Ÿงน - Eliminated AgentSelectionPanel from settings interface completely ๐Ÿ—‘๏ธ - Cleaned up agent configuration state management across tests ๐Ÿงช - Simplified agent selection to auto-select first available option ๐ŸŽฏ - Removed defaultAgent from useSettings hook and persistence layer ๐Ÿ’พ - Streamlined NewInstanceModal to focus on essential functionality โšก - Deleted unused agent configuration options from settings modal ๐Ÿ”ฅ - Updated all test files to reflect simplified component API ๐Ÿ”„ - Removed agent-specific configuration panels and related logic ๐Ÿšซ - Made agent selection more straightforward without default preferences โœจ --- .../components/NewInstanceModal.test.tsx | 61 --- .../components/SettingsModal.test.tsx | 77 ---- .../renderer/hooks/useSettings.test.ts | 17 +- src/main/index.ts | 2 - src/main/ipc/handlers/persistence.ts | 1 - src/renderer/App.tsx | 10 +- .../components/AgentSelectionPanel.tsx | 350 ------------------ src/renderer/components/NewInstanceModal.tsx | 15 +- src/renderer/components/SettingsModal.tsx | 47 +-- src/renderer/hooks/useSettings.ts | 18 - 10 files changed, 11 insertions(+), 587 deletions(-) delete mode 100644 src/renderer/components/AgentSelectionPanel.tsx diff --git a/src/__tests__/renderer/components/NewInstanceModal.test.tsx b/src/__tests__/renderer/components/NewInstanceModal.test.tsx index 7c1d27fe0..195aa7acf 100644 --- a/src/__tests__/renderer/components/NewInstanceModal.test.tsx +++ b/src/__tests__/renderer/components/NewInstanceModal.test.tsx @@ -120,7 +120,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -138,7 +137,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -156,7 +154,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -172,7 +169,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -193,7 +189,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -215,7 +210,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -242,7 +236,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -264,7 +257,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -286,7 +278,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -309,7 +300,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -332,7 +322,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="unavailable-agent" existingSessions={[]} /> ); @@ -357,7 +346,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="" existingSessions={[]} /> ); @@ -382,7 +370,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="" existingSessions={[]} /> ); @@ -409,7 +396,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -442,7 +428,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -484,7 +469,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -530,7 +514,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -568,7 +551,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -593,7 +575,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -618,7 +599,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -643,7 +623,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -676,7 +655,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -710,7 +688,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -745,7 +722,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -780,7 +756,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -816,7 +791,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -851,7 +825,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -879,7 +852,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -903,7 +875,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -930,7 +901,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -957,7 +927,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -981,7 +950,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1005,7 +973,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1032,7 +999,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1062,7 +1028,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1091,7 +1056,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1126,7 +1090,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1154,7 +1117,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1188,7 +1150,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1215,7 +1176,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1228,7 +1188,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1247,7 +1206,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1259,7 +1217,6 @@ describe('NewInstanceModal', () => { onClose={newOnClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1280,7 +1237,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1311,7 +1267,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1339,7 +1294,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1378,7 +1332,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1411,7 +1364,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1452,7 +1404,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1477,7 +1428,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1511,7 +1461,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1533,7 +1482,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1555,7 +1503,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1579,7 +1526,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1600,7 +1546,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1622,7 +1567,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1642,7 +1586,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1666,7 +1609,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1692,7 +1634,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1716,7 +1657,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); @@ -1754,7 +1694,6 @@ describe('NewInstanceModal', () => { onClose={onClose} onCreate={onCreate} theme={theme} - defaultAgent="claude-code" existingSessions={[]} /> ); diff --git a/src/__tests__/renderer/components/SettingsModal.test.tsx b/src/__tests__/renderer/components/SettingsModal.test.tsx index 548e9a3a1..015a3407c 100644 --- a/src/__tests__/renderer/components/SettingsModal.test.tsx +++ b/src/__tests__/renderer/components/SettingsModal.test.tsx @@ -147,8 +147,6 @@ const createDefaultProps = (overrides = {}) => ({ setApiKey: vi.fn(), shortcuts: mockShortcuts, setShortcuts: vi.fn(), - defaultAgent: 'claude-code', - setDefaultAgent: vi.fn(), fontFamily: 'Menlo', setFontFamily: vi.fn(), fontSize: 14, @@ -1710,81 +1708,6 @@ describe('SettingsModal', () => { }); }); - describe('Agent configuration options', () => { - it('should render checkbox config options for selected agent', async () => { - vi.mocked(window.maestro.agents.detect).mockResolvedValue([ - { - id: 'claude-code', - name: 'Claude Code', - available: true, - path: '/usr/local/bin/claude', - hidden: false, - configOptions: [ - { - key: 'dangerMode', - label: 'Danger Mode', - description: 'Enable dangerous operations', - type: 'checkbox' as const, - default: false, - }, - ], - }, - ] as AgentConfig[]); - - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect(screen.getByText('Claude Code Configuration')).toBeInTheDocument(); - expect(screen.getByText('Danger Mode')).toBeInTheDocument(); - expect(screen.getByText('Enable dangerous operations')).toBeInTheDocument(); - }); - - it('should update agent config when checkbox is changed', async () => { - vi.mocked(window.maestro.agents.detect).mockResolvedValue([ - { - id: 'claude-code', - name: 'Claude Code', - available: true, - path: '/usr/local/bin/claude', - hidden: false, - configOptions: [ - { - key: 'dangerMode', - label: 'Danger Mode', - description: 'Enable dangerous operations', - type: 'checkbox' as const, - default: false, - }, - ], - }, - ] as AgentConfig[]); - - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - // Find the checkbox in the config section - const checkbox = screen.getByText('Danger Mode').closest('label')?.querySelector('input[type="checkbox"]'); - expect(checkbox).toBeDefined(); - - fireEvent.click(checkbox!); - - await act(async () => { - await vi.advanceTimersByTimeAsync(50); - }); - - expect((window.maestro as any).agents.setConfig).toHaveBeenCalledWith( - 'claude-code', - expect.objectContaining({ dangerMode: true }) - ); - }); - }); - describe('Custom font removal', () => { it('should remove custom font when X is clicked', async () => { // Preload custom fonts diff --git a/src/__tests__/renderer/hooks/useSettings.test.ts b/src/__tests__/renderer/hooks/useSettings.test.ts index e0a963c4e..7fa9aef6f 100644 --- a/src/__tests__/renderer/hooks/useSettings.test.ts +++ b/src/__tests__/renderer/hooks/useSettings.test.ts @@ -49,11 +49,10 @@ describe('useSettings', () => { expect(result.current.apiKey).toBe(''); }); - it('should have correct default values for agent settings', async () => { + it('should have correct default values for shell settings', async () => { const { result } = renderHook(() => useSettings()); await waitForSettingsLoaded(result); - expect(result.current.defaultAgent).toBe('claude-code'); expect(result.current.defaultShell).toBe('zsh'); expect(result.current.ghPath).toBe(''); }); @@ -369,19 +368,7 @@ describe('useSettings', () => { }); }); - describe('setter functions - agent settings', () => { - it('should update defaultAgent and persist to settings', async () => { - const { result } = renderHook(() => useSettings()); - await waitForSettingsLoaded(result); - - act(() => { - result.current.setDefaultAgent('aider'); - }); - - expect(result.current.defaultAgent).toBe('aider'); - expect(window.maestro.settings.set).toHaveBeenCalledWith('defaultAgent', 'aider'); - }); - + describe('setter functions - shell settings', () => { it('should update defaultShell and persist to settings', async () => { const { result } = renderHook(() => useSettings()); await waitForSettingsLoaded(result); diff --git a/src/main/index.ts b/src/main/index.ts index db9612eb9..4c78b702d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -57,7 +57,6 @@ interface MaestroSettings { modelSlug: string; apiKey: string; shortcuts: Record; - defaultAgent: string; fontSize: number; fontFamily: string; customFonts: string[]; @@ -79,7 +78,6 @@ const store = new Store({ modelSlug: 'anthropic/claude-3.5-sonnet', apiKey: '', shortcuts: {}, - defaultAgent: 'claude-code', fontSize: 14, fontFamily: 'Roboto Mono, Menlo, "Courier New", monospace', customFonts: [], diff --git a/src/main/ipc/handlers/persistence.ts b/src/main/ipc/handlers/persistence.ts index f37b952bc..c35664a97 100644 --- a/src/main/ipc/handlers/persistence.ts +++ b/src/main/ipc/handlers/persistence.ts @@ -27,7 +27,6 @@ export interface MaestroSettings { modelSlug: string; apiKey: string; shortcuts: Record; - defaultAgent: string; fontSize: number; fontFamily: string; customFonts: string[]; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 92ab40f17..8188e4bc5 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -154,7 +154,6 @@ export default function MaestroConsole() { llmProvider, setLlmProvider, modelSlug, setModelSlug, apiKey, setApiKey, - defaultAgent, setDefaultAgent, defaultShell, setDefaultShell, ghPath, setGhPath, fontFamily, setFontFamily, @@ -464,10 +463,10 @@ export default function MaestroConsole() { let correctedSession = { ...session }; let aiAgentType = correctedSession.toolType; - // If toolType is 'terminal', use the default agent instead for AI process + // If toolType is 'terminal', use claude-code instead for AI process if (aiAgentType === 'terminal') { - console.warn(`[restoreSession] Session has toolType='terminal', using default agent for AI process`); - aiAgentType = defaultAgent as ToolType; + console.warn(`[restoreSession] Session has toolType='terminal', using claude-code for AI process`); + aiAgentType = 'claude-code' as ToolType; // Add warning to the active tab's logs const warningLog: LogEntry = { @@ -5901,7 +5900,6 @@ export default function MaestroConsole() { onClose={() => setNewInstanceModalOpen(false)} onCreate={createNewSession} theme={theme} - defaultAgent={defaultAgent} existingSessions={sessionsForValidation} /> @@ -5943,8 +5941,6 @@ export default function MaestroConsole() { setApiKey={setApiKey} shortcuts={shortcuts} setShortcuts={setShortcuts} - defaultAgent={defaultAgent} - setDefaultAgent={setDefaultAgent} defaultShell={defaultShell} setDefaultShell={setDefaultShell} ghPath={ghPath} diff --git a/src/renderer/components/AgentSelectionPanel.tsx b/src/renderer/components/AgentSelectionPanel.tsx deleted file mode 100644 index 5c4d5fa10..000000000 --- a/src/renderer/components/AgentSelectionPanel.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { RefreshCw } from 'lucide-react'; -import type { AgentConfig, Theme } from '../types'; - -export interface AgentSelectionPanelProps { - /** List of available agents */ - agents: AgentConfig[]; - /** Whether agents are still loading */ - loading: boolean; - /** Currently selected default agent ID */ - defaultAgent: string; - /** Callback to set the default agent */ - setDefaultAgent: (agentId: string) => void; - /** Agent configurations keyed by agent ID */ - agentConfigs: Record>; - /** Callback to update agent configurations */ - setAgentConfigs: React.Dispatch>>>; - /** Custom agent paths keyed by agent ID */ - customAgentPaths: Record; - /** Callback to update custom agent paths */ - setCustomAgentPaths: React.Dispatch>>; - /** Callback to reload agents after path changes */ - loadAgents: () => Promise; - /** The current theme */ - theme: Theme; -} - -/** - * A panel for selecting and configuring AI agents in settings. - * Displays: - * - List of available agents with availability badges - * - Custom path input for Claude Code - * - Agent-specific configuration options (checkboxes) - */ -export function AgentSelectionPanel({ - agents, - loading, - defaultAgent, - setDefaultAgent, - agentConfigs, - setAgentConfigs, - customAgentPaths, - setCustomAgentPaths, - loadAgents, - theme, -}: AgentSelectionPanelProps): React.ReactElement { - // Track available models for agents that support model selection - const [availableModels, setAvailableModels] = useState>({}); - const [loadingModels, setLoadingModels] = useState>({}); - - // Fetch models for an agent - const fetchModels = useCallback(async (agentId: string, forceRefresh = false) => { - setLoadingModels(prev => ({ ...prev, [agentId]: true })); - try { - const models = await window.maestro.agents.getModels(agentId, forceRefresh); - setAvailableModels(prev => ({ ...prev, [agentId]: models })); - } catch (error) { - console.error(`Failed to fetch models for ${agentId}:`, error); - setAvailableModels(prev => ({ ...prev, [agentId]: [] })); - } finally { - setLoadingModels(prev => ({ ...prev, [agentId]: false })); - } - }, []); - - // Fetch models when an agent with model selection is selected - useEffect(() => { - const selectedAgent = agents.find(a => a.id === defaultAgent); - // Check if the agent's configOptions include a 'model' option - // This indicates the agent supports model selection - const hasModelOption = selectedAgent?.configOptions?.some((opt) => opt.key === 'model'); - - if (selectedAgent && selectedAgent.available && hasModelOption && !availableModels[defaultAgent]) { - fetchModels(defaultAgent); - } - }, [defaultAgent, agents, fetchModels, availableModels]); - - return ( - <> - {/* Default AI Agent Selection */} -
- - {loading ? ( -
Loading agents...
- ) : ( -
- {agents.filter((agent) => !agent.hidden).map((agent) => ( -
- - {/* Custom path input for supported agents */} - {(agent.id === 'claude-code' || agent.id === 'opencode' || agent.id === 'codex') && ( -
- -
- { - const newPaths = { ...customAgentPaths, [agent.id]: e.target.value }; - setCustomAgentPaths(newPaths); - }} - onBlur={async () => { - const path = customAgentPaths[agent.id]?.trim() || null; - await window.maestro.agents.setCustomPath(agent.id, path); - // Refresh agents to pick up the new path - loadAgents(); - }} - placeholder="/path/to/claude" - className="flex-1 p-1.5 rounded border bg-transparent outline-none text-xs font-mono" - style={{ borderColor: theme.colors.border, color: theme.colors.textMain }} - /> - {customAgentPaths[agent.id] && ( - - )} -
-

- Specify a custom path if the agent is not in your PATH -

-
- )} -
- ))} -
- )} -
- - {/* Agent-Specific Configuration */} - {!loading && agents.length > 0 && (() => { - const selectedAgent = agents.find(a => a.id === defaultAgent); - if (!selectedAgent || !selectedAgent.configOptions || selectedAgent.configOptions.length === 0) { - return null; - } - - return ( -
- -
- {selectedAgent.configOptions.map((option: any) => ( -
- {option.type === 'checkbox' && ( - - )} - {option.type === 'text' && ( -
-
-
- {option.label} -
-
- {option.description} -
-
- {/* Model selection with dropdown for discovered models */} - {option.key === 'model' && (availableModels[selectedAgent.id]?.length > 0 || loadingModels[selectedAgent.id]) ? ( -
- - -
- ) : ( - { - const newConfig = { - ...agentConfigs[selectedAgent.id], - [option.key]: e.target.value - }; - setAgentConfigs(prev => ({ - ...prev, - [selectedAgent.id]: newConfig - })); - }} - onBlur={() => { - // Only persist on blur to avoid excessive writes - const currentConfig = agentConfigs[selectedAgent.id] || {}; - window.maestro.agents.setConfig(selectedAgent.id, currentConfig); - }} - placeholder={option.default || ''} - className="w-full p-2 rounded border bg-transparent outline-none text-sm font-mono" - style={{ borderColor: theme.colors.border, color: theme.colors.textMain }} - /> - )} - {/* Show hint if model discovery is available but not yet loaded */} - {option.key === 'model' && !loadingModels[selectedAgent.id] && !availableModels[selectedAgent.id] && ( - - )} -
- )} - {option.type === 'number' && ( -
-
-
- {option.label} -
-
- {option.description} -
-
- { - const value = e.target.value === '' ? 0 : parseInt(e.target.value, 10); - const newConfig = { - ...agentConfigs[selectedAgent.id], - [option.key]: isNaN(value) ? 0 : value - }; - setAgentConfigs(prev => ({ - ...prev, - [selectedAgent.id]: newConfig - })); - }} - onBlur={() => { - // Only persist on blur to avoid excessive writes - const currentConfig = agentConfigs[selectedAgent.id] || {}; - window.maestro.agents.setConfig(selectedAgent.id, currentConfig); - }} - placeholder={option.default?.toString() || '0'} - min={0} - className="w-full p-2 rounded border bg-transparent outline-none text-sm font-mono" - style={{ borderColor: theme.colors.border, color: theme.colors.textMain }} - /> -
- )} -
- ))} -
-
- ); - })()} - - ); -} diff --git a/src/renderer/components/NewInstanceModal.tsx b/src/renderer/components/NewInstanceModal.tsx index b7919ff79..9311bad37 100644 --- a/src/renderer/components/NewInstanceModal.tsx +++ b/src/renderer/components/NewInstanceModal.tsx @@ -26,7 +26,6 @@ interface NewInstanceModalProps { onClose: () => void; onCreate: (agentId: string, workingDir: string, name: string, nudgeMessage?: string) => void; theme: any; - defaultAgent: string; existingSessions: Session[]; } @@ -42,9 +41,9 @@ interface EditAgentModalProps { // Supported agents that are fully implemented const SUPPORTED_AGENTS = ['claude-code', 'opencode', 'codex']; -export function NewInstanceModal({ isOpen, onClose, onCreate, theme, defaultAgent, existingSessions }: NewInstanceModalProps) { +export function NewInstanceModal({ isOpen, onClose, onCreate, theme, existingSessions }: NewInstanceModalProps) { const [agents, setAgents] = useState([]); - const [selectedAgent, setSelectedAgent] = useState(defaultAgent); + const [selectedAgent, setSelectedAgent] = useState(''); const [expandedAgent, setExpandedAgent] = useState(null); const [workingDir, setWorkingDir] = useState(''); const [instanceName, setInstanceName] = useState(''); @@ -103,13 +102,9 @@ export function NewInstanceModal({ isOpen, onClose, onCreate, theme, defaultAgen } setAgentConfigs(configs); - // Set default or first available - const defaultAvailable = detectedAgents.find((a: AgentConfig) => a.id === defaultAgent && a.available); + // Select first available agent const firstAvailable = detectedAgents.find((a: AgentConfig) => a.available); - - if (defaultAvailable) { - setSelectedAgent(defaultAgent); - } else if (firstAvailable) { + if (firstAvailable) { setSelectedAgent(firstAvailable.id); } } catch (error) { @@ -205,7 +200,7 @@ export function NewInstanceModal({ isOpen, onClose, onCreate, theme, defaultAgen // Keep all agents collapsed by default setExpandedAgent(null); } - }, [isOpen, defaultAgent]); + }, [isOpen]); if (!isOpen) return null; diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index 704bd7f60..75f78096a 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef, memo } from 'react'; import { X, Key, Moon, Sun, Keyboard, Check, Terminal, Bell, Cpu, Settings, Palette, Sparkles, History, Download, Bug } from 'lucide-react'; -import type { AgentConfig, Theme, ThemeColors, ThemeId, Shortcut, ShellInfo, CustomAICommand } from '../types'; +import type { Theme, ThemeColors, ThemeId, Shortcut, ShellInfo, CustomAICommand } from '../types'; import { CustomThemeBuilder } from './CustomThemeBuilder'; import { useLayerStack } from '../contexts/LayerStackContext'; import { MODAL_PRIORITIES } from '../constants/modalPriorities'; @@ -8,7 +8,6 @@ import { AICommandsPanel } from './AICommandsPanel'; import { formatShortcutKeys } from '../utils/shortcutFormatter'; import { ToggleButtonGroup, ToggleButtonOption } from './ToggleButtonGroup'; import { SettingCheckbox } from './SettingCheckbox'; -import { AgentSelectionPanel } from './AgentSelectionPanel'; import { FontConfigurationPanel } from './FontConfigurationPanel'; import { NotificationsPanel } from './NotificationsPanel'; @@ -36,8 +35,6 @@ interface SettingsModalProps { setApiKey: (key: string) => void; shortcuts: Record; setShortcuts: (shortcuts: Record) => void; - defaultAgent: string; - setDefaultAgent: (agentId: string) => void; fontFamily: string; setFontFamily: (font: string) => void; fontSize: number; @@ -87,9 +84,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro const [customFonts, setCustomFonts] = useState([]); const [fontLoading, setFontLoading] = useState(false); const [fontsLoaded, setFontsLoaded] = useState(false); - const [agents, setAgents] = useState([]); - const [loading, setLoading] = useState(true); - const [agentConfigs, setAgentConfigs] = useState>>({}); const [recordingId, setRecordingId] = useState(null); const [shortcutsFilter, setShortcutsFilter] = useState(''); const [testingLLM, setTestingLLM] = useState(false); @@ -97,7 +91,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro const [shells, setShells] = useState([]); const [shellsLoading, setShellsLoading] = useState(false); const [shellsLoaded, setShellsLoaded] = useState(false); - const [customAgentPaths, setCustomAgentPaths] = useState>({}); // Layer stack integration const { registerLayer, unregisterLayer, updateLayerHandler } = useLayerStack(); @@ -107,7 +100,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro useEffect(() => { if (isOpen) { - loadAgents(); // Don't load fonts immediately - only when user interacts with font selector // Set initial tab if provided, otherwise default to 'general' setActiveTab(initialTab || 'general'); @@ -203,30 +195,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro } }, [isOpen, activeTab]); - const loadAgents = async () => { - setLoading(true); - try { - const detectedAgents = await window.maestro.agents.detect(); - setAgents(detectedAgents); - - // Load configurations for all agents - const configs: Record> = {}; - for (const agent of detectedAgents) { - const config = await window.maestro.agents.getConfig(agent.id); - configs[agent.id] = config; - } - setAgentConfigs(configs); - - // Load custom paths for agents - const paths = await window.maestro.agents.getAllCustomPaths(); - setCustomAgentPaths(paths); - } catch (error) { - console.error('Failed to load agents:', error); - } finally { - setLoading(false); - } - }; - const loadFonts = async () => { if (fontsLoaded) return; // Don't reload if already loaded @@ -580,19 +548,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
{activeTab === 'general' && (
- - {/* Font Family */} void; setApiKey: (value: string) => void; - // Agent settings - defaultAgent: string; - setDefaultAgent: (value: string) => void; - // Shell settings defaultShell: string; setDefaultShell: (value: string) => void; @@ -231,9 +227,6 @@ export function useSettings(): UseSettingsReturn { const [modelSlug, setModelSlugState] = useState('anthropic/claude-3.5-sonnet'); const [apiKey, setApiKeyState] = useState(''); - // Agent Config - const [defaultAgent, setDefaultAgentState] = useState('claude-code'); - // Shell Config const [defaultShell, setDefaultShellState] = useState('zsh'); @@ -329,11 +322,6 @@ export function useSettings(): UseSettingsReturn { window.maestro.settings.set('apiKey', value); }, []); - const setDefaultAgent = useCallback((value: string) => { - setDefaultAgentState(value); - window.maestro.settings.set('defaultAgent', value); - }, []); - const setDefaultShell = useCallback((value: string) => { setDefaultShellState(value); window.maestro.settings.set('defaultShell', value); @@ -876,7 +864,6 @@ export function useSettings(): UseSettingsReturn { const savedLlmProvider = await window.maestro.settings.get('llmProvider'); const savedModelSlug = await window.maestro.settings.get('modelSlug'); const savedApiKey = await window.maestro.settings.get('apiKey'); - const savedDefaultAgent = await window.maestro.settings.get('defaultAgent'); const savedDefaultShell = await window.maestro.settings.get('defaultShell'); const savedGhPath = await window.maestro.settings.get('ghPath'); const savedFontSize = await window.maestro.settings.get('fontSize'); @@ -920,7 +907,6 @@ export function useSettings(): UseSettingsReturn { if (savedLlmProvider !== undefined) setLlmProviderState(savedLlmProvider); if (savedModelSlug !== undefined) setModelSlugState(savedModelSlug); if (savedApiKey !== undefined) setApiKeyState(savedApiKey); - if (savedDefaultAgent !== undefined) setDefaultAgentState(savedDefaultAgent); if (savedDefaultShell !== undefined) setDefaultShellState(savedDefaultShell); if (savedGhPath !== undefined) setGhPathState(savedGhPath); if (savedFontSize !== undefined) setFontSizeState(savedFontSize); @@ -1077,8 +1063,6 @@ export function useSettings(): UseSettingsReturn { setLlmProvider, setModelSlug, setApiKey, - defaultAgent, - setDefaultAgent, defaultShell, setDefaultShell, ghPath, @@ -1175,7 +1159,6 @@ export function useSettings(): UseSettingsReturn { llmProvider, modelSlug, apiKey, - defaultAgent, defaultShell, ghPath, fontFamily, @@ -1215,7 +1198,6 @@ export function useSettings(): UseSettingsReturn { setLlmProvider, setModelSlug, setApiKey, - setDefaultAgent, setDefaultShell, setGhPath, setFontFamily, From 08fd1c2732e288604241adc446944e5ecda2cd06 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Wed, 17 Dec 2025 23:48:14 -0600 Subject: [PATCH 4/5] # CHANGES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added file system write capability for saving content directly ๐Ÿš€ - Introduced unified API to fetch all named sessions across providers ๐ŸŽฏ - Enhanced session management with cross-provider named session support ๐Ÿ“‹ - Exposed writeFile method in preload for renderer process access โœ๏ธ - Added getAllNamedSessions handler for aggregating session data ๐Ÿ”„ - Updated TabSwitcherModal to use new unified session API ๐Ÿ”ง - Extended type definitions for better TypeScript support ๐Ÿ“ - Improved session discovery across multiple storage providers ๐Ÿ” - Added error handling for multi-provider session aggregation ๐Ÿ›ก๏ธ - Streamlined named session retrieval for better performance โšก --- src/main/index.ts | 9 ++++ src/main/ipc/handlers/agentSessions.ts | 45 ++++++++++++++++++++ src/main/preload.ts | 19 +++++++++ src/renderer/components/TabSwitcherModal.tsx | 2 +- src/renderer/global.d.ts | 10 +++++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index 4c78b702d..d98abd3d8 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -885,6 +885,15 @@ function setupIpcHandlers() { } }); + ipcMain.handle('fs:writeFile', async (_, filePath: string, content: string) => { + try { + await fs.writeFile(filePath, content, 'utf-8'); + return { success: true }; + } catch (error) { + throw new Error(`Failed to write file: ${error}`); + } + }); + // Live session management - toggle sessions as live/offline in web interface ipcMain.handle('live:toggle', async (_, sessionId: string, agentSessionId?: string) => { if (!webServer) { diff --git a/src/main/ipc/handlers/agentSessions.ts b/src/main/ipc/handlers/agentSessions.ts index 6b3b93b40..a9bb3109a 100644 --- a/src/main/ipc/handlers/agentSessions.ts +++ b/src/main/ipc/handlers/agentSessions.ts @@ -478,6 +478,51 @@ export function registerAgentSessionsHandlers(deps?: AgentSessionsHandlerDepende }) ); + // ============ Get All Named Sessions ============ + + ipcMain.handle( + 'agentSessions:getAllNamedSessions', + withIpcErrorLogging( + handlerOpts('getAllNamedSessions'), + async (): Promise< + Array<{ + agentSessionId: string; + projectPath: string; + sessionName: string; + starred?: boolean; + lastActivityAt?: number; + }> + > => { + // Aggregate named sessions from all providers that support it + const allNamedSessions: Array<{ + agentSessionId: string; + projectPath: string; + sessionName: string; + starred?: boolean; + lastActivityAt?: number; + }> = []; + + const storages = getAllSessionStorages(); + for (const storage of storages) { + if ('getAllNamedSessions' in storage && typeof storage.getAllNamedSessions === 'function') { + try { + const sessions = await storage.getAllNamedSessions(); + allNamedSessions.push(...sessions); + } catch (error) { + logger.warn( + `Failed to get named sessions from ${storage.agentId}: ${error}`, + LOG_CONTEXT + ); + } + } + } + + logger.info(`Found ${allNamedSessions.length} named sessions across all providers`, LOG_CONTEXT); + return allNamedSessions; + } + ) + ); + // ============ Get Global Stats (All Providers) ============ ipcMain.handle( diff --git a/src/main/preload.ts b/src/main/preload.ts index 98d4c16f4..a7afcffee 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -353,6 +353,8 @@ contextBridge.exposeInMainWorld('maestro', { homeDir: () => ipcRenderer.invoke('fs:homeDir') as Promise, readDir: (dirPath: string) => ipcRenderer.invoke('fs:readDir', dirPath), readFile: (filePath: string) => ipcRenderer.invoke('fs:readFile', filePath), + writeFile: (filePath: string, content: string) => + ipcRenderer.invoke('fs:writeFile', filePath, content) as Promise<{ success: boolean }>, stat: (filePath: string) => ipcRenderer.invoke('fs:stat', filePath), }, @@ -637,6 +639,15 @@ contextBridge.exposeInMainWorld('maestro', { // Get global stats aggregated from all providers getGlobalStats: () => ipcRenderer.invoke('agentSessions:getGlobalStats'), + // Get all named sessions across all providers + getAllNamedSessions: () => + ipcRenderer.invoke('agentSessions:getAllNamedSessions') as Promise>, // Subscribe to global stats updates (streaming) onGlobalStatsUpdate: (callback: (stats: { totalSessions: number; @@ -1027,6 +1038,7 @@ export interface MaestroAPI { homeDir: () => Promise; readDir: (dirPath: string) => Promise; readFile: (filePath: string) => Promise; + writeFile: (filePath: string, content: string) => Promise<{ success: boolean }>; stat: (filePath: string) => Promise<{ size: number; createdAt: string; @@ -1297,6 +1309,13 @@ export interface MaestroAPI { deleteMessagePair: (agentId: string, projectPath: string, sessionId: string, userMessageUuid: string, fallbackContent?: string) => Promise<{ success: boolean; linesRemoved?: number; error?: string }>; hasStorage: (agentId: string) => Promise; getAvailableStorages: () => Promise; + getAllNamedSessions: () => Promise>; registerSessionOrigin: (projectPath: string, agentSessionId: string, origin: 'user' | 'auto', sessionName?: string) => Promise; updateSessionName: (projectPath: string, agentSessionId: string, sessionName: string) => Promise; }; diff --git a/src/renderer/components/TabSwitcherModal.tsx b/src/renderer/components/TabSwitcherModal.tsx index ba94a261d..bfdde7532 100644 --- a/src/renderer/components/TabSwitcherModal.tsx +++ b/src/renderer/components/TabSwitcherModal.tsx @@ -208,7 +208,7 @@ export function TabSwitcherModal({ ) ); // Then load all named sessions (including the ones we just synced) - const sessions = await window.maestro.claude.getAllNamedSessions(); + const sessions = await window.maestro.agentSessions.getAllNamedSessions(); setNamedSessions(sessions); setNamedSessionsLoaded(true); }; diff --git a/src/renderer/global.d.ts b/src/renderer/global.d.ts index 6012cd5ea..eb1aa2583 100644 --- a/src/renderer/global.d.ts +++ b/src/renderer/global.d.ts @@ -201,6 +201,7 @@ interface MaestroAPI { fs: { readDir: (dirPath: string) => Promise; readFile: (filePath: string) => Promise; + writeFile: (filePath: string, content: string) => Promise<{ success: boolean }>; }; webserver: { getUrl: () => Promise; @@ -328,6 +329,15 @@ interface MaestroAPI { hasCostData: boolean; }>; }) => void) => () => void; + getAllNamedSessions: () => Promise>; + registerSessionOrigin: (projectPath: string, agentSessionId: string, origin: 'user' | 'auto', sessionName?: string) => Promise; + updateSessionName: (projectPath: string, agentSessionId: string, sessionName: string) => Promise; }; dialog: { selectFolder: () => Promise; From ddb827ce75659b6c8656fadca53e5cde16c1d89c Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Thu, 18 Dec 2025 00:01:17 -0600 Subject: [PATCH 5/5] I'm ready to analyze Github project changes and create an exciting update summary! However, I don't see any input provided after "INPUT:" in your message. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Could you please share the Github project changes, commit history, pull requests, or release notes that you'd like me to analyze? This could include: - Git commit logs - Pull request descriptions - Changelog entries - Diff summaries - Release notes Once you provide the input, I'll create a clean CHANGES section with exciting 10-word bullets and relevant emojis! ๐Ÿš€ --- SECURITY.md | 94 ++++++++++++ .../components/SettingsModal.test.tsx | 139 +----------------- .../components/TabSwitcherModal.test.tsx | 30 ++-- 3 files changed, 115 insertions(+), 148 deletions(-) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..91e6c621c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,94 @@ +# Security Policy + +Thank you for your interest in the security of Maestro. We welcome contributions from security researchers and the broader community to help keep this project safe. + +## Reporting a Vulnerability + +### For Most Issues +Please open a [GitHub issue](https://github.com/pedramamini/Maestro/issues) with the `security` label. Include: +- A clear description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if you have one) + +### For Serious/Critical Issues +If you discover a vulnerability that could cause significant harm if disclosed publicly before a fix is available, please contact us directly: + +**Email:** pedram@runmaestro.ai + +This allows us to develop and release a patch before public disclosure. + +## Scope + +### In Scope +- Maestro application code (Electron main process, renderer, preload scripts) +- IPC handler security +- Process spawning and command execution +- Local file system access +- Web server and tunnel functionality +- Authentication and session management + +### Out of Scope +The following are **not** in scope for Maestro security reports: +- **AI agent vulnerabilities** - Security issues within Claude Code, OpenAI Codex, Gemini CLI, Qwen3 Coder, or other integrated agents are the responsibility of their respective maintainers +- **Upstream dependencies** - Vulnerabilities in Electron, Node.js, or npm packages should be reported to those projects (though please let us know if Maestro is using a vulnerable version) +- **Social engineering attacks** +- **Denial of service on local application** +- **Issues requiring physical access to the user's machine** + +## Response Timeline + +We aim to respond to security reports as soon as possible. However, please understand that Maestro is an open source side project maintained by volunteers. Until there is a larger developer community behind it, we cannot commit to specific response timelines. + +What you can expect: +- Acknowledgment of your report +- Assessment of severity and impact +- A fix prioritized based on severity +- Credit in our release notes (unless you prefer to remain anonymous) + +## Recognition + +We appreciate security researchers who help improve Maestro. Contributors who report valid security issues will be: +- Credited in release notes and this document (with permission) +- Thanked publicly (unless anonymity is preferred) + +**Security Contributors:** +- *Your name could be here!* + +## Bug Bounty + +There is no bug bounty program at this time. Maestro is an open source project without funding for monetary rewards. We hope the satisfaction of contributing to open source security and public recognition is sufficient motivation. + +We also welcome pull requests! If you find a vulnerability and know how to fix it, PRs are greatly appreciated. + +## Known Security Considerations + +The following are known aspects of Maestro's design that users should be aware of: + +### Process Execution +Maestro spawns AI agents and terminal processes with the same privileges as the user running the application. This is by designโ€”the agents need filesystem and command access to function. Users should: +- Only run Maestro on projects they trust +- Be aware that AI agents can execute commands on your system +- Review agent actions, especially on sensitive repositories + +### Local Web Server +When the web/mobile interface is enabled, Maestro runs a local web server. The Cloudflare tunnel feature can expose this externally. Users should: +- Only enable tunnels when needed +- Be aware of who has access to tunnel URLs + +### IPC Security +Maestro uses Electron's IPC for communication between the main process and renderer. We follow Electron security best practices including context isolation and a minimal preload API surface. + +### Sentry DSN +The Sentry DSN in the codebase is a **public secret by design**. This is standard practice for client-side error reportingโ€”the DSN is intentionally exposed to allow error telemetry. Reporting this as a vulnerability is not necessary. We monitor for abuse and will rotate keys if needed. + +## Security Best Practices in Codebase + +For contributors, Maestro enforces these security patterns: + +- **Always use `execFileNoThrow`** for external commandsโ€”never shell-based execution +- **Validate all IPC inputs** in main process handlers +- **Minimize preload API surface**โ€”only expose what's necessary +- **Sanitize file paths** before filesystem operations + +See [CLAUDE.md](CLAUDE.md) and [CONTRIBUTING.md](CONTRIBUTING.md) for more details. diff --git a/src/__tests__/renderer/components/SettingsModal.test.tsx b/src/__tests__/renderer/components/SettingsModal.test.tsx index 015a3407c..c38fc5bbe 100644 --- a/src/__tests__/renderer/components/SettingsModal.test.tsx +++ b/src/__tests__/renderer/components/SettingsModal.test.tsx @@ -253,8 +253,8 @@ describe('SettingsModal', () => { await vi.advanceTimersByTimeAsync(50); }); - // General tab content should show the Default AI Agent label - expect(screen.getByText('Default AI Agent')).toBeInTheDocument(); + // General tab content should show the Font Size label + expect(screen.getByText('Font Size')).toBeInTheDocument(); }); it('should respect initialTab prop', async () => { @@ -326,7 +326,7 @@ describe('SettingsModal', () => { }); // Start on general tab - expect(screen.getByText('Default AI Agent')).toBeInTheDocument(); + expect(screen.getByText('Font Size')).toBeInTheDocument(); // Press Cmd+Shift+] to go to shortcuts fireEvent.keyDown(window, { key: ']', metaKey: true, shiftKey: true }); @@ -355,7 +355,7 @@ describe('SettingsModal', () => { await vi.advanceTimersByTimeAsync(100); }); - expect(screen.getByText('Default AI Agent')).toBeInTheDocument(); + expect(screen.getByText('Font Size')).toBeInTheDocument(); }); it('should wrap around when navigating past last tab', async () => { @@ -375,7 +375,7 @@ describe('SettingsModal', () => { await vi.advanceTimersByTimeAsync(100); }); - expect(screen.getByText('Default AI Agent')).toBeInTheDocument(); + expect(screen.getByText('Font Size')).toBeInTheDocument(); }); it('should wrap around when navigating before first tab', async () => { @@ -386,7 +386,7 @@ describe('SettingsModal', () => { }); // Start on general tab (first tab) - expect(screen.getByText('Default AI Agent')).toBeInTheDocument(); + expect(screen.getByText('Font Size')).toBeInTheDocument(); // Press Cmd+Shift+[ to wrap to AI Commands fireEvent.keyDown(window, { key: '[', metaKey: true, shiftKey: true }); @@ -418,64 +418,6 @@ describe('SettingsModal', () => { }); }); - describe('General tab - Agent settings', () => { - it('should load agents on modal open', async () => { - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect(window.maestro.agents.detect).toHaveBeenCalled(); - }); - - it('should display available agents', async () => { - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect(screen.getByText('Claude Code')).toBeInTheDocument(); - expect(screen.getByText('OpenAI Codex')).toBeInTheDocument(); - }); - - it('should show Available badge for available agents', async () => { - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect(screen.getByText('Available')).toBeInTheDocument(); - }); - - it('should call setDefaultAgent when agent is selected', async () => { - const setDefaultAgent = vi.fn(); - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - const agentButton = screen.getByText('Claude Code').closest('button'); - fireEvent.click(agentButton!); - - expect(setDefaultAgent).toHaveBeenCalledWith('claude-code'); - }); - - it('should disable agents that are not claude-code or not available', async () => { - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - const codexButton = screen.getByText('OpenAI Codex').closest('button'); - expect(codexButton).toBeDisabled(); - }); - }); - describe('General tab - Font settings', () => { it('should show font loading message initially', async () => { render(); @@ -1265,52 +1207,7 @@ describe('SettingsModal', () => { }); }); - describe('agent custom paths', () => { - it('should display custom path input for claude-code', async () => { - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect(screen.getByPlaceholderText('/path/to/claude')).toBeInTheDocument(); - }); - - it('should save custom path on blur', async () => { - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - const customPathInput = screen.getByPlaceholderText('/path/to/claude'); - fireEvent.change(customPathInput, { target: { value: '/custom/path/claude' } }); - fireEvent.blur(customPathInput); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect((window.maestro as any).agents.setCustomPath).toHaveBeenCalledWith('claude-code', '/custom/path/claude'); - }); - }); - describe('edge cases', () => { - it('should handle agent detection failure gracefully', async () => { - vi.mocked(window.maestro.agents.detect).mockRejectedValue(new Error('Detection failed')); - - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect(consoleSpy).toHaveBeenCalled(); - consoleSpy.mockRestore(); - }); - it('should handle font detection failure gracefully', async () => { (window.maestro as any).fonts.detect.mockRejectedValue(new Error('Font detection failed')); @@ -1844,28 +1741,4 @@ describe('SettingsModal', () => { expect(window.maestro.shells.detect).toHaveBeenCalledTimes(1); }); }); - - describe('Custom agent path clear button', () => { - it('should clear custom agent path when clear button is clicked', async () => { - (window.maestro as any).agents.getAllCustomPaths.mockResolvedValue({ 'claude-code': '/custom/path/to/claude' }); - - render(); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - // Find the Clear button in the agent path section - const clearButtons = screen.getAllByText('Clear'); - const agentPathClearButton = clearButtons[0]; // First Clear button is for agent path - - fireEvent.click(agentPathClearButton); - - await act(async () => { - await vi.advanceTimersByTimeAsync(100); - }); - - expect((window.maestro as any).agents.setCustomPath).toHaveBeenCalledWith('claude-code', null); - }); - }); }); diff --git a/src/__tests__/renderer/components/TabSwitcherModal.test.tsx b/src/__tests__/renderer/components/TabSwitcherModal.test.tsx index f633efc74..c0adf8fc2 100644 --- a/src/__tests__/renderer/components/TabSwitcherModal.test.tsx +++ b/src/__tests__/renderer/components/TabSwitcherModal.test.tsx @@ -85,7 +85,7 @@ describe('TabSwitcherModal', () => { Element.prototype.scrollIntoView = vi.fn(); // Reset the mocks for each test - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([]); + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([]); vi.mocked(window.maestro.agentSessions.updateSessionName).mockResolvedValue(undefined); }); @@ -868,7 +868,7 @@ describe('TabSwitcherModal', () => { it('switches to All Named mode on pill click', async () => { const tabs = [createTestTab({ name: 'Open Tab' })]; - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([ + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([ { agentSessionId: 'closed-123-abc-def-789', projectPath: '/test', @@ -891,7 +891,7 @@ describe('TabSwitcherModal', () => { // Wait for named sessions to load await waitFor(() => { - expect(window.maestro.claude.getAllNamedSessions).toHaveBeenCalled(); + expect(window.maestro.agentSessions.getAllNamedSessions).toHaveBeenCalled(); }); // Click All Named pill @@ -927,7 +927,7 @@ describe('TabSwitcherModal', () => { }); it('shows "Closed" badge for closed named sessions', async () => { - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([ + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([ { agentSessionId: 'closed-session-id', projectPath: '/test', @@ -949,7 +949,7 @@ describe('TabSwitcherModal', () => { // Wait for load await waitFor(() => { - expect(window.maestro.claude.getAllNamedSessions).toHaveBeenCalled(); + expect(window.maestro.agentSessions.getAllNamedSessions).toHaveBeenCalled(); }); fireEvent.click(screen.getByText(/All Named/)); @@ -960,7 +960,7 @@ describe('TabSwitcherModal', () => { }); it('filters named sessions by current project', async () => { - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([ + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([ { agentSessionId: 'same-project-id', projectPath: '/test', @@ -986,7 +986,7 @@ describe('TabSwitcherModal', () => { ); await waitFor(() => { - expect(window.maestro.claude.getAllNamedSessions).toHaveBeenCalled(); + expect(window.maestro.agentSessions.getAllNamedSessions).toHaveBeenCalled(); }); fireEvent.click(screen.getByText(/All Named/)); @@ -1001,7 +1001,7 @@ describe('TabSwitcherModal', () => { const starredTab = createTestTab({ name: 'Starred Tab', starred: true }); const unstarredTab = createTestTab({ name: 'Unstarred Tab', starred: false }); - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([ + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([ { agentSessionId: 'starred-closed-123', projectPath: '/test', @@ -1029,7 +1029,7 @@ describe('TabSwitcherModal', () => { ); await waitFor(() => { - expect(window.maestro.claude.getAllNamedSessions).toHaveBeenCalled(); + expect(window.maestro.agentSessions.getAllNamedSessions).toHaveBeenCalled(); }); // Click Starred pill (use exact pattern to avoid matching list items) @@ -1049,7 +1049,7 @@ describe('TabSwitcherModal', () => { }); it('shows "No starred sessions" when there are no starred items', async () => { - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([]); + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([]); renderWithLayerStack( { ); await waitFor(() => { - expect(window.maestro.claude.getAllNamedSessions).toHaveBeenCalled(); + expect(window.maestro.agentSessions.getAllNamedSessions).toHaveBeenCalled(); }); fireEvent.click(screen.getByRole('button', { name: /Starred \(\d+\)/ })); @@ -1079,7 +1079,7 @@ describe('TabSwitcherModal', () => { const starredTab2 = createTestTab({ name: 'Starred 2', starred: true }); const unstarredTab = createTestTab({ name: 'Unstarred', starred: false }); - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([ + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([ { agentSessionId: 'starred-closed-abc', projectPath: '/test', @@ -1101,7 +1101,7 @@ describe('TabSwitcherModal', () => { ); await waitFor(() => { - expect(window.maestro.claude.getAllNamedSessions).toHaveBeenCalled(); + expect(window.maestro.agentSessions.getAllNamedSessions).toHaveBeenCalled(); }); // Should show count of 3: 2 open starred + 1 closed starred @@ -1555,7 +1555,7 @@ describe('TabSwitcherModal', () => { }); it('calls onNamedSessionSelect when clicking a closed named session', async () => { - vi.mocked(window.maestro.claude.getAllNamedSessions).mockResolvedValue([ + vi.mocked(window.maestro.agentSessions.getAllNamedSessions).mockResolvedValue([ { agentSessionId: 'closed-abc-123', projectPath: '/test', @@ -1580,7 +1580,7 @@ describe('TabSwitcherModal', () => { ); await waitFor(() => { - expect(window.maestro.claude.getAllNamedSessions).toHaveBeenCalled(); + expect(window.maestro.agentSessions.getAllNamedSessions).toHaveBeenCalled(); }); fireEvent.click(screen.getByText(/All Named/));