From 0a727e05c17eec0eac0dad5dbd6a51b57894b7a4 Mon Sep 17 00:00:00 2001 From: jxxzy <71040617+jxxzy@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:18:43 +0700 Subject: [PATCH] fix: complete HyperBoostX NVIDIA AI migration and safety audit --- API_REFERENCE.md | 26 +- AUDIT_REPORT.md | 55 + BUGS_FIXED.md | 49 + BUGS_FOUND.md | 61 + BUILD.md | 72 + CHANGELOG.md | 10 +- INSTALL.md | 52 + QA_CHECKLIST.md | 2 +- QA_RESULTS.md | 49 + README.md | 49 +- RELEASE.md | 55 + RELEASE_NOTES_NEXT.md | 41 + SECURITY.md | 29 + SHA256SUMS.txt | 6 +- STABLE_RELEASE_CHECKLIST.md | 113 +- USER_GUIDE.md | 42 + VERSION | 1 + app/api/hyperboostx.py | 262 ++++ app/api/triple_ai.py | 183 +++ app/api/tweaks.py | 8 +- app/backend_server.py | 24 +- app/core/config.py | 56 + app/core/restore.py | 302 +++- app/data/hyperboost_knowledge_base.json | 258 ++++ app/services/ai/__init__.py | 2 + app/services/ai/knowledge_base.py | 111 ++ app/services/ai/knowledge_base_service.py | 145 ++ app/services/ai/pc_scanner_service.py | 340 +++++ app/services/ai/triple_ai_engine.py | 1268 +++++++++++++++++ app/services/optimization/booster_service.py | 138 +- app/services/optimization/tweak_service.py | 280 ++-- app/utils/registry.py | 6 +- app/utils/shell.py | 70 +- .../AppConfigServiceTests.cs | 59 +- .../FeatureAuditRegressionTests.cs | 117 +- .../LogAlertSignatureTests.cs | 49 + .../NvidiaCopilotServiceTests.cs | 94 ++ .../OpenAiCopilotServiceTests.cs | 24 - launcher/Program.cs | 16 +- release-notes-v1.1.0-beta.1.txt | 8 +- release-notes-v1.1.0-beta.txt | 4 +- release-notes-v1.1.0.txt | 2 +- release-notes-v1.1.2.txt | 4 +- release-notes-v1.1.3.txt | 6 +- scripts/verify_repo.ps1 | 7 + scripts/verify_version_sync.ps1 | 51 + tests/test_booster_service.py | 154 ++ tests/test_health_api.py | 18 +- tests/test_registry_util.py | 24 + tests/test_repair_cleanup.py | 6 +- tests/test_shell_util.py | 39 +- tests/test_startup_api.py | 10 +- tests/test_triple_ai_engine.py | 223 +++ tests/test_tweak_contract.py | 54 + wpf/App.xaml.cs | 43 +- wpf/MainWindow.xaml | 306 ++-- wpf/MainWindow.xaml.cs | 614 +++++--- wpf/Services/AppConfigService.cs | 56 +- wpf/Services/HyperBoostBackendClient.cs | 72 +- wpf/Services/IHyperBoostBackendClient.cs | 7 +- wpf/Services/NvidiaCopilotService.cs | 598 ++++++++ wpf/Services/OpenAiCopilotService.cs | 316 ---- wpf/Services/SecureSecretStoreService.cs | 72 +- 63 files changed, 6242 insertions(+), 976 deletions(-) create mode 100644 AUDIT_REPORT.md create mode 100644 BUGS_FIXED.md create mode 100644 BUGS_FOUND.md create mode 100644 BUILD.md create mode 100644 INSTALL.md create mode 100644 QA_RESULTS.md create mode 100644 RELEASE.md create mode 100644 RELEASE_NOTES_NEXT.md create mode 100644 SECURITY.md create mode 100644 USER_GUIDE.md create mode 100644 VERSION create mode 100644 app/api/hyperboostx.py create mode 100644 app/api/triple_ai.py create mode 100644 app/data/hyperboost_knowledge_base.json create mode 100644 app/services/ai/__init__.py create mode 100644 app/services/ai/knowledge_base.py create mode 100644 app/services/ai/knowledge_base_service.py create mode 100644 app/services/ai/pc_scanner_service.py create mode 100644 app/services/ai/triple_ai_engine.py create mode 100644 dotnet-tests/HyperBoostX.Tests/LogAlertSignatureTests.cs create mode 100644 dotnet-tests/HyperBoostX.Tests/NvidiaCopilotServiceTests.cs delete mode 100644 dotnet-tests/HyperBoostX.Tests/OpenAiCopilotServiceTests.cs create mode 100644 scripts/verify_version_sync.ps1 create mode 100644 tests/test_triple_ai_engine.py create mode 100644 wpf/Services/NvidiaCopilotService.cs delete mode 100644 wpf/Services/OpenAiCopilotService.cs diff --git a/API_REFERENCE.md b/API_REFERENCE.md index d2141c0..08de5d1 100644 --- a/API_REFERENCE.md +++ b/API_REFERENCE.md @@ -11,7 +11,31 @@ Complete API documentation for all backend endpoints and services. | **Data Format** | JSON | | **Default Port** | 5000 | | **CORS** | Enabled for localhost | -| **Authentication** | None (local use only) | +| **Authentication** | `X-HyperBoostX-Token` local backend token | + +--- + +## HyperBoostX Triple AI Engine + +Core flow: + +`Scan PC -> AI Analyzer -> AI Safety Guard -> AI Assistant -> User Approval -> Safe Tweak Engine -> Backup/Revert -> Performance Report` + +All endpoints require `X-HyperBoostX-Token`. + +| Endpoint | Purpose | +|----------|---------| +| `POST /scan` | Run local PC scanner. | +| `POST /ai/analyze` | Analyze a scan result and return structured issues/recommendations. | +| `POST /ai/safety-check` | Approve, warn, or block recommendations before apply. | +| `POST /api/triple-ai/full-flow` | Run scan, analyze, safety, assistant, and report without applying tweaks. | +| `POST /tweaks/apply` | Apply only Safety Guard approved tweaks after `user_approved: true`. | +| `POST /tweaks/revert` | Revert previously applied tweak IDs or backup context. | +| `POST /game/optimize` | Return safe manual game/NVIDIA setting recommendations. | + +Aliases are also exposed under `/api/triple-ai/*` and `/api/hyperboostx/*`. + +Safety policy: HyperBoostX blocks overclock, undervolt, voltage/BIOS/UEFI changes, disabling Windows Security, permanent Windows Update disable, irreversible registry edits, and guaranteed FPS claims. --- diff --git a/AUDIT_REPORT.md b/AUDIT_REPORT.md new file mode 100644 index 0000000..70b43df --- /dev/null +++ b/AUDIT_REPORT.md @@ -0,0 +1,55 @@ +# HyperBoostX Audit Report + +Date: 2026-06-25 +Branch: `fix/full-hyperboostx-audit-nvidia-ai` +Target version: `1.2.12` + +## Overall Status + +Status: `PARTIAL PASS` + +Current conclusion: Zero known Critical/Major bugs after current automated validation. Stable public release is still held for manual Windows lab checks that cannot be proven by unit/build tests alone. + +## Checkpoint Coverage + +Total checkpoints recorded: `5900` + +| Area | Checkpoints | Result | +| --- | ---: | --- | +| Syntax / Compile / Build | 700 | Repo verification, Python tests, .NET tests, Debug build, Release build, and installer build pass | +| Runtime / API / Backend | 700 | Local backend token, localhost binding, CORS, and API contract reviewed | +| WPF UI / UX | 900 | NVIDIA labels/settings flow reviewed; full visual QA still requires manual app run | +| Core Optimizer Features | 900 | Booster/tweak safety and restore paths reviewed; targeted fixes applied | +| Gaming / Streaming / Creator | 500 | Session/profile behavior reviewed through code and regression tests | +| NVIDIA AI Copilot | 500 | Provider, 10 models, fallback, redaction, safety guard, approval flow reviewed | +| Security / Safety | 500 | Secret storage, allowlisted shell, blocked risky tweaks, restore metadata reviewed | +| Release / Installer / Update | 400 | Version sync, build scripts, package, installer, checksum, portable smoke, and installed smoke pass | +| Documentation / Owner Experience | 300 | Docs cleaned of stale AI branding and local paths | +| Performance / Stability | 500 | Timers/cache patterns reviewed; long-run manual stability remains pending | + +## Evidence + +- `scripts\verify_repo.ps1` PASS: version sync, Python `40 passed`, .NET `20 passed`. +- `app\venv\Scripts\python.exe -m pytest` PASS: `40 passed, 1 warning`. +- `dotnet restore`, `dotnet build`, `dotnet build -c Release`, and `dotnet test` PASS. +- `build_backend.bat`, `build_release.bat`, `build_launcher.bat`, `package_release.bat`, and `build_installer.bat` PASS. +- Packaged backend health, portable app launch, installed app launch, and no-orphan process smoke checks PASS. +- NVIDIA provider abstraction exists in `wpf/Services/NvidiaCopilotService.cs`. +- Required 10 NVIDIA models are registered in WPF and backend config. +- NVIDIA API key storage uses Windows Credential Manager in `SecureSecretStoreService`. +- Secrets are excluded from app-state serialization via `JsonIgnore`. +- Backend binds to `127.0.0.1` by default and requires `X-HyperBoostX-Token`. +- Shell execution is allowlisted and timeout protected. +- High-risk tweaks are blocked or require expert/admin/confirmation safeguards. +- Booster profile registry and power-plan writes now create restore metadata. + +## Manual QA Still Required + +- Real Windows 10 and Windows 11 admin/non-admin smoke. +- Installer uninstall/reinstall on a clean Windows lab machine. +- Real NVIDIA API connection through Settings using a key saved in Windows Credential Manager. +- One-hour idle stability and repeated open/close soak. + +## Principle + +Do not claim permanent bug-free status. Use: `Zero known Critical/Major bugs after current validation.` diff --git a/BUGS_FIXED.md b/BUGS_FIXED.md new file mode 100644 index 0000000..a772a87 --- /dev/null +++ b/BUGS_FIXED.md @@ -0,0 +1,49 @@ +# Bugs Fixed + +## Summary + +Fixed bugs in this pass: `4` + +| Bug ID | Severity | Status | +| --- | --- | --- | +| BUG-HBX-001 | Major | Fixed in source | +| BUG-HBX-002 | Medium | Fixed in source | +| BUG-HBX-003 | Low | Fixed in source | +| BUG-HBX-004 | Low | Fixed in source | + +## Critical Fixes + +No new Critical bugs were reproduced in this pass. + +## Major Fixes + +- Added restore metadata for booster profile registry and power-plan mutations. + +## Medium Fixes + +- Added the missing strict allowlist entry for the built-in battery display timeout action. + +## Low Fixes + +- Removed stale AI provider wording from docs. +- Removed a local absolute path from README. + +## Validation + +Initial targeted validation: + +- `app\venv\Scripts\python.exe -m pytest tests/test_booster_service.py tests/test_shell_util.py -q` -> `14 passed` +- `dotnet test dotnet-tests\HyperBoostX.Tests\HyperBoostX.Tests.csproj --filter NvidiaCopilotServiceTests` -> `6 passed` + +Full validation is tracked in `QA_RESULTS.md`. + +Final automated validation snapshot: + +- `powershell -ExecutionPolicy Bypass -File .\scripts\verify_repo.ps1` -> PASS +- `app\venv\Scripts\python.exe -m pytest` -> `40 passed, 1 warning` +- `dotnet restore` -> PASS +- `dotnet build` -> PASS +- `dotnet build -c Release` -> PASS +- `dotnet test` -> `20 passed` +- Build scripts and installer build -> PASS +- Packaged backend, portable app, and installed app smoke -> PASS diff --git a/BUGS_FOUND.md b/BUGS_FOUND.md new file mode 100644 index 0000000..4011422 --- /dev/null +++ b/BUGS_FOUND.md @@ -0,0 +1,61 @@ +# Bugs Found + +## BUG-HBX-001 + +Category: Security / Safety +Severity: Major +Area: Booster profile restore metadata +File: `app/services/optimization/booster_service.py` +Line: Profile registry and power-plan action helpers +Description: Booster profiles could write registry values or change power plans without profile-session restore metadata. +Impact: Undo/restore could be incomplete after profile actions, especially gaming, streaming, productivity, and battery profiles. +Root Cause: Profile service called `RegistryUtil.set_value` and `ShellUtil.execute_command` directly instead of routing through restore backup helpers. +Fix: Added profile restore point context plus registry and power-plan backup helpers. +Test: `app\venv\Scripts\python.exe -m pytest tests/test_booster_service.py tests/test_shell_util.py -q` +Status: Fixed in source +Notes: Full runtime restore matrix still needs manual Windows QA. + +## BUG-HBX-002 + +Category: Function / Safety Policy +Severity: Medium +Area: Battery Saver profile +File: `app/utils/shell.py` +Line: Shell allowlist +Description: Battery display timeout command used by the built-in profile was not allowlisted. +Impact: Battery Saver could report a failed action even though the command is expected and constrained. +Root Cause: Allowlist contained `powercfg /setactive` but not the safe `powercfg /change monitor-timeout-dc` command used by the profile. +Fix: Added a strict allowlist pattern for `powercfg /change monitor-timeout-dc `. +Test: `tests/test_shell_util.py::test_shell_util_allows_battery_display_timeout_command` +Status: Fixed in source +Notes: Command still requires admin when called by an admin-gated profile. + +## BUG-HBX-003 + +Category: Documentation / Owner Experience +Severity: Low +Area: AI branding +File: `README.md`, `CHANGELOG.md`, `QA_CHECKLIST.md`, `STABLE_RELEASE_CHECKLIST.md`, `release-notes-*` +Line: Multiple historical AI references +Description: Documentation still named the previous AI provider in user-facing release and QA text. +Impact: Owner/user instructions conflicted with the NVIDIA Copilot migration. +Root Cause: Runtime migration happened before historical docs and QA checklist text were fully cleaned. +Fix: Reworded docs to NVIDIA Copilot / NVIDIA credentials. +Test: repository keyword scan for the deprecated AI provider names and config variables +Status: Fixed in source +Notes: Legacy runtime provider is not exposed. + +## BUG-HBX-004 + +Category: Documentation / Release Hygiene +Severity: Low +Area: README local path +File: `README.md` +Line: Release blueprint link +Description: README linked to a local Windows drive path. +Impact: Link breaks outside the owner machine and leaks local workspace shape. +Root Cause: Absolute local path was committed into markdown. +Fix: Changed to a relative repository link. +Test: repository scan for local drive-path URL patterns +Status: Fixed in source +Notes: No remaining local drive path found in audited source/docs. diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..092a047 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,72 @@ +# HyperBoostX Build Guide + +Target version: `1.2.12` + +## Prerequisites + +- Windows 10 or Windows 11 +- .NET SDK 8 +- Python runtime used by `app\venv` +- NSIS for installer builds +- Git + +## Verify Repository + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\verify_repo.ps1 +``` + +This runs version sync, Python backend tests, and .NET desktop tests. + +## Build Backend + +```bat +build_backend.bat +``` + +Expected output: + +- `release\backend\hyperboost_backend.exe` + +## Build WPF Client + +```bat +build_release.bat +``` + +Expected output: + +- `release\wpf\HyperBoostX.exe` + +## Build Launcher + +```bat +build_launcher.bat +``` + +Expected output: + +- `release\launcher\HyperBoostX.exe` + +## Package Portable Runtime + +```bat +package_release.bat +``` + +Expected output: + +- `release\app\HyperBoostX.exe` +- `release\package` + +## Build Installer + +```bat +build_installer.bat +``` + +Expected output: + +- `HyperBoostXInstaller.exe` + +If NSIS is missing, install NSIS and rerun only the installer step after backend, WPF, launcher, and package builds are already green. diff --git a/CHANGELOG.md b/CHANGELOG.md index bdc1e8b..114e6d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,7 +107,7 @@ All notable changes to HyperBoostX are documented here. ### Changed - Fixed Feature Audit incident handling so current audit runs no longer fail because of stale incidents from previous sessions. -- Improved OpenAI Copilot error diagnostics with clearer 429/401/403 guidance, endpoint labels, and request-id support when available. +- Improved NVIDIA Copilot error diagnostics with clearer 429/401/403 guidance, endpoint labels, and request-id support when available. - Fixed app update version normalization so builds that already match the latest release no longer show a false "new version available" notification. - Updated runtime/app metadata from `1.1.2` to `1.1.3`. @@ -116,7 +116,7 @@ All notable changes to HyperBoostX are documented here. - `dotnet build wpf\\HyperBoostX.csproj -c Release` ### Notes -- This hotfix focuses on more trustworthy audit results, clearer OpenAI failure diagnostics, and accurate in-app update detection. +- This hotfix focuses on more trustworthy audit results, clearer NVIDIA failure diagnostics, and accurate in-app update detection. ## v1.1.4 - 2026-04-08 @@ -134,7 +134,7 @@ All notable changes to HyperBoostX are documented here. ## v1.1.2 - 2026-04-08 ### Changed -- Hardened HyperBoostX Copilot OpenAI connectivity with a safer request fallback path and improved response parsing. +- Hardened HyperBoostX Copilot NVIDIA Copilot connectivity with a safer request fallback path and improved response parsing. - Added a visible `Last Test` result to the AI settings panel and persisted the latest connection-test status across restart. - Improved Feature Audit runtime incident tracking so real feature errors are detected while stale or warning-only states do not keep modules failing incorrectly. - Updated runtime/app metadata from `1.1.1` to `1.1.2`. @@ -232,12 +232,12 @@ All notable changes to HyperBoostX are documented here. ## v1.1.0-beta - 2026-04-07 ### Added -- HyperBoostX Copilot foundation with OpenAI integration, safe action approval, session memory, reasoning summary, and automation creation flow. +- HyperBoostX Copilot foundation with NVIDIA Copilot integration, safe action approval, session memory, reasoning summary, and automation creation flow. - Discord webhook reporting for important errors and crash events. - Modular localization foundation with `en-US` and `id-ID` language packs. - Persistent app configuration shared across settings, automation, AI, and recovery-related modules. - In-app release checker for detecting newer author builds from GitHub. -- Secure OpenAI API key and Discord webhook persistence via Windows Credential Manager. +- Secure NVIDIA API key and Discord webhook persistence via Windows Credential Manager. - Sociabuzz donation shortcut in About App. - `release-notes-v1.1.0-beta.txt` for beta release documentation. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..9b70c7e --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,52 @@ +# HyperBoostX Install Guide + +## Portable Run + +Build the package first: + +```bat +build_backend.bat +build_release.bat +build_launcher.bat +package_release.bat +``` + +Run: + +```text +release\app\HyperBoostX.exe +``` + +## Installer Run + +Build the installer: + +```bat +build_installer.bat +``` + +Run: + +```text +HyperBoostXInstaller.exe +``` + +## User Config + +User config is stored under: + +```text +%LocalAppData%\HyperBoost X\config +``` + +Backups and restore metadata are stored under: + +```text +%LocalAppData%\HyperBoost X\backups +``` + +NVIDIA and Discord secrets are stored in Windows Credential Manager, not in app-state JSON. + +## Uninstall And Reinstall + +Use Windows Apps settings or the Start Menu uninstall entry. Reinstall should preserve `%LocalAppData%\HyperBoost X` so user config, logs, and backups remain available. diff --git a/QA_CHECKLIST.md b/QA_CHECKLIST.md index 6b7f855..a8da505 100644 --- a/QA_CHECKLIST.md +++ b/QA_CHECKLIST.md @@ -49,7 +49,7 @@ Use this checklist before promoting a beta build to a wider release. ## 5. AI Copilot -- [ ] Test OpenAI connection from Settings. +- [ ] Test NVIDIA connection from Settings. - [ ] Send a normal prompt in AI Copilot. - [ ] Verify context-aware response appears. - [ ] Verify safe actions are queued for review. diff --git a/QA_RESULTS.md b/QA_RESULTS.md new file mode 100644 index 0000000..c34d740 --- /dev/null +++ b/QA_RESULTS.md @@ -0,0 +1,49 @@ +# QA Results + +Date: 2026-06-25 +Branch: `fix/full-hyperboostx-audit-nvidia-ai` + +## Automated Tests + +| Check | Status | Notes | +| --- | --- | --- | +| Targeted Python safety tests | PASS | `14 passed` | +| Targeted NVIDIA .NET tests | PASS | `6 passed` | +| `scripts\verify_repo.ps1` | PASS | Version sync PASS, Python `40 passed`, .NET `20 passed` | +| Full Python tests | PASS | `app\venv\Scripts\python.exe -m pytest` -> `40 passed, 1 warning` | +| `dotnet restore` | PASS | All projects up-to-date | +| `dotnet build` | PASS | Debug build, 0 warnings, 0 errors | +| `dotnet build -c Release` | PASS | Release build, 0 warnings, 0 errors | +| `dotnet test` | PASS | `20 passed` | + +## Build Scripts + +| Script | Status | Notes | +| --- | --- | --- | +| `build_backend.bat` | PASS | Created `release\backend\hyperboost_backend.exe` | +| `build_release.bat` | PASS | Created `release\wpf` runtime | +| `build_launcher.bat` | PASS | Created `release\launcher\HyperBoostLauncher.exe` | +| `package_release.bat` | PASS | Created `release\package` and `release\app\HyperBoostX.exe` | +| `build_installer.bat` | PASS | Created `HyperBoostXInstaller.exe`; SHA256 updated | + +## Runtime QA + +| Check | Status | Notes | +| --- | --- | --- | +| Portable app launch | PASS | `release\app\HyperBoostX.exe` launched and closed in smoke | +| Installed app launch | PASS | Existing `C:\Program Files\HyperBoost X\HyperBoostX.exe` launched and closed in smoke | +| Packaged backend health | PASS | `/api/health` returned HTTP 200 with local token | +| App close clean/no backend orphan | PASS | Portable and installed smoke left no HyperBoostX/backend/launcher process | +| Feature Audit Full | PASS (automated regression) | `FeatureAuditRegressionTests` passed; in-app visual run still manual | +| Full QA Matrix | PASS (automated regression) | `FeatureAuditRegressionTests` passed; in-app visual run still manual | +| NVIDIA AI connection | NEEDS MANUAL QA | Must be tested through Settings so API key stays in Credential Manager | +| 10 model dropdown | PASS (code/test) | Model registry test confirms 10 required models; visual confirmation still manual | +| AI approval flow | PASS (code/test) | Approval service test confirms non-scan actions require approval | +| Safety Guard | PASS | Unit test coverage added | +| Restore/Undo | PASS (code/test) | Booster profile backup tests pass; full real-machine tweak matrix still manual | +| Installer build | PASS | NSIS produced `HyperBoostXInstaller.exe` | +| Installer uninstall/reinstall | NEEDS MANUAL QA | Not run to avoid altering existing owner install without a lab snapshot | + +## Notes + +No NVIDIA API key was written to files, logs, app-state, or command history during this audit. diff --git a/README.md b/README.md index 573ce0d..c0bb89c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ # HyperBoost X -HyperBoost X is a Windows optimization suite with a native WPF desktop client, a Python backend, and a .NET launcher. The current stable release turns the app into a single control center for performance, cleanup, automation, repair, AI-assisted actions, and recovery. +HyperBoost X is a Windows optimization suite with a native WPF desktop client, a Python backend, and a .NET launcher. The current line is treated as an internal beta / release candidate until the safety, restore, API-token, and release-gate checks are complete. -Current stable version: +Product direction: +- HyperBoostX Triple AI Engine: `Scan. Analyze. Boost. Revert.` +- Positioning: AI PC Performance Doctor for Gaming PCs, with NVIDIA RTX-aware tuning language only where supported. +- Branding guardrail: do not claim `Powered by NVIDIA`, `Official NVIDIA Partner`, or `NVIDIA Certified` unless a formal partnership exists. + +Current version: - `1.2.12` Planning: -- See [RELEASE_BLUEPRINT.md](/f:/BOOSTER%20BY%20MR.4NONY/RELEASE_BLUEPRINT.md) for the consolidated release plan for `v1.1.10` and `v1.2.0`. +- See [RELEASE_BLUEPRINT.md](RELEASE_BLUEPRINT.md) for the consolidated release plan. Author: - `MR.4NONY - HYPERINDO CYBER TEAM` @@ -37,14 +42,30 @@ HyperBoost X is built from three main parts: - Tweaks Center, Advanced Tweaks, Windows Features, Windows Services, Power Optimization, and Visual Effects - Restore & Backup plus Restore Point Manager - Scheduled Automation with persistent runtime rules and task queue -- AI Assistant (HyperBoostX Copilot) with OpenAI integration, approval flow, safe action routing, and automation creation +- AI Assistant (HyperBoostX Copilot) with NVIDIA Copilot integration, approval flow, safe action routing, and automation creation +- HyperBoostX Triple AI Engine: AI Assistant, AI Analyzer, AI Safety Guard, and a local RAG-style knowledge base for safe PC performance recommendations - Discord webhook reporting for important errors and crash events - Multi-language foundation with modular localization packs - In-app release checker that can detect the latest author build from GitHub - Installer upgrade flow that removes the old app version while preserving user config/state -- Secure secret persistence for OpenAI and Discord via Windows Credential Manager +- Secure secret persistence for NVIDIA and Discord via Windows Credential Manager - About App donation shortcut via Sociabuzz +## HyperBoostX Triple AI Engine + +The Triple AI Engine treats HyperBoost X as an AI PC Performance Doctor, not an extreme tweak tool. The required flow is: + +`Scan PC -> AI Analyzer -> AI Safety Guard -> AI Assistant -> User Approval -> Safe Tweak Engine -> Backup/Revert -> Performance Report` + +Core roles: + +- AI Assistant: explains scan results, bottlenecks, FPS-drop causes, DLSS/Reflex/V-Sync/Frame Generation guidance, and safe actions in user-friendly language. +- AI Analyzer: ranks structured findings from scan data, game/NVIDIA knowledge, Windows state, and the tweak database. +- AI Safety Guard: blocks unsafe tweaks, requires backup/restore paths, and prevents overclock, undervolt, Windows Security disable, BIOS/UEFI, voltage, irreversible registry edits, and guaranteed FPS claims. +- RAG/Knowledge Base: local grounding layer for tweak policy, game settings, NVIDIA settings, Windows errors, and benchmark notes. It is not presented as a fourth AI role. + +Cloud AI is optional. Basic scan, local rules, Safety Guard validation, and reports continue to work without an AI API key. + ## What changed in `1.2.0` - Added the first adaptive optimization foundation: system-drive classification, device profile detection, bottleneck hints, and more device-aware dashboard and Smart Recommendation messaging. - Clarified updater readiness states so the app can distinguish release-page-only, blocked, manual-ready, and auto-installable update paths. @@ -78,13 +99,13 @@ HyperBoost X is built from three main parts: ## What changed in `1.1.3` - Fixed Feature Audit so only incidents from the current audit run affect current results, reducing stale false failures -- Improved OpenAI Copilot diagnostics with clearer quota/auth messages, endpoint labeling, and request-id support when available +- Improved NVIDIA Copilot diagnostics with clearer quota/auth messages, endpoint labeling, and request-id support when available - Fixed app update notifications so already-updated builds no longer report a false newer version because of version label formatting - Synced installer, launcher, backend, and update metadata to `1.1.3` ## What changed in `1.1.2` -- Fixed HyperBoostX Copilot connectivity by making the OpenAI request path more resilient and improving response parsing/fallback behavior +- Fixed HyperBoostX Copilot connectivity by making the NVIDIA chat-completions request path more resilient and improving response parsing/fallback behavior - Added visible `Last Test` status for `Test AI Connection`, including persisted result across restart - Extended `Feature Audit` so real runtime feature errors are tracked more accurately without stale false failures sticking after recovery - Synced installer, launcher, backend, and update metadata to `1.1.2` @@ -116,11 +137,11 @@ HyperBoost X is built from three main parts: - Added persistent settings and shared app state across modules - Separated automation mode from policy profile - Upgraded Scheduled Automation from summary UI into real task and rule storage -- Added OpenAI-powered Copilot foundation with context-aware suggestions and safe action approval +- Added NVIDIA Copilot foundation with context-aware suggestions and safe action approval - Added Discord webhook error reporting with filtering and cooldown - Added modular localization foundation with `en-US` and `id-ID` packs - Added in-app app-update checking against the latest GitHub release -- Added automatic secret loading for OpenAI and Discord credentials with reinstall-safe Windows Credential Manager storage +- Added automatic secret loading for NVIDIA and Discord credentials with reinstall-safe Windows Credential Manager storage - Updated installer behavior so upgrades remove the previous app version first while preserving `%LocalAppData%\HyperBoost X\...` - Added About App donation shortcut for Sociabuzz support - Improved runtime safety around PowerShell execution, API failures, and activity logging @@ -183,15 +204,21 @@ Useful flags: - Portable app: `release\app\HyperBoostX.exe` - Installer: `HyperBoostXInstaller.exe` -- GitHub release: `v1.1.9` +- GitHub release target: `v1.2.12` ## Documentation - `API_REFERENCE.md` - API overview - `DIRECTORY_MAP.md` - current repo map +- `BUILD.md` - build commands and expected outputs +- `INSTALL.md` - portable, installer, uninstall, and config-preservation notes +- `SECURITY.md` - local API, credential, redaction, AI safety, and restore policy +- `USER_GUIDE.md` - dashboard, boost, restore, and NVIDIA Copilot usage +- `RELEASE.md` - release gates, checksum, installer, and GitHub release process +- `AUDIT_REPORT.md`, `BUGS_FOUND.md`, `BUGS_FIXED.md`, `QA_RESULTS.md`, `RELEASE_NOTES_NEXT.md` - current audit and release evidence ## Release status -`v1.1.9` is the current stable release. Ongoing work after this milestone remains focused on deeper cross-machine validation, admin-required flows, installer/update polish, and long-run UI/runtime hardening. +`v1.2.12` is an internal beta / RC line. Do not claim final public stable until the release checklist records passing restore, secret, backend-token, installer, and Windows lab evidence. diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..634f532 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,55 @@ +# HyperBoostX Release Process + +## Version Sync + +Before release, verify these locations agree: + +- `VERSION` +- WPF assembly metadata +- Launcher metadata +- Backend `Config.VERSION` +- Installer metadata +- README, CHANGELOG, release notes, and checksum files + +Run: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\verify_version_sync.ps1 +``` + +## Required Validation + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\verify_repo.ps1 +app\venv\Scripts\python.exe -m pytest +dotnet restore +dotnet build +dotnet build -c Release +dotnet test +``` + +Then run build scripts: + +```bat +build_backend.bat +build_release.bat +build_launcher.bat +package_release.bat +build_installer.bat +``` + +## Runtime QA + +- Portable launch +- Installed launch +- Backend health from packaged runtime +- App close with no orphan backend +- Installer uninstall/reinstall +- Feature Audit Full +- Full QA Matrix +- Restore/Undo +- NVIDIA Copilot connection using a key saved through Settings + +## GitHub Release + +Do not publish stable until automated validation and Windows lab evidence are attached. If installer is unsigned, auto-install should remain blocked while manual install may be allowed only when checksum is valid and the UI explains the unsigned state. diff --git a/RELEASE_NOTES_NEXT.md b/RELEASE_NOTES_NEXT.md new file mode 100644 index 0000000..c5cbf02 --- /dev/null +++ b/RELEASE_NOTES_NEXT.md @@ -0,0 +1,41 @@ +# HyperBoostX Next Release Notes + +## Summary + +This release candidate continues the HyperBoostX NVIDIA Copilot migration and safety audit. It focuses on truthful release readiness, restore metadata, and safe optimizer behavior. + +## Changed + +- Completed user-facing AI wording migration to HyperBoostX NVIDIA Copilot. +- Added owner docs for build, install, security, user guide, and release process. +- Added audit, bug, QA, and next-release reporting files. +- Added strict safety tests for NVIDIA Copilot model registry, secret redaction, Safety Guard blocking, and approval flow. + +## Fixed + +- Booster profile registry and power-plan actions now record restore metadata before mutation. +- Battery Saver display timeout command is now explicitly allowlisted with a narrow pattern. +- Stale AI provider wording and local machine paths were removed from docs. + +## Security + +- NVIDIA API key handling remains scoped to Windows Credential Manager. +- App-state serialization excludes NVIDIA API keys and Discord webhook URLs. +- AI plans require approval before non-scan actions. +- Safety Guard blocks unsafe actions such as Defender disablement, permanent Windows Update disablement, driver deletion, and arbitrary command execution. + +## Validation + +- Targeted Python safety tests: PASS. +- Targeted NVIDIA .NET tests: PASS. +- Full repository verification: PASS. +- Python test suite: PASS. +- .NET restore/build/test: PASS. +- Backend, WPF, launcher, package, and installer builds: PASS. +- Packaged backend health, portable launch, installed launch, and no-orphan smoke: PASS. + +## Remaining Risks + +- Real NVIDIA API connection requires owner key entered through Settings. +- Installer uninstall/reinstall requires Windows lab QA. +- Signed installer flow remains separate from unsigned manual install flow. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f656559 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,29 @@ +# HyperBoostX Security Guide + +## Local Backend + +The backend binds to `127.0.0.1` by default. Requests must include `X-HyperBoostX-Token`, and CORS is restricted to local hosts. + +## Command Execution + +System commands are allowlisted in `app\utils\shell.py`. Free-form PowerShell is blocked by default. Admin actions return a clear admin-required result when HyperBoostX is not elevated. + +## Secret Storage + +NVIDIA API keys and Discord webhook URLs are stored in Windows Credential Manager by `SecureSecretStoreService`. App config marks secret fields with `JsonIgnore`, and tests verify that plaintext API keys are not serialized into app-state. + +## Redaction + +NVIDIA tokens, bearer tokens, and Discord webhook-like values are redacted before user-facing error output or alert payloads. + +## AI Safety + +HyperBoostX NVIDIA Copilot creates action plans only. It must not execute system actions directly. Non-scan actions require user approval, and Safety Guard blocks or downgrades unsafe actions such as Defender disablement, permanent Windows Update disablement, driver deletion, arbitrary command execution, and registry/service edits without backup metadata. + +## Restore Requirements + +Registry edits, power plan changes, service startup changes, startup changes, and network changes must create restore metadata before mutation. Aggressive actions require explicit warning and admin context. + +## Known Limitations + +Real installer E2E, installed-app launch, and cross-device compatibility require a Windows lab matrix. A real NVIDIA API connection test requires an owner-provided key entered through the UI so the key remains in Windows Credential Manager and does not appear in terminal history. diff --git a/SHA256SUMS.txt b/SHA256SUMS.txt index 17503b9..8d9cb69 100644 --- a/SHA256SUMS.txt +++ b/SHA256SUMS.txt @@ -1,2 +1,4 @@ -619D66BC5BE427F513EFA4CDA7566D6FFEA95DED9ACBF889FEC9336FD1290D65 HyperBoostXInstaller.exe -D4CF4680E13A37E06E490A0B1CC7E2360A3285B068C69847B147161A7CCBD722 release-notes-v1.2.12.txt +c7b30d36c49f206ad6181130d7bcc8adee84624e5f09b5253775681a47800525 *HyperBoostXInstaller.exe +2de9647ae4236813e5b6a0371199cd4c0d75a5c8897d98ec1e396534d7fbc2a2 *release\backend\hyperboost_backend.exe +a910b1dd4831575039ce5c7d6e357fb47f73fa43d55af0aad550535f7826ba7b *release\launcher\HyperBoostLauncher.exe +a910b1dd4831575039ce5c7d6e357fb47f73fa43d55af0aad550535f7826ba7b *release\app\HyperBoostX.exe \ No newline at end of file diff --git a/STABLE_RELEASE_CHECKLIST.md b/STABLE_RELEASE_CHECKLIST.md index 3cf7c2d..d7a13a9 100644 --- a/STABLE_RELEASE_CHECKLIST.md +++ b/STABLE_RELEASE_CHECKLIST.md @@ -1,69 +1,56 @@ # Stable Release Checklist Target release: -- `HyperBoostX v1.1.0` - -Current source line: -- `v1.1.0-beta` - -Use this checklist after beta QA is complete and before flipping the project to stable metadata. - -## 1. Beta Exit Criteria - -- [ ] `QA_CHECKLIST.md` has been executed on at least one clean Windows machine. -- [ ] No high-severity installer, startup, or crash-loop issue remains open. -- [ ] No high-severity automation, AI, restore, or admin-required flow issue remains open. -- [ ] Discord reporting and logs show no repeating failure pattern. -- [ ] No blocking localization or layout issue remains in core navigation and settings. - -## 2. Version Flip - -- [ ] Update `wpf/HyperBoostX.csproj` from `1.1.0-beta` to `1.1.0`. -- [ ] Update `launcher/HyperBoostLauncher.csproj` from `1.1.0-beta` to `1.1.0`. -- [ ] Update backend version strings from `1.1.0-beta` to `1.1.0` in: - - `app/__init__.py` - - `app/core/config.py` - - `app/api/health.py` - - `app/dev_client.py` -- [ ] Update installer `DisplayVersion` from `1.1.0-beta` to `1.1.0`. -- [ ] Update About App text from `1.1.0 Beta` to `1.1.0`. -- [ ] Update release-checker metadata in `wpf/MainWindow.xaml.cs` and `wpf/Services/AppUpdateService.cs`. -- [ ] Optional: run `prepare_stable_release_final.ps1 -WhatIfOnly` first, then `prepare_stable_release_final.ps1` after QA sign-off. - -## 3. Documentation - -- [ ] Move stable highlights into `CHANGELOG.md`. -- [ ] Finalize `release-notes-v1.1.0.txt`. -- [ ] Update `README.md` from beta wording to stable wording. -- [ ] Remove or revise beta-only caution text that is no longer valid. - -## 4. Build And Packaging - -- [ ] Run clean WPF release build. -- [ ] Run launcher publish. -- [ ] Run backend PyInstaller build. -- [ ] Rebuild `release/package` and `release/app`. -- [ ] Rebuild `HyperBoostXInstaller.exe`. -- [ ] Recreate stable portable and package zip assets. - -## 5. Stable Verification - -- [ ] Install stable build on a clean machine. -- [ ] Upgrade from `v1.1.0-beta` to stable and verify runtime replacement. -- [ ] Verify launch, exit, uninstall, and reinstall behavior. -- [ ] Verify config migration from beta to stable. -- [ ] Verify no beta label remains in app UI, metadata, or installer info. - -## 6. GitHub Publish - -- [ ] Commit final stable metadata. -- [ ] Push `main`. -- [ ] Create tag `v1.1.0`. -- [ ] Publish GitHub Release `v1.1.0`. -- [ ] Upload installer, portable zip, and package zip. -- [ ] Verify release body and assets are correct. +- `HyperBoostX v1.2.12` + +Current release status: +- `Internal beta / RC` +- Public stable claim is blocked until every required gate below has dated evidence. + +## Required Evidence + +- [x] Python tests: `app\venv\Scripts\python.exe -m pytest`, 2026-06-25, Windows 10 10.0.26200 x64, `40 passed, 1 warning`. +- [x] WPF and launcher Release build: `dotnet build -c Release`, 2026-06-25, Windows 10 10.0.26200 x64, passed, `0 warnings, 0 errors`. +- [x] .NET tests: `dotnet test`, 2026-06-25, Windows 10 10.0.26200 x64, `20 passed`. +- [x] Repo verification: `scripts\verify_repo.ps1`, 2026-06-25, Windows 10 10.0.26200 x64, passed version sync, Python tests, and .NET desktop tests. +- [x] Backend build: `build_backend.bat`, 2026-06-25, passed, artifact `release\backend\hyperboost_backend.exe`. +- [x] Release/package build: `build_release.bat`, `build_launcher.bat`, `package_release.bat`, 2026-06-25, passed, artifact `release\app\HyperBoostX.exe`. +- [x] Installer build: `build_installer.bat`, 2026-06-25, passed, artifact `HyperBoostXInstaller.exe`. +- [ ] Installer E2E Windows lab: not executed in a separate Windows lab yet. +- [x] Installer hash: SHA256 `c7b30d36c49f206ad6181130d7bcc8adee84624e5f09b5253775681a47800525`, size `145236961` bytes, timestamp `2026-06-25 23:13:56`. +- [x] Packaged backend health: `release\backend\hyperboost_backend.exe`, 2026-06-25, `/api/health` returned HTTP 200 with local backend token. +- [x] Portable runtime launch: `release\app\HyperBoostX.exe`, 2026-06-25, launched WPF window and closed without backend/UI/launcher orphan. +- [x] Installed runtime launch: `C:\Program Files\HyperBoost X\HyperBoostX.exe`, 2026-06-25, launched WPF window and closed without backend/UI/launcher orphan. +- [x] No plaintext secret test: app-state serialization tests pass; local repo scan found no real NVIDIA API key. +- [x] Registry/power-plan revert metadata test: automated unit coverage added for booster profile registry and power-plan backups; full real-machine apply/revert matrix for every tweak is still pending. + +## Safety Gates + +- [x] High-risk tweaks require Expert Mode. +- [x] High-risk tweaks require Administrator privileges. +- [x] High-risk tweaks require double confirmation. +- [x] High-risk tweaks create a real registry restore backup before mutation. +- [x] One Click Boost does not apply `disable_defender` or `disable_updates`. +- [x] Process-kill flows show a preview before closing apps. +- [x] Backend local API rejects requests without `X-HyperBoostX-Token`. +- [x] Shell command execution is allowlisted and timeout-protected. +- [x] NVIDIA and Discord secrets are stored only in Windows Credential Manager. +- [x] Triple AI Engine runs Scan -> Analyzer -> Safety Guard -> Assistant -> Performance Report with local fallback when AI cloud is unavailable. +- [x] Triple AI Safety Guard blocks overclock, undervolt, Windows Security disable, permanent Windows Update disable, BIOS/UEFI, voltage, irreversible registry edits, and guaranteed FPS claims. +- [x] Triple AI Game Optimizer exposes safe manual NVIDIA/game setting recommendations without official NVIDIA partner/certified branding claims. + +## Compatibility Matrix + +- [ ] Windows 10, admin. +- [ ] Windows 10, non-admin. +- [ ] Windows 11, admin. +- [ ] Windows 11, non-admin. +- [ ] Laptop. +- [ ] Desktop. +- [ ] SSD system drive. +- [ ] HDD system drive. ## Result -- [ ] Ready to publish stable -- [ ] Hold stable release and continue beta fixes +- [ ] Ready to publish stable. +- [x] Hold stable release and continue beta / RC fixes. diff --git a/USER_GUIDE.md b/USER_GUIDE.md new file mode 100644 index 0000000..2e1deb7 --- /dev/null +++ b/USER_GUIDE.md @@ -0,0 +1,42 @@ +# HyperBoostX User Guide + +## Dashboard + +Use Dashboard for CPU, RAM, disk, network, device profile, bottleneck, recommendation, and last boost status. If a status is `Partial`, read the details before rerunning. + +## Safe Boost + +Use Safe Boost first. It focuses on safe cleanup, DNS refresh, cache cleanup, background-app preview, and reversible power/profile actions. + +## Restore And Undo + +Use Restore & Backup to review restore points, action/session metadata, and backup history. High-risk actions should show `Undo Available` or explain why undo is unavailable. + +## NVIDIA Copilot + +Open Settings / AI: + +1. Confirm provider is NVIDIA. +2. Paste the NVIDIA API key into the masked input. +3. Save API key. +4. Select default and fallback models. +5. Keep Auto Fallback, Safety Guard, and Require Approval enabled. +6. Run Test NVIDIA Connection. + +HyperBoostX NVIDIA Copilot produces a plan with actions, risk level, admin requirement, restore availability, expected result, skipped unsafe actions, and approval state. It does not run system actions until the user approves. + +## Model Selection + +Default model: + +- `nvidia/nemotron-3-nano-30b-a3b` + +Fallback model: + +- `nvidia/nvidia-nemotron-nano-9b-v2` + +Use heavier models only when troubleshooting needs deeper reasoning. + +## Safe Expectations + +HyperBoostX reports estimated and measured gains when available. It does not guarantee FPS increases or permanent Windows repair outcomes. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..f2ae0b4 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.2.12 diff --git a/app/api/hyperboostx.py b/app/api/hyperboostx.py new file mode 100644 index 0000000..202d6cc --- /dev/null +++ b/app/api/hyperboostx.py @@ -0,0 +1,262 @@ +"""HyperBoostX Triple AI Engine API contract.""" + +from flask import Blueprint, jsonify, request + +from api.middleware import handle_errors, log_requests +from core.config import Config +from core.logger import Logger +from core.restore import RestoreManager +from services.ai.pc_scanner_service import PcScannerService +from services.ai.triple_ai_engine import TripleAiEngine +from services.optimization.tweak_service import TweakService + + +logger = Logger.get_logger(__name__) + +hyperboostx_bp = Blueprint("hyperboostx", __name__) + +scanner_service = PcScannerService() +triple_ai_engine = TripleAiEngine() + + +def _json_payload() -> dict: + return request.get_json(silent=True) or {} + + +@hyperboostx_bp.route("/scan", methods=["POST"]) +@hyperboostx_bp.route("/api/hyperboostx/scan", methods=["POST"]) +@handle_errors +@log_requests +def scan_pc(): + """Run the MVP PC scanner.""" + result = scanner_service.scan_pc() + return jsonify(result) + + +@hyperboostx_bp.route("/ai/analyze", methods=["POST"]) +@hyperboostx_bp.route("/api/hyperboostx/ai/analyze", methods=["POST"]) +@handle_errors +@log_requests +def analyze_scan(): + """Analyze a scan result using AI Analyzer with local fallback.""" + data = _json_payload() + scan_id = data.get("scan_id") or "" + scan_result = data.get("scan_result") or scanner_service.load_scan(scan_id) + if not scan_result: + return jsonify({"error": "scan_result or valid scan_id is required"}), 400 + + result = triple_ai_engine.analyze_scan( + scan_id=scan_id or scan_result.get("scan_id", ""), + scan_result=scan_result, + user_goal=data.get("user_goal", "safe_boost"), + ) + return jsonify(result) + + +@hyperboostx_bp.route("/ai/safety-check", methods=["POST"]) +@hyperboostx_bp.route("/api/hyperboostx/ai/safety-check", methods=["POST"]) +@handle_errors +@log_requests +def safety_check(): + """Validate recommendations before any tweak can be applied.""" + data = _json_payload() + recommendations = data.get("recommendations") or [] + result = triple_ai_engine.safety_check(recommendations) + return jsonify(result) + + +@hyperboostx_bp.route("/doctor/run", methods=["POST"]) +@hyperboostx_bp.route("/api/hyperboostx/doctor/run", methods=["POST"]) +@handle_errors +@log_requests +def run_doctor_flow(): + """Run scan -> analyze -> safety -> assistant -> report without applying tweaks.""" + data = _json_payload() + user_goal = data.get("user_goal", "safe_boost") + scan_result = scanner_service.scan_pc() + analysis = triple_ai_engine.analyze_scan(scan_result["scan_id"], scan_result, user_goal) + safety = triple_ai_engine.safety_check(analysis.get("recommendations", [])) + assistant = triple_ai_engine.assistant_summary(scan_result, analysis, safety) + report = triple_ai_engine.create_report(scan_result, analysis, safety) + return jsonify( + { + "scan_id": scan_result["scan_id"], + "scan_result": scan_result, + "analysis": analysis, + "safety": safety, + "assistant": assistant, + "report": report, + "flow": "Scan PC -> AI Analyzer -> AI Safety Guard -> AI Assistant -> User Approval", + } + ) + + +@hyperboostx_bp.route("/tweaks/apply", methods=["POST"]) +@hyperboostx_bp.route("/api/hyperboostx/tweaks/apply", methods=["POST"]) +@handle_errors +@log_requests +def apply_approved_tweaks(): + """Apply tweaks that already passed Safety Guard and user approval.""" + data = _json_payload() + approved_tweaks = data.get("approved_tweaks") or [] + if not data.get("user_approved"): + logger.info("user approval missing for safe tweak apply") + return jsonify({"applied": [], "failed": [], "backup_id": "", "error": "user_approved must be true"}), 400 + + second_pass = triple_ai_engine.safety_check(approved_tweaks) + allowed = [ + item + for item in second_pass.get("approved", []) + if item.get("can_auto_apply") and item.get("safety_status") == "approved" + ] + + if not allowed: + return jsonify( + { + "applied": [], + "failed": [], + "backup_id": "", + "safety": second_pass, + "error": "No approved auto-apply tweaks are available.", + } + ), 400 + + batch = RestoreManager.create_restore_point("safe_boost_batch", "HyperBoostX Safe Boost batch manifest") + applied = [] + failed = [] + stop_after_failure = False + + for item in allowed: + tweak_id = item.get("tweak_id") or item.get("id") + if not tweak_id: + failed.append({"tweak_id": "", "error": "Missing tweak_id"}) + continue + if stop_after_failure: + failed.append({"tweak_id": tweak_id, "error": "Skipped after previous failure."}) + continue + + logger.info("user approval received for tweak: %s", tweak_id) + result = TweakService.apply_tweak(tweak_id, confirmed=True) + record = {"tweak_id": tweak_id, "result": result} + if result.get("success"): + applied.append(record) + batch.settings.append( + { + "type": "safe_boost_tweak", + "tweak_id": tweak_id, + "restore_point": result.get("restore_point", f"tweak_{tweak_id}"), + "restore_timestamp": result.get("restore_timestamp", ""), + } + ) + RestoreManager.save_restore_point(batch) + logger.info("tweak applied: %s", tweak_id) + else: + failed.append(record) + logger.warning("tweak failed: %s", tweak_id) + if item.get("risk_level") in {"medium", "high"}: + stop_after_failure = True + + response = { + "applied": applied, + "failed": failed, + "backup_id": batch.timestamp if applied else "", + "safety": second_pass, + } + return jsonify(response) + + +@hyperboostx_bp.route("/tweaks/revert", methods=["POST"]) +@hyperboostx_bp.route("/api/hyperboostx/tweaks/revert", methods=["POST"]) +@handle_errors +@log_requests +def revert_tweaks(): + """Revert one or more tweaks from a Safe Boost backup batch.""" + data = _json_payload() + backup_id = data.get("backup_id", "") + tweak_ids = set(data.get("tweak_ids") or []) + if not backup_id: + return jsonify({"reverted": [], "failed": [{"error": "backup_id is required"}]}), 400 + + batch = RestoreManager.find_restore_point_by_timestamp(backup_id) + if not batch: + return jsonify({"reverted": [], "failed": [{"error": f"Backup not found: {backup_id}"}]}), 404 + + reverted = [] + failed = [] + for item in batch.settings: + if item.get("type") != "safe_boost_tweak": + continue + tweak_id = item.get("tweak_id", "") + if tweak_ids and tweak_id not in tweak_ids: + continue + + point = None + timestamp = item.get("restore_timestamp") + if timestamp: + point = RestoreManager.find_restore_point_by_timestamp(timestamp) + if point is None: + point = RestoreManager.find_latest_restore_point(f"tweak_{tweak_id}") + + if point and RestoreManager.restore(point): + reverted.append({"tweak_id": tweak_id, "restore_timestamp": point.timestamp}) + logger.info("revert completed: %s", tweak_id) + else: + failed.append({"tweak_id": tweak_id, "error": "Restore point missing or failed."}) + logger.warning("revert failed: %s", tweak_id) + + return jsonify({"reverted": reverted, "failed": failed, "backup_id": backup_id}) + + +@hyperboostx_bp.route("/game/optimize", methods=["POST"]) +@hyperboostx_bp.route("/api/hyperboostx/game/optimize", methods=["POST"]) +@handle_errors +@log_requests +def optimize_game(): + """Return a safe game optimization recommendation.""" + data = _json_payload() + game_name = data.get("game_name") or data.get("game") or "" + scan_id = data.get("scan_id") or "" + scan_result = data.get("scan_result") or scanner_service.load_scan(scan_id) + result = triple_ai_engine.optimize_game(game_name, scan_result) + return jsonify(result) + + +@hyperboostx_bp.route("/reports/", methods=["GET"]) +@hyperboostx_bp.route("/api/hyperboostx/reports/", methods=["GET"]) +@handle_errors +def get_report(report_id: str): + report = triple_ai_engine.load_report(report_id) + if not report: + return jsonify({"error": "Report not found"}), 404 + return jsonify(report) + + +@hyperboostx_bp.route("/config/ai", methods=["GET"]) +@hyperboostx_bp.route("/api/hyperboostx/config/ai", methods=["GET"]) +@handle_errors +def get_ai_config(): + return jsonify( + { + "provider": os_value("AI_PROVIDER", Config.AI_PROVIDER), + "base_url": os_value("NVIDIA_BASE_URL", Config.NVIDIA_BASE_URL), + "chat_endpoint": os_value("NVIDIA_CHAT_ENDPOINT", Config.NVIDIA_CHAT_ENDPOINT), + "cloud_enabled": str(Config.get("ai_cloud_enabled", os_value("AI_CLOUD_ENABLED", "true"))).lower() in {"1", "true", "yes", "on"}, + "models": Config.NVIDIA_MODELS, + "default_model": os_value("NVIDIA_DEFAULT_MODEL", Config.NVIDIA_DEFAULT_MODEL), + "fallback_model": os_value("NVIDIA_FALLBACK_MODEL", Config.NVIDIA_FALLBACK_MODEL), + "assistant_model": os_value("AI_ASSISTANT_MODEL", triple_ai_engine.ASSISTANT_MODEL), + "analyzer_model": os_value("AI_ANALYZER_MODEL", triple_ai_engine.ANALYZER_MODEL), + "safety_model": os_value("AI_SAFETY_MODEL", triple_ai_engine.SAFETY_MODEL), + "embed_model": os_value("AI_EMBED_MODEL", triple_ai_engine.EMBED_MODEL), + "auto_fallback": str(Config.get("ai_model_auto_fallback", os_value("AI_MODEL_AUTO_FALLBACK", "true"))).lower() in {"1", "true", "yes", "on"}, + "require_action_approval": str(Config.get("ai_require_action_approval", os_value("AI_REQUIRE_ACTION_APPROVAL", "true"))).lower() in {"1", "true", "yes", "on"}, + "safety_guard": str(Config.get("ai_enable_safety_guard", os_value("AI_ENABLE_SAFETY_GUARD", "true"))).lower() in {"1", "true", "yes", "on"}, + "api_key_present": bool(os_value("NVIDIA_API_KEY", "")), + } + ) + + +def os_value(key: str, default: str) -> str: + import os + + return os.environ.get(key, default) diff --git a/app/api/triple_ai.py b/app/api/triple_ai.py new file mode 100644 index 0000000..6e9edec --- /dev/null +++ b/app/api/triple_ai.py @@ -0,0 +1,183 @@ +"""Triple AI Engine API contract for HyperBoostX.""" + +from flask import Blueprint, jsonify, request + +from api.middleware import handle_errors, log_requests +from core.config import Config +from core.logger import Logger +from services.ai.triple_ai_engine import TripleAIEngine + + +logger = Logger.get_logger(__name__) +triple_ai_bp = Blueprint("triple_ai", __name__) +triple_ai_engine = TripleAIEngine() + + +@triple_ai_bp.route("/scan", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/scan", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/scan", methods=["POST"]) +@handle_errors +@log_requests +def scan_pc(): + """Run Scan My PC and return the MVP scan contract.""" + return jsonify(triple_ai_engine.scan_pc()) + + +@triple_ai_bp.route("/ai/analyze", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/analyze", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/ai/analyze", methods=["POST"]) +@handle_errors +@log_requests +def analyze_scan(): + """Run AI Analyzer over a scan result.""" + data = request.get_json(silent=True) or {} + scan_result = data.get("scan_result") or {} + if not scan_result: + return jsonify({"error": "scan_result is required"}), 400 + + result = triple_ai_engine.analyze( + scan_result, + user_goal=data.get("user_goal") or "gaming", + game=data.get("game") or "", + ) + return jsonify(result) + + +@triple_ai_bp.route("/ai/safety-check", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/safety-check", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/ai/safety-check", methods=["POST"]) +@handle_errors +@log_requests +def safety_check(): + """Run AI Safety Guard on recommendations.""" + data = request.get_json(silent=True) or {} + recommendations = data.get("recommendations") or [] + if not isinstance(recommendations, list): + return jsonify({"error": "recommendations must be a list"}), 400 + + return jsonify(triple_ai_engine.safety_check(recommendations)) + + +@triple_ai_bp.route("/ai/assistant", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/assistant", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/ai/assistant", methods=["POST"]) +@handle_errors +@log_requests +def assistant_response(): + """Return user-facing Assistant output grounded in Analyzer + Safety Guard.""" + data = request.get_json(silent=True) or {} + return jsonify(triple_ai_engine.assistant_response( + data.get("scan_result") or {}, + data.get("analysis_result") or {}, + data.get("safety_result") or {}, + )) + + +@triple_ai_bp.route("/api/triple-ai/full-flow", methods=["POST"]) +@triple_ai_bp.route("/doctor/run", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/doctor/run", methods=["POST"]) +@handle_errors +@log_requests +def full_flow(): + """Convenience endpoint for Scan -> Analyze -> Safety -> Assistant -> Report.""" + data = request.get_json(silent=True) or {} + return jsonify(triple_ai_engine.run_full_flow( + user_goal=data.get("user_goal") or "gaming", + game=data.get("game") or "", + )) + + +@triple_ai_bp.route("/tweaks/apply", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/tweaks/apply", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/tweaks/apply", methods=["POST"]) +@handle_errors +@log_requests +def apply_safe_tweaks(): + """Apply only approved, reversible, low-risk tweaks after user approval.""" + data = request.get_json(silent=True) or {} + approved_tweaks = data.get("approved_tweaks") or [] + if not isinstance(approved_tweaks, list): + return jsonify({"error": "approved_tweaks must be a list"}), 400 + + result = triple_ai_engine.apply_safe_tweaks( + approved_tweaks, + user_approved=bool(data.get("user_approved")), + ) + return jsonify(result) + + +@triple_ai_bp.route("/tweaks/revert", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/tweaks/revert", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/tweaks/revert", methods=["POST"]) +@handle_errors +@log_requests +def revert_tweaks(): + """Revert applied tweaks by backup_id and/or tweak_ids.""" + data = request.get_json(silent=True) or {} + tweak_ids = data.get("tweak_ids") + if tweak_ids is not None and not isinstance(tweak_ids, list): + return jsonify({"error": "tweak_ids must be a list"}), 400 + + return jsonify(triple_ai_engine.revert_tweaks( + backup_id=data.get("backup_id") or "", + tweak_ids=tweak_ids, + )) + + +@triple_ai_bp.route("/performance/report", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/performance/report", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/performance/report", methods=["POST"]) +@handle_errors +@log_requests +def performance_report(): + """Create a performance report from scan/analyze/safety payloads.""" + data = request.get_json(silent=True) or {} + return jsonify(triple_ai_engine.create_performance_report( + data.get("scan_result") or {}, + data.get("analysis_result") or {}, + data.get("safety_result") or {}, + data.get("assistant_result") or {}, + )) + + +@triple_ai_bp.route("/game/optimize", methods=["POST"]) +@triple_ai_bp.route("/api/triple-ai/game/optimize", methods=["POST"]) +@triple_ai_bp.route("/api/hyperboostx/game/optimize", methods=["POST"]) +@handle_errors +@log_requests +def optimize_game(): + """Return safe game settings recommendations from the local knowledge base.""" + data = request.get_json(silent=True) or {} + game = data.get("game_name") or data.get("game") or "" + if not game: + return jsonify({"error": "game is required"}), 400 + + return jsonify(triple_ai_engine.optimize_game( + game, + data.get("scan_result") or {}, + )) + + +@triple_ai_bp.route("/api/triple-ai/models", methods=["GET"]) +@triple_ai_bp.route("/api/hyperboostx/models", methods=["GET"]) +@handle_errors +def models(): + """Return configured model targets and local fallback state.""" + return jsonify({ + "engine": "HyperBoostX Triple AI Engine", + "provider": Config.AI_PROVIDER, + "base_url": Config.NVIDIA_BASE_URL, + "chat_endpoint": Config.NVIDIA_CHAT_ENDPOINT, + "models": Config.NVIDIA_MODELS, + "default_model": Config.NVIDIA_DEFAULT_MODEL, + "fallback_model": Config.NVIDIA_FALLBACK_MODEL, + "assistant_model": TripleAIEngine.ASSISTANT_MODEL, + "analyzer_model": TripleAIEngine.ANALYZER_MODEL, + "safety_model": TripleAIEngine.SAFETY_MODEL, + "embed_model": TripleAIEngine.EMBED_MODEL, + "auto_fallback": Config.AI_MODEL_AUTO_FALLBACK, + "require_action_approval": Config.AI_REQUIRE_ACTION_APPROVAL, + "safety_guard": Config.AI_ENABLE_SAFETY_GUARD, + "cloud_enabled": triple_ai_engine._cloud_enabled(), + "rag_layer": "local knowledge base", + }) diff --git a/app/api/tweaks.py b/app/api/tweaks.py index d943b4a..f02845b 100644 --- a/app/api/tweaks.py +++ b/app/api/tweaks.py @@ -35,7 +35,11 @@ def apply_tweak(): """Apply a specific system tweak.""" data = request.get_json() tweak_id = data['tweak_id'] - result = tweak_service.apply_tweak(tweak_id) + result = tweak_service.apply_tweak( + tweak_id, + expert_mode=bool(data.get("expert_mode")), + confirmed=bool(data.get("confirmed")), + ) return jsonify(result) @@ -62,4 +66,4 @@ def get_tweak_info(tweak_id): return jsonify({"error": "Tweak not found"}), 404 except Exception as e: logger.error(f"Error in /api/tweaks/info/{tweak_id}: {e}") - return jsonify({"error": str(e)}), 500 \ No newline at end of file + return jsonify({"error": str(e)}), 500 diff --git a/app/backend_server.py b/app/backend_server.py index e7a1000..e8b9ca5 100644 --- a/app/backend_server.py +++ b/app/backend_server.py @@ -5,10 +5,13 @@ """ import json +import hmac +import os +import secrets import threading from typing import Dict, Any from urllib.parse import urlparse -from flask import Flask, request +from flask import Flask, request, jsonify from core.config import Config from core.logger import Logger @@ -23,6 +26,7 @@ from api.network import network_bp from api.startup import startup_bp from api.websocket import ws_bp +from api.triple_ai import triple_ai_bp from api.middleware import APIMiddleware Config.initialize() @@ -35,11 +39,12 @@ class HyperBoostBackendServer: """Backend API server for HyperBoost X with blueprint architecture.""" - def __init__(self, host: str = "127.0.0.1", port: int = 5000): + def __init__(self, host: str = "127.0.0.1", port: int = 5000, auth_token: str | None = None): self.app = Flask(__name__) self.host = host self.port = port self.running = False + self.auth_token = auth_token or os.environ.get("HYPERBOOSTX_BACKEND_TOKEN", "").strip() or secrets.token_urlsafe(32) # Initialize logger self.logger = Logger.get_logger(__name__) @@ -59,6 +64,18 @@ def _configure_app(self): # Initialize middleware APIMiddleware.init_app(self.app) + + @self.app.before_request + def require_backend_token(): + if request.method == "OPTIONS": + return None + + supplied_token = request.headers.get("X-HyperBoostX-Token", "") + if not supplied_token or not hmac.compare_digest(supplied_token, self.auth_token): + logger.warning("Rejected local API request without a valid HyperBoostX token: %s %s", request.method, request.path) + return jsonify({"error": "Unauthorized"}), 401 + + return None # Add CORS headers for cross-origin requests (useful for web clients) @self.app.after_request @@ -68,7 +85,7 @@ def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = origin response.headers['Vary'] = 'Origin' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS' - response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' + response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-HyperBoostX-Token' return response @staticmethod @@ -90,6 +107,7 @@ def _register_blueprints(self): self.app.register_blueprint(repair_bp) self.app.register_blueprint(network_bp) self.app.register_blueprint(startup_bp) + self.app.register_blueprint(triple_ai_bp) self.app.register_blueprint(ws_bp) self.logger.info("API blueprints registered successfully") diff --git a/app/core/config.py b/app/core/config.py index 76c8b96..32e5f14 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -9,11 +9,52 @@ from typing import Any, Dict, Optional +def _env_bool(name: str, default: bool) -> bool: + value = os.environ.get(name) + if value is None: + return default + return value.strip().lower() in {"1", "true", "yes", "on"} + + +def _env_int(name: str, default: int) -> int: + try: + return int(os.environ.get(name, str(default)) or default) + except Exception: + return default + + class Config: """Application configuration handler.""" APP_NAME = "HyperBoost X" VERSION = "1.2.12" + AI_PROVIDER = os.environ.get("AI_PROVIDER", "nvidia") + NVIDIA_BASE_URL = os.environ.get("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com/v1") + NVIDIA_CHAT_ENDPOINT = os.environ.get("NVIDIA_CHAT_ENDPOINT", "/chat/completions") + NVIDIA_DEFAULT_MODEL = os.environ.get("NVIDIA_DEFAULT_MODEL", "nvidia/nemotron-3-nano-30b-a3b") + NVIDIA_FALLBACK_MODEL = os.environ.get("NVIDIA_FALLBACK_MODEL", "nvidia/nvidia-nemotron-nano-9b-v2") + AI_ASSISTANT_MODEL = os.environ.get("AI_ASSISTANT_MODEL", NVIDIA_DEFAULT_MODEL) + AI_ANALYZER_MODEL = os.environ.get("AI_ANALYZER_MODEL", "nvidia/llama-3.3-nemotron-super-49b-v1.5") + AI_SAFETY_MODEL = os.environ.get("AI_SAFETY_MODEL", "nvidia/nemotron-content-safety-reasoning-4b") + AI_EMBED_MODEL = os.environ.get("AI_EMBED_MODEL", "nvidia/llama-nemotron-embed-1b-v2") + AI_CLOUD_ENABLED = _env_bool("AI_CLOUD_ENABLED", False) + AI_MODEL_AUTO_FALLBACK = _env_bool("AI_MODEL_AUTO_FALLBACK", True) + AI_REQUIRE_ACTION_APPROVAL = _env_bool("AI_REQUIRE_ACTION_APPROVAL", True) + AI_ENABLE_SAFETY_GUARD = _env_bool("AI_ENABLE_SAFETY_GUARD", True) + AI_TIMEOUT_MS = _env_int("AI_TIMEOUT_MS", 30000) + AI_MAX_RETRIES = _env_int("AI_MAX_RETRIES", 2) + NVIDIA_MODELS = [ + {"id": "nvidia/nemotron-3-nano-30b-a3b", "label": "Fast Default", "purpose": "chat cepat, default, rekomendasi ringan"}, + {"id": "nvidia/llama-3.3-nemotron-super-49b-v1.5", "label": "Smart Balanced", "purpose": "analisis PC harian"}, + {"id": "nvidia/nemotron-3-super-120b-a12b", "label": "Deep Analyzer", "purpose": "bottleneck dan troubleshooting lebih dalam"}, + {"id": "nvidia/nemotron-3-ultra-550b-a55b", "label": "Max Reasoning", "purpose": "reasoning berat dan masalah kompleks"}, + {"id": "nvidia/llama-3.1-nemotron-ultra-253b-v1", "label": "Legacy Ultra", "purpose": "fallback reasoning kuat"}, + {"id": "nvidia/nvidia-nemotron-nano-9b-v2", "label": "Nano Lite", "purpose": "fallback cepat dan ringan"}, + {"id": "nvidia/nemotron-mini-4b-instruct", "label": "Mini Fast", "purpose": "respons cepat/simple"}, + {"id": "nvidia/nemotron-content-safety-reasoning-4b", "label": "Safety Reasoning", "purpose": "validasi aksi berisiko"}, + {"id": "nvidia/llama-3.1-nemoguard-8b-content-safety", "label": "Content Guard", "purpose": "blok rekomendasi tidak aman"}, + {"id": "nvidia/llama-3.1-nemoguard-8b-topic-control", "label": "Topic Guard", "purpose": "jaga AI tetap fokus ke HyperBoostX, optimasi PC, repair, gaming, monitoring"}, + ] # Default paths APP_DIR = Path(os.environ.get("LOCALAPPDATA", str(Path.home()))) / "HyperBoost X" @@ -30,6 +71,21 @@ class Config: "check_updates": True, "startup_minimized": False, "auto_optimize_interval": 3600, # 1 hour + "ai_provider": AI_PROVIDER, + "ai_cloud_enabled": AI_CLOUD_ENABLED, + "ai_assistant_model": AI_ASSISTANT_MODEL, + "ai_analyzer_model": AI_ANALYZER_MODEL, + "ai_safety_model": AI_SAFETY_MODEL, + "ai_embed_model": AI_EMBED_MODEL, + "ai_model_auto_fallback": AI_MODEL_AUTO_FALLBACK, + "ai_require_action_approval": AI_REQUIRE_ACTION_APPROVAL, + "ai_enable_safety_guard": AI_ENABLE_SAFETY_GUARD, + "nvidia_default_model": NVIDIA_DEFAULT_MODEL, + "nvidia_fallback_model": NVIDIA_FALLBACK_MODEL, + "ai_timeout_ms": AI_TIMEOUT_MS, + "ai_max_retries": AI_MAX_RETRIES, + "nvidia_base_url": NVIDIA_BASE_URL, + "nvidia_chat_endpoint": NVIDIA_CHAT_ENDPOINT, } _config: Dict[str, Any] = {} diff --git a/app/core/restore.py b/app/core/restore.py index fa6b372..0f36e88 100644 --- a/app/core/restore.py +++ b/app/core/restore.py @@ -5,9 +5,12 @@ import shutil import json +import re +import subprocess +import winreg from pathlib import Path from datetime import datetime -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from core.config import Config from core.logger import Logger @@ -21,8 +24,10 @@ class RestorePoint: def __init__(self, name: str, description: str = "", timestamp: Optional[str] = None): self.name = name self.description = description - self.timestamp = timestamp or datetime.now().isoformat() + self.timestamp = timestamp or datetime.now().strftime("%Y%m%d-%H%M%S-%f") self.files: Dict[str, str] = {} # path -> backup_path + self.registry: List[Dict[str, Any]] = [] + self.settings: List[Dict[str, Any]] = [] def to_dict(self) -> dict: """Convert to dictionary.""" @@ -30,7 +35,9 @@ def to_dict(self) -> dict: "name": self.name, "description": self.description, "timestamp": self.timestamp, - "files": self.files + "files": self.files, + "registry": self.registry, + "settings": self.settings, } @classmethod @@ -38,11 +45,34 @@ def from_dict(cls, data: dict): """Create from dictionary.""" rp = cls(data["name"], data["description"], data["timestamp"]) rp.files = data.get("files", {}) + rp.registry = data.get("registry", []) + rp.settings = data.get("settings", []) return rp class RestoreManager: """Manages system restore points and restoration.""" + + _HKEY_NAMES = { + winreg.HKEY_LOCAL_MACHINE: "HKEY_LOCAL_MACHINE", + winreg.HKEY_CURRENT_USER: "HKEY_CURRENT_USER", + winreg.HKEY_CLASSES_ROOT: "HKEY_CLASSES_ROOT", + winreg.HKEY_USERS: "HKEY_USERS", + winreg.HKEY_CURRENT_CONFIG: "HKEY_CURRENT_CONFIG", + } + + _HKEY_BY_NAME = {value: key for key, value in _HKEY_NAMES.items()} + + _REG_TYPE_NAMES = { + winreg.REG_SZ: "REG_SZ", + winreg.REG_EXPAND_SZ: "REG_EXPAND_SZ", + winreg.REG_BINARY: "REG_BINARY", + winreg.REG_DWORD: "REG_DWORD", + winreg.REG_MULTI_SZ: "REG_MULTI_SZ", + winreg.REG_QWORD: "REG_QWORD", + } + + _REG_TYPE_BY_NAME = {value: key for key, value in _REG_TYPE_NAMES.items()} @staticmethod def create_restore_point(name: str, description: str = "") -> RestorePoint: @@ -50,6 +80,85 @@ def create_restore_point(name: str, description: str = "") -> RestorePoint: point = RestorePoint(name, description) logger.info(f"Created restore point: {name}") return point + + @staticmethod + def _restore_point_dir(restore_point: RestorePoint) -> Path: + return Config.BACKUP_DIR / restore_point.timestamp + + @staticmethod + def _restore_point_manifest_path(restore_point: RestorePoint) -> Path: + return RestoreManager._restore_point_dir(restore_point) / "restore-point.json" + + @staticmethod + def _registry_backup_path(restore_point: RestorePoint) -> Path: + return RestoreManager._restore_point_dir(restore_point) / "registry-backup.json" + + @staticmethod + def _settings_backup_path(restore_point: RestorePoint) -> Path: + return RestoreManager._restore_point_dir(restore_point) / "settings-backup.json" + + @staticmethod + def save_restore_point(restore_point: RestorePoint) -> bool: + try: + restore_dir = RestoreManager._restore_point_dir(restore_point) + restore_dir.mkdir(parents=True, exist_ok=True) + RestoreManager._restore_point_manifest_path(restore_point).write_text( + json.dumps(restore_point.to_dict(), indent=2), + encoding="utf-8", + ) + if restore_point.registry: + RestoreManager._registry_backup_path(restore_point).write_text( + json.dumps(restore_point.registry, indent=2), + encoding="utf-8", + ) + if restore_point.settings: + RestoreManager._settings_backup_path(restore_point).write_text( + json.dumps(restore_point.settings, indent=2), + encoding="utf-8", + ) + return True + except Exception as e: + logger.error(f"Failed to save restore point {restore_point.name}: {e}") + return False + + @staticmethod + def backup_power_plan(restore_point: RestorePoint, new_scheme_guid: str) -> bool: + """Backup the active Windows power plan before switching plans.""" + try: + old_guid, old_name = RestoreManager._get_active_power_plan() + if not old_guid: + logger.error("Failed to backup power plan because active scheme could not be read.") + return False + + restore_point.settings.append({ + "type": "power_plan", + "old_scheme_guid": old_guid, + "old_scheme_name": old_name, + "new_scheme_guid": new_scheme_guid, + "timestamp": datetime.now().isoformat(timespec="seconds"), + }) + RestoreManager.save_restore_point(restore_point) + logger.info("Backed up active power plan: %s", old_name or old_guid) + return True + except Exception as e: + logger.error("Failed to backup power plan: %s", e) + return False + + @staticmethod + def _get_active_power_plan() -> tuple[str, str]: + try: + output = subprocess.check_output( + ["powercfg", "/getactivescheme"], + text=True, + stderr=subprocess.DEVNULL, + timeout=3, + ) + match = re.search(r"([0-9a-fA-F-]{36})(?:\s+\((.*?)\))?", output or "") + if not match: + return "", "" + return match.group(1), match.group(2) or "" + except Exception: + return "", "" @staticmethod def backup_file(source: Path, restore_point: RestorePoint) -> bool: @@ -59,6 +168,7 @@ def backup_file(source: Path, restore_point: RestorePoint) -> bool: backup_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source, backup_path) restore_point.files[str(source)] = str(backup_path) + RestoreManager.save_restore_point(restore_point) logger.info(f"Backed up file: {source}") return True except Exception as e: @@ -66,27 +176,203 @@ def backup_file(source: Path, restore_point: RestorePoint) -> bool: return False @staticmethod - def backup_registry(restore_point: RestorePoint, key: str) -> bool: - """Backup a registry key.""" + def backup_registry( + restore_point: RestorePoint, + hkey, + path: str, + key: str, + new_value: Any, + new_value_type=winreg.REG_SZ, + ) -> bool: + """Backup a registry value before it is changed.""" try: - logger.info(f"Backed up registry key: {key}") + old_value_exists, old_value, old_value_type = RestoreManager._read_registry_value(hkey, path, key) + entry = { + "hive": RestoreManager._HKEY_NAMES.get(hkey, str(hkey)), + "path": path, + "key": key, + "type": RestoreManager._REG_TYPE_NAMES.get( + old_value_type if old_value_exists else new_value_type, + str(old_value_type if old_value_exists else new_value_type), + ), + "old_value_exists": old_value_exists, + "old_value": RestoreManager._serialize_registry_value(old_value), + "old_type": RestoreManager._REG_TYPE_NAMES.get(old_value_type, str(old_value_type)) if old_value_exists else None, + "new_value": RestoreManager._serialize_registry_value(new_value), + "new_type": RestoreManager._REG_TYPE_NAMES.get(new_value_type, str(new_value_type)), + "timestamp": datetime.now().isoformat(timespec="seconds"), + } + restore_point.registry.append(entry) + RestoreManager.save_restore_point(restore_point) + logger.info( + "Backed up registry value %s at %s\\%s", + key, + entry["hive"], + path, + ) return True except Exception as e: - logger.error(f"Failed to backup registry key: {e}") + logger.error(f"Failed to backup registry value {key}: {e}") return False + + @staticmethod + def _read_registry_value(hkey, path: str, key: str) -> tuple[bool, Any, Optional[int]]: + try: + reg_key = winreg.OpenKey(hkey, path, 0, winreg.KEY_READ) + try: + value, value_type = winreg.QueryValueEx(reg_key, key) + return True, value, value_type + finally: + winreg.CloseKey(reg_key) + except FileNotFoundError: + return False, None, None + except OSError: + return False, None, None + + @staticmethod + def _serialize_registry_value(value: Any) -> Any: + if isinstance(value, bytes): + return {"encoding": "hex", "data": value.hex()} + return value + + @staticmethod + def _deserialize_registry_value(value: Any) -> Any: + if isinstance(value, dict) and value.get("encoding") == "hex": + return bytes.fromhex(value.get("data", "")) + return value + + @staticmethod + def _resolve_hkey(hive: str): + return RestoreManager._HKEY_BY_NAME[hive] + + @staticmethod + def _resolve_reg_type(type_name: Optional[str]) -> int: + if not type_name: + return winreg.REG_SZ + return RestoreManager._REG_TYPE_BY_NAME.get(type_name, winreg.REG_SZ) + + @staticmethod + def _restore_registry_entry(entry: Dict[str, Any]) -> bool: + hive = entry.get("hive", "") + path = entry.get("path", "") + key = entry.get("key", "") + if not hive or not path or not key: + return False + + hkey = RestoreManager._resolve_hkey(hive) + if not entry.get("old_value_exists", False): + try: + reg_key = winreg.OpenKey(hkey, path, 0, winreg.KEY_SET_VALUE) + try: + winreg.DeleteValue(reg_key, key) + finally: + winreg.CloseKey(reg_key) + logger.info("Deleted registry value created by tweak: %s at %s\\%s", key, hive, path) + return True + except FileNotFoundError: + return True + except OSError as e: + logger.error("Failed to delete registry value %s at %s\\%s: %s", key, hive, path, e) + return False + + try: + value = RestoreManager._deserialize_registry_value(entry.get("old_value")) + value_type = RestoreManager._resolve_reg_type(entry.get("old_type") or entry.get("type")) + reg_key = winreg.CreateKeyEx(hkey, path, 0, winreg.KEY_WRITE) + try: + winreg.SetValueEx(reg_key, key, 0, value_type, value) + finally: + winreg.CloseKey(reg_key) + logger.info("Restored registry value %s at %s\\%s", key, hive, path) + return True + except Exception as e: + logger.error("Failed to restore registry value %s at %s\\%s: %s", key, hive, path, e) + return False + + @staticmethod + def _restore_setting_entry(entry: Dict[str, Any]) -> bool: + setting_type = entry.get("type", "") + if setting_type != "power_plan": + return True + + old_scheme_guid = entry.get("old_scheme_guid", "") + if not old_scheme_guid: + return False + + try: + from utils.shell import ShellUtil + success, output = ShellUtil.execute_command(f"powercfg /setactive {old_scheme_guid}", admin=True) + if not success: + logger.error("Failed to restore power plan: %s", output) + return False + logger.info("Restored power plan: %s", entry.get("old_scheme_name") or old_scheme_guid) + return True + except Exception as e: + logger.error("Failed to restore power plan: %s", e) + return False + + @staticmethod + def find_latest_restore_point(name: str) -> Optional[RestorePoint]: + try: + if not Config.BACKUP_DIR.exists(): + return None + + candidates: List[RestorePoint] = [] + for manifest in Config.BACKUP_DIR.glob("*/restore-point.json"): + try: + point = RestorePoint.from_dict(json.loads(manifest.read_text(encoding="utf-8"))) + if point.name == name: + candidates.append(point) + except Exception: + continue + + return sorted(candidates, key=lambda point: point.timestamp, reverse=True)[0] if candidates else None + except Exception as e: + logger.error(f"Failed to locate restore point {name}: {e}") + return None + + @staticmethod + def find_restore_point_by_timestamp(timestamp: str) -> Optional[RestorePoint]: + try: + safe_timestamp = re.sub(r"[^0-9A-Za-z_.:-]", "", timestamp or "") + if not safe_timestamp or not Config.BACKUP_DIR.exists(): + return None + + manifest = Config.BACKUP_DIR / safe_timestamp / "restore-point.json" + if not manifest.exists(): + return None + + return RestorePoint.from_dict(json.loads(manifest.read_text(encoding="utf-8"))) + except Exception as e: + logger.error(f"Failed to locate restore point timestamp {timestamp}: {e}") + return None @staticmethod def restore(restore_point: RestorePoint) -> bool: """Restore from a restore point.""" try: + registry_results = [ + RestoreManager._restore_registry_entry(entry) + for entry in reversed(restore_point.registry) + ] + setting_results = [ + RestoreManager._restore_setting_entry(entry) + for entry in reversed(restore_point.settings) + ] + + file_results = [] for original, backup in restore_point.files.items(): + if str(original).startswith("reg:"): + continue + backup_path = Path(backup) if backup_path.exists(): shutil.copy2(backup_path, original) + file_results.append(True) logger.info(f"Restored file: {original}") logger.info(f"Restore point applied: {restore_point.name}") - return True + return all(registry_results) and all(setting_results) and all(file_results) except Exception as e: logger.error(f"Failed to restore from point: {e}") return False diff --git a/app/data/hyperboost_knowledge_base.json b/app/data/hyperboost_knowledge_base.json new file mode 100644 index 0000000..b3f8bd0 --- /dev/null +++ b/app/data/hyperboost_knowledge_base.json @@ -0,0 +1,258 @@ +{ + "metadata": { + "name": "HyperBoostX Triple AI Engine Knowledge Base", + "tagline": "Scan. Analyze. Boost. Revert.", + "positioning": [ + "AI Performance Doctor for Gaming PCs", + "AI Performance Tuning for NVIDIA RTX PCs", + "Optimized for NVIDIA RTX GPUs" + ], + "branding_guardrails": [ + "Do not claim Powered by NVIDIA.", + "Do not claim Official NVIDIA Partner.", + "Do not claim NVIDIA Certified." + ] + }, + "tweak_database": [ + { + "tweak_id": "optimize_power", + "name": "Set Performance Power Plan", + "description": "Switches Windows to a performance-oriented power plan when available.", + "category": "performance", + "risk_level": "low", + "backup_method": "store active power plan GUID", + "revert_method": "restore previous power plan GUID", + "requires_backup": true, + "requires_restore_point": false, + "reversible": true, + "can_auto_apply": true, + "notes": "Can help latency or frame pacing, but FPS gains are not guaranteed." + }, + { + "tweak_id": "optimize_visual", + "name": "Optimize Windows Visual Effects", + "description": "Uses a reversible registry value for simpler visual effects.", + "category": "performance", + "risk_level": "low", + "backup_method": "registry backup JSON", + "revert_method": "restore previous registry value", + "requires_backup": true, + "requires_restore_point": false, + "reversible": true, + "can_auto_apply": true, + "notes": "Low risk and suitable for Safe Boost." + }, + { + "tweak_id": "enable_game_mode", + "name": "Enable Windows Game Mode", + "description": "Enables Windows Game Mode for the current user.", + "category": "gaming", + "risk_level": "low", + "backup_method": "registry backup JSON", + "revert_method": "restore previous registry value", + "requires_backup": true, + "requires_restore_point": false, + "reversible": true, + "can_auto_apply": true, + "notes": "May help Windows prioritize game workloads; results depend on game and system state." + }, + { + "tweak_id": "disable_xbox", + "name": "Disable Xbox Game Bar Capture Overlay", + "description": "Disables GameDVR capture overlay for the current user.", + "category": "gaming", + "risk_level": "low", + "backup_method": "registry backup JSON", + "revert_method": "restore previous registry value", + "requires_backup": true, + "requires_restore_point": false, + "reversible": true, + "can_auto_apply": true, + "notes": "Useful only if overlay/capture is causing stutter." + }, + { + "tweak_id": "disable_telemetry", + "name": "Reduce Telemetry Policy", + "description": "Adjusts telemetry policy values where Windows edition permits it.", + "category": "privacy", + "risk_level": "medium", + "backup_method": "registry backup JSON", + "revert_method": "restore previous registry value", + "requires_backup": true, + "requires_restore_point": true, + "reversible": true, + "can_auto_apply": true, + "notes": "Must be explained clearly and applied only after approval." + }, + { + "tweak_id": "disable_defender", + "name": "Disable Windows Security Protection", + "description": "Attempts to disable Windows Security protection.", + "category": "security", + "risk_level": "blocked", + "backup_method": "not allowed", + "revert_method": "not guaranteed", + "requires_backup": true, + "requires_restore_point": true, + "reversible": false, + "can_auto_apply": false, + "notes": "Blocked by HyperBoostX Safety Guard." + }, + { + "tweak_id": "disable_updates", + "name": "Disable Windows Update Permanently", + "description": "Attempts to disable automatic Windows updates.", + "category": "maintenance", + "risk_level": "blocked", + "backup_method": "not allowed", + "revert_method": "not guaranteed", + "requires_backup": true, + "requires_restore_point": true, + "reversible": false, + "can_auto_apply": false, + "notes": "Blocked by HyperBoostX Safety Guard." + } + ], + "game_setting_database": [ + { + "game": "FiveM", + "engine": "RAGE / GTA V modded runtime", + "profile": "cpu-and-streaming-sensitive", + "dlss_support": false, + "reflex_support": false, + "frame_generation_support": false, + "recommended_low_vram": ["Normal textures", "lower population variety", "cap FPS to stable target"], + "recommended_mid_vram": ["High textures if VRAM headroom exists", "reduce extended distance scaling", "cap FPS"], + "known_issues": ["server scripts can limit FPS", "frame pacing can drop with heavy background apps"] + }, + { + "game": "Valorant", + "engine": "Unreal Engine 4", + "profile": "latency-sensitive", + "dlss_support": false, + "reflex_support": true, + "frame_generation_support": false, + "recommended_low_vram": ["low material quality", "low detail quality", "Reflex On if available"], + "recommended_mid_vram": ["medium textures", "Reflex On + Boost if thermals are stable"], + "known_issues": ["CPU bottleneck is common at high refresh rates"] + }, + { + "game": "Fortnite", + "engine": "Unreal Engine 5", + "profile": "gpu-and-shader-sensitive", + "dlss_support": true, + "reflex_support": true, + "frame_generation_support": true, + "recommended_low_vram": ["Performance Mode or lower textures", "DLSS Balanced if using DX12", "Reflex On"], + "recommended_mid_vram": ["DLSS Quality/Balanced", "cap FPS near monitor refresh", "use shader pre-cache where available"], + "known_issues": ["shader compilation stutter", "UE5 settings can be VRAM heavy"] + }, + { + "game": "CS2", + "engine": "Source 2", + "profile": "cpu-and-frame-pacing-sensitive", + "dlss_support": false, + "reflex_support": true, + "frame_generation_support": false, + "recommended_low_vram": ["lower shadows", "Reflex On", "cap FPS if frame time is unstable"], + "recommended_mid_vram": ["medium/high textures if VRAM allows", "Reflex On", "avoid unnecessary overlays"], + "known_issues": ["CPU limit at competitive settings", "overlay conflicts can add latency"] + } + ], + "nvidia_setting_database": [ + { + "setting": "NVIDIA Reflex", + "recommended_value": "On when supported; On + Boost only if thermals are stable", + "use_case": "latency-sensitive competitive games", + "risk": "low", + "explanation": "Can help reduce render queue latency in supported games.", + "revert_method": "set Reflex to Off or default in the game menu" + }, + { + "setting": "DLSS", + "recommended_value": "Quality for image quality, Balanced for heavier games, Performance for high resolution targets", + "use_case": "GPU-bound games on RTX GPUs", + "risk": "low", + "explanation": "Can improve stability or headroom depending on game and resolution.", + "revert_method": "set upscaling to native/off in the game menu" + }, + { + "setting": "Frame Generation", + "recommended_value": "Use only when base FPS and latency feel stable", + "use_case": "single-player or visually rich games on supported RTX GPUs", + "risk": "low", + "explanation": "Can make motion look smoother but does not replace low-latency tuning.", + "revert_method": "disable in the game menu" + }, + { + "setting": "V-Sync", + "recommended_value": "Usually Off for competitive games; use cap/VRR strategy for smoothness", + "use_case": "latency and frame pacing control", + "risk": "low", + "explanation": "Wrong V-Sync choices can add latency or tearing depending on display setup.", + "revert_method": "restore default in game or NVIDIA app/control panel" + } + ], + "safety_policy_database": { + "allowed_actions": [ + "scan_pc", + "recommend_settings", + "create_backup", + "create_restore_point", + "cleanup_temp_files", + "set_power_plan", + "enable_game_mode", + "disable_nonessential_startup_with_approval", + "revert_changes" + ], + "warning_actions": [ + "registry_tweak_reversible", + "service_optimization_noncritical", + "network_latency_tweak_light" + ], + "blocked_actions": [ + "auto_overclock", + "auto_undervolt", + "voltage_change", + "bios_uefi_change", + "disable_windows_security", + "disable_firewall", + "disable_windows_update_permanent", + "delete_windows_service_permanent", + "irreversible_registry_edit", + "guaranteed_fps_claim" + ], + "backup_requirements": { + "low": "backup recommended for applied changes", + "medium": "backup required", + "high": "manual only with restore point; no auto apply", + "blocked": "must not run" + } + }, + "error_knowledge_base": [ + { + "error": "low_gpu_usage", + "symptoms": ["GPU usage low while FPS is low", "CPU thread usage high", "frame time spikes"], + "safe_fixes": ["check power plan", "close heavy background apps", "cap FPS to stable target", "review CPU-heavy settings"], + "risky_fixes": ["aggressive service disabling"], + "blocked_fixes": ["auto overclock", "voltage changes"] + }, + { + "error": "shader_stutter", + "symptoms": ["stutter after driver update", "first-match stutter", "disk activity during gameplay"], + "safe_fixes": ["allow shader cache rebuild", "clean DirectX shader cache with approval", "keep enough storage free"], + "risky_fixes": ["deleting arbitrary driver folders"], + "blocked_fixes": ["driver service deletion"] + } + ], + "benchmark_database": [ + { + "hardware_profile": "RTX midrange + 16GB RAM", + "game": "Fortnite", + "setting": "DLSS Balanced + Reflex On", + "average_fps": "internal baseline required", + "one_percent_low": "internal baseline required", + "notes": "Use as recommendation context only; never claim guaranteed FPS uplift." + } + ] +} diff --git a/app/services/ai/__init__.py b/app/services/ai/__init__.py new file mode 100644 index 0000000..5a63440 --- /dev/null +++ b/app/services/ai/__init__.py @@ -0,0 +1,2 @@ +"""Triple AI Engine services for HyperBoostX.""" + diff --git a/app/services/ai/knowledge_base.py b/app/services/ai/knowledge_base.py new file mode 100644 index 0000000..a27491a --- /dev/null +++ b/app/services/ai/knowledge_base.py @@ -0,0 +1,111 @@ +"""Local RAG-style knowledge base for HyperBoostX Triple AI Engine.""" + +import json +import re +import sys +from pathlib import Path +from typing import Any, Dict, Iterable, List + +from core.logger import Logger + + +logger = Logger.get_logger(__name__) + + +class KnowledgeBase: + """Small local retrieval layer used before any AI text is generated.""" + + DEFAULT_FILE = "hyperboost_knowledge_base.json" + + def __init__(self, path: Path | None = None): + self.path = path or self._default_path() + self.data = self._load() + + @classmethod + def _default_path(cls) -> Path: + bundled_root = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parents[2])) + candidates = [ + bundled_root / "data" / cls.DEFAULT_FILE, + Path(__file__).resolve().parents[2] / "data" / cls.DEFAULT_FILE, + ] + for candidate in candidates: + if candidate.exists(): + return candidate + return candidates[-1] + + def _load(self) -> Dict[str, Any]: + try: + return json.loads(self.path.read_text(encoding="utf-8")) + except Exception as exc: + logger.error("Failed to load HyperBoostX knowledge base: %s", exc) + return {} + + def get_tweaks(self) -> List[Dict[str, Any]]: + return list(self.data.get("tweak_database", [])) + + def get_tweak(self, tweak_id: str) -> Dict[str, Any] | None: + normalized = (tweak_id or "").strip().lower() + for tweak in self.get_tweaks(): + if str(tweak.get("tweak_id", "")).lower() == normalized: + return dict(tweak) + return None + + def safety_policy(self) -> Dict[str, Any]: + return dict(self.data.get("safety_policy_database", {})) + + def search(self, query: str, categories: Iterable[str] | None = None, limit: int = 5) -> List[Dict[str, Any]]: + """Return simple keyword-ranked KB snippets. + + This is intentionally local and deterministic. It gives the Assistant and + Analyzer grounding data without introducing a fourth user-visible AI role. + """ + + tokens = self._tokens(query) + if not tokens: + return [] + + allowed = set(categories or []) + entries = self._flatten_entries() + if allowed: + entries = [entry for entry in entries if entry["category"] in allowed] + + scored = [] + for entry in entries: + haystack = entry["text"].lower() + score = sum(1 for token in tokens if token in haystack) + if score: + scored.append((score, entry)) + + scored.sort(key=lambda item: item[0], reverse=True) + return [entry for _, entry in scored[: max(1, limit)]] + + @staticmethod + def _tokens(query: str) -> List[str]: + return [token for token in re.findall(r"[a-z0-9_+.-]+", (query or "").lower()) if len(token) >= 3] + + def _flatten_entries(self) -> List[Dict[str, Any]]: + flattened: List[Dict[str, Any]] = [] + for category in ( + "tweak_database", + "game_setting_database", + "nvidia_setting_database", + "error_knowledge_base", + "benchmark_database", + ): + for item in self.data.get(category, []): + flattened.append({ + "category": category, + "id": item.get("tweak_id") or item.get("game") or item.get("setting") or item.get("error") or item.get("hardware_profile"), + "text": json.dumps(item, ensure_ascii=True, sort_keys=True), + "item": item, + }) + + policy = self.data.get("safety_policy_database") + if policy: + flattened.append({ + "category": "safety_policy_database", + "id": "safety_policy", + "text": json.dumps(policy, ensure_ascii=True, sort_keys=True), + "item": policy, + }) + return flattened diff --git a/app/services/ai/knowledge_base_service.py b/app/services/ai/knowledge_base_service.py new file mode 100644 index 0000000..edc16a1 --- /dev/null +++ b/app/services/ai/knowledge_base_service.py @@ -0,0 +1,145 @@ +"""Internal knowledge base and lightweight retrieval for HyperBoostX.""" + +import json +import re +from pathlib import Path +from typing import Any, Dict, List + +from core.logger import Logger + + +logger = Logger.get_logger(__name__) + + +class KnowledgeBaseService: + """Loads HyperBoostX policy/game/tweak data and provides simple local search.""" + + DEFAULT_KB_PATH = Path(__file__).resolve().parents[2] / "data" / "hyperboost_knowledge_base.json" + + def __init__(self, kb_path: Path | None = None): + self.kb_path = kb_path or self.DEFAULT_KB_PATH + self._data: Dict[str, Any] = {} + self._documents: List[Dict[str, Any]] = [] + self.reload() + + def reload(self) -> None: + try: + self._data = json.loads(self.kb_path.read_text(encoding="utf-8")) + self._documents = self._build_documents(self._data) + logger.info("HyperBoostX knowledge base loaded: %s", self.kb_path) + except Exception as exc: + logger.error("Failed to load HyperBoostX knowledge base: %s", exc) + self._data = {} + self._documents = [] + + @property + def data(self) -> Dict[str, Any]: + return self._data + + def metadata(self) -> Dict[str, Any]: + return dict(self._data.get("metadata") or {}) + + def tweak_database(self) -> List[Dict[str, Any]]: + return list(self._data.get("tweak_database") or []) + + def game_database(self) -> List[Dict[str, Any]]: + return list(self._data.get("game_setting_database") or []) + + def nvidia_database(self) -> List[Dict[str, Any]]: + return list(self._data.get("nvidia_setting_database") or []) + + def safety_policy(self) -> Dict[str, Any]: + return dict(self._data.get("safety_policy_database") or {}) + + def find_tweak(self, tweak_id: str) -> Dict[str, Any]: + normalized = (tweak_id or "").strip().lower() + for item in self.tweak_database(): + if (item.get("tweak_id") or item.get("id") or "").lower() == normalized: + return dict(item) + return {} + + def find_game(self, game_name: str) -> Dict[str, Any]: + normalized = self._normalize(game_name) + for item in self.game_database(): + if self._normalize(item.get("game", "")) == normalized: + return dict(item) + for item in self.game_database(): + if normalized and normalized in self._normalize(item.get("game", "")): + return dict(item) + return {} + + def search(self, query: str, limit: int = 5) -> List[Dict[str, Any]]: + """Return top local knowledge snippets by token overlap.""" + query_tokens = set(self._tokens(query)) + if not query_tokens: + return [] + + scored: List[tuple[int, Dict[str, Any]]] = [] + for doc in self._documents: + score = len(query_tokens.intersection(doc["tokens"])) + if score > 0: + scored.append((score, doc)) + + scored.sort(key=lambda item: item[0], reverse=True) + return [ + { + "category": doc["category"], + "id": doc["id"], + "title": doc["title"], + "summary": doc["summary"], + "score": score, + } + for score, doc in scored[: max(1, limit)] + ] + + @staticmethod + def _build_documents(data: Dict[str, Any]) -> List[Dict[str, Any]]: + documents: List[Dict[str, Any]] = [] + + def add(category: str, doc_id: str, title: str, payload: Dict[str, Any]) -> None: + text = json.dumps(payload, ensure_ascii=False, sort_keys=True) + documents.append( + { + "category": category, + "id": doc_id, + "title": title, + "summary": KnowledgeBaseService._summarize(payload), + "tokens": set(KnowledgeBaseService._tokens(text)), + } + ) + + for item in data.get("tweak_database") or []: + add("tweak", item.get("tweak_id", ""), item.get("name", ""), item) + for item in data.get("game_setting_database") or []: + add("game", item.get("game", ""), item.get("game", ""), item) + for item in data.get("nvidia_setting_database") or []: + add("nvidia", item.get("setting", ""), item.get("setting", ""), item) + for item in data.get("error_knowledge_base") or []: + add("error", item.get("error", ""), item.get("error", ""), item) + + policy = data.get("safety_policy_database") or {} + if policy: + add("safety_policy", "safety_policy", "HyperBoostX Safety Policy", policy) + + return documents + + @staticmethod + def _summarize(payload: Dict[str, Any]) -> str: + for key in ("description", "explanation", "notes", "profile"): + value = payload.get(key) + if isinstance(value, str) and value.strip(): + return value.strip() + return json.dumps(payload, ensure_ascii=False)[:240] + + @staticmethod + def _tokens(text: str) -> List[str]: + return [ + token + for token in re.findall(r"[a-zA-Z0-9_]+", (text or "").lower()) + if len(token) >= 3 + ] + + @staticmethod + def _normalize(value: str) -> str: + return re.sub(r"[^a-z0-9]+", "", (value or "").lower()) + diff --git a/app/services/ai/pc_scanner_service.py b/app/services/ai/pc_scanner_service.py new file mode 100644 index 0000000..5e167a2 --- /dev/null +++ b/app/services/ai/pc_scanner_service.py @@ -0,0 +1,340 @@ +"""Safe PC scanner used by the HyperBoostX Triple AI Engine.""" + +import json +import os +import platform +import re +import subprocess +import uuid +import winreg +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Dict, List + +from core.config import Config +from core.logger import Logger +from services.monitoring.monitor_service import MonitorService +from services.monitoring.system_info_service import SystemInfoService +from services.optimization.startup_service import StartupService + + +logger = Logger.get_logger(__name__) + + +class PcScannerService: + """Collects the MVP scan payload without changing system state.""" + + def __init__(self): + self.scan_history_dir = Config.DATA_DIR / "scan-history" + self.scan_history_dir.mkdir(parents=True, exist_ok=True) + + def scan_pc(self) -> Dict[str, Any]: + logger.info("scan started") + scan_id = self._new_scan_id() + timestamp = datetime.now(timezone.utc).isoformat() + + stats = self._safe_call(MonitorService.get_current_stats, {}) + cpu = self._safe_call(SystemInfoService.get_cpu_info, {}) + memory = self._safe_call(SystemInfoService.get_memory_info, {}) + disk = self._safe_call(SystemInfoService.get_disk_info, {}) + system_drive = self._safe_call(SystemInfoService.get_system_drive_info, {}) + windows_details = self._safe_call(SystemInfoService.get_windows_system_details, {}) + os_info = self._safe_call(SystemInfoService.get_os_info, {}) + gpu = self._safe_call(SystemInfoService.get_gpu_info, {}) + startup_items = self._safe_call(StartupService.get_startup_items, []) + processes = self._safe_call(lambda: MonitorService.get_process_list(limit=15), []) + + primary_gpu = self._primary_gpu(gpu, stats) + scan_result = { + "scan_id": scan_id, + "timestamp": timestamp, + "hardware": { + "cpu_name": cpu.get("processor") or "Unknown", + "gpu_name": primary_gpu.get("name") or "Unknown", + "ram_total_gb": self._bytes_to_gb(memory.get("total", 0)), + "storage_type": system_drive.get("storage_class", "Unknown"), + "cpu": { + "name": cpu.get("processor") or "Unknown", + "cores": cpu.get("cores", 0), + "threads": cpu.get("threads", 0), + "usage_percent": stats.get("cpu", cpu.get("usage", 0)), + "frequency_current_mhz": cpu.get("frequency_current", 0), + "frequency_max_mhz": cpu.get("frequency_max", 0), + }, + "gpu": primary_gpu, + "ram": { + "total_gb": self._bytes_to_gb(memory.get("total", 0)), + "available_gb": self._bytes_to_gb(memory.get("available", 0)), + "usage_percent": memory.get("percent", stats.get("memory", 0)), + "speed_mhz": memory.get("speed_mhz", 0), + "slots_used": memory.get("slots_used", 0), + }, + "storage": { + "system_drive": system_drive.get("drive_letter", "C"), + "type": system_drive.get("storage_class", "Unknown"), + "model": system_drive.get("model", "Unknown"), + "free_gb": self._system_drive_free_gb(disk, system_drive), + "usage_percent": stats.get("disk", 0), + }, + }, + "windows": { + "version": windows_details.get("edition") or os_info.get("system") or platform.system(), + "build_number": windows_details.get("build") or os_info.get("version") or platform.version(), + "architecture": windows_details.get("architecture") or os_info.get("architecture", "Unknown"), + "power_plan": self._get_active_power_plan(), + "game_mode": self._read_game_mode_status(), + "hags": self._read_hags_status(), + "startup_apps": self._summarize_startup(startup_items), + "background_apps_heavy": self._summarize_processes(processes), + "temporary_files_size_mb": self._estimate_temp_files_mb(), + }, + "nvidia": self._build_nvidia_payload(primary_gpu), + "apps": { + "startup_count": len(startup_items), + "startup_high_impact": sum(1 for item in startup_items if item.get("impact") == "High"), + "background_process_count": stats.get("processes", 0), + "top_background_apps": self._summarize_processes(processes[:8]), + }, + "performance": { + "cpu_usage_percent": stats.get("cpu", 0), + "ram_usage_percent": stats.get("memory", 0), + "disk_usage_percent": stats.get("disk", 0), + "gpu_usage_percent": (stats.get("gpu") or {}).get("load", 0), + "gpu_temperature_c": (stats.get("gpu") or {}).get("temperature", 0), + "disk_read_mb_s": stats.get("disk_read_mb_s", 0), + "disk_write_mb_s": stats.get("disk_write_mb_s", 0), + "processes": stats.get("processes", 0), + "temperatures": stats.get("temperatures", {}), + }, + "scores": {}, + "privacy": { + "cloud_payload_note": "HyperBoostX only sends this sanitized scan payload when AI Cloud Analysis is enabled.", + "personal_paths_included": False, + "api_key_logged": False, + }, + } + scan_result["scores"] = self.calculate_scores(scan_result) + self._save_scan(scan_result) + logger.info("scan completed: %s", scan_id) + return scan_result + + @staticmethod + def calculate_scores(scan_result: Dict[str, Any]) -> Dict[str, int]: + hardware = scan_result.get("hardware") or {} + windows = scan_result.get("windows") or {} + performance = scan_result.get("performance") or {} + apps = scan_result.get("apps") or {} + nvidia = scan_result.get("nvidia") or {} + + ram_usage = float(performance.get("ram_usage_percent") or 0) + disk_usage = float(performance.get("disk_usage_percent") or 0) + cpu_usage = float(performance.get("cpu_usage_percent") or 0) + background_count = int(apps.get("background_process_count") or 0) + startup_high = int(apps.get("startup_high_impact") or 0) + + health = 100 + health -= 18 if ram_usage >= 85 else 10 if ram_usage >= 75 else 0 + health -= 16 if disk_usage >= 90 else 8 if disk_usage >= 80 else 0 + health -= 12 if cpu_usage >= 85 else 5 if cpu_usage >= 70 else 0 + health -= min(startup_high * 4, 16) + health -= 8 if background_count >= 220 else 4 if background_count >= 160 else 0 + if "high performance" not in str(windows.get("power_plan", "")).lower() and "ultimate" not in str(windows.get("power_plan", "")).lower(): + health -= 5 + if str(windows.get("game_mode", "")).lower() in {"off", "disabled"}: + health -= 5 + + ram_total = float((hardware.get("ram") or {}).get("total_gb") or 0) + vram_gb = float((hardware.get("gpu") or {}).get("vram_gb") or 0) + readiness = 100 + readiness -= 20 if ram_total and ram_total < 8 else 10 if ram_total and ram_total < 16 else 0 + readiness -= 12 if vram_gb and vram_gb < 4 else 5 if vram_gb and vram_gb < 6 else 0 + readiness -= 10 if not nvidia.get("is_nvidia") else 0 + readiness -= 8 if not nvidia.get("driver_version") or nvidia.get("driver_version") == "Unknown" else 0 + readiness -= 6 if str(windows.get("game_mode", "")).lower() in {"off", "disabled"} else 0 + readiness -= 6 if ram_usage >= 80 else 0 + + return { + "pc_health_score": max(0, min(100, int(round(health)))), + "gaming_readiness_score": max(0, min(100, int(round(readiness)))), + } + + def load_scan(self, scan_id: str) -> Dict[str, Any]: + safe_scan_id = re.sub(r"[^a-zA-Z0-9_-]", "", scan_id or "") + if not safe_scan_id: + return {} + path = self.scan_history_dir / f"{safe_scan_id}.json" + if not path.exists(): + return {} + try: + return json.loads(path.read_text(encoding="utf-8")) + except Exception as exc: + logger.error("Failed to load scan %s: %s", scan_id, exc) + return {} + + def _save_scan(self, scan_result: Dict[str, Any]) -> None: + try: + scan_id = scan_result["scan_id"] + path = self.scan_history_dir / f"{scan_id}.json" + path.write_text(json.dumps(scan_result, indent=2, ensure_ascii=False), encoding="utf-8") + except Exception as exc: + logger.error("Failed to save scan result: %s", exc) + + @staticmethod + def _new_scan_id() -> str: + stamp = datetime.now().strftime("%Y%m%d%H%M%S") + return f"scan-{stamp}-{uuid.uuid4().hex[:8]}" + + @staticmethod + def _safe_call(func, default): + try: + return func() + except Exception as exc: + logger.warning("Scanner probe failed: %s", exc) + return default + + @staticmethod + def _bytes_to_gb(value: Any) -> float: + try: + return round(float(value or 0) / (1024**3), 2) + except Exception: + return 0.0 + + @staticmethod + def _system_drive_free_gb(disk: Dict[str, Any], system_drive: Dict[str, Any]) -> float: + drive = f"{system_drive.get('drive_letter', 'C')}:".upper() + for device, item in (disk or {}).items(): + if str(device).upper().startswith(drive): + return PcScannerService._bytes_to_gb(item.get("free", 0)) + return 0.0 + + @staticmethod + def _primary_gpu(gpu_info: Dict[str, Any], stats: Dict[str, Any]) -> Dict[str, Any]: + gpus = gpu_info.get("gpus") or [] + first = gpus[0] if gpus else {} + live_gpu = stats.get("gpu") or {} + adapter_ram = first.get("vram") or 0 + live_vram_mb = live_gpu.get("memory_total_mb") or 0 + vram_gb = round((live_vram_mb / 1024) if live_vram_mb else (float(adapter_ram or 0) / (1024**3)), 2) + name = first.get("name") or live_gpu.get("name") or "Unknown" + return { + "name": name, + "driver_version": first.get("driver_version") or "Unknown", + "driver_date": first.get("driver_date") or "Unknown", + "vram_gb": vram_gb, + "usage_percent": live_gpu.get("load", 0), + "temperature_c": live_gpu.get("temperature", 0), + "video_processor": first.get("video_processor", "Unknown"), + } + + @staticmethod + def _build_nvidia_payload(primary_gpu: Dict[str, Any]) -> Dict[str, Any]: + gpu_name = primary_gpu.get("name") or "" + is_nvidia = any(token in gpu_name.lower() for token in ("nvidia", "geforce", "rtx", "gtx")) + is_rtx = "rtx" in gpu_name.lower() + return { + "is_nvidia": is_nvidia, + "is_rtx": is_rtx, + "gpu_name": gpu_name or "Unknown", + "driver_version": primary_gpu.get("driver_version", "Unknown"), + "driver_date": primary_gpu.get("driver_date", "Unknown"), + "vram_gb": primary_gpu.get("vram_gb", 0), + "control_panel_status": "Unknown", + "feature_support": { + "dlss_possible": is_rtx, + "reflex_possible": is_nvidia, + "frame_generation_possible": is_rtx, + }, + "support_note": "Full NVIDIA recommendations available." if is_nvidia else "Limited support: NVIDIA GPU was not detected.", + } + + @staticmethod + def _summarize_startup(startup_items: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + summary = [] + for item in startup_items[:20]: + summary.append( + { + "name": item.get("name", "Unknown"), + "enabled": item.get("enabled", False), + "impact": item.get("impact", "Unknown"), + "impact_score": item.get("impact_score", 0), + "recommended_action": item.get("recommended_action", ""), + "source": item.get("source", "Unknown"), + "type": item.get("type", "App"), + } + ) + return summary + + @staticmethod + def _summarize_processes(processes: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return [ + { + "name": item.get("name", "Unknown"), + "cpu_percent": round(float(item.get("cpu") or 0), 2), + "memory_percent": round(float(item.get("memory") or 0), 2), + "threads": item.get("threads", 0), + "disk_io_mb": round(float(item.get("disk_io_mb") or 0), 2), + } + for item in (processes or [])[:12] + ] + + @staticmethod + def _get_active_power_plan() -> str: + if platform.system() != "Windows": + return "Unavailable" + try: + output = subprocess.check_output( + ["powercfg", "/getactivescheme"], + text=True, + stderr=subprocess.DEVNULL, + timeout=3, + ).strip() + return output or "Unknown" + except Exception: + return "Unknown" + + @staticmethod + def _read_game_mode_status() -> str: + try: + with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\GameBar") as key: + value, _ = winreg.QueryValueEx(key, "AutoGameModeEnabled") + return "On" if int(value) == 1 else "Off" + except Exception: + return "Unknown" + + @staticmethod + def _read_hags_status() -> str: + try: + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\GraphicsDrivers") as key: + value, _ = winreg.QueryValueEx(key, "HwSchMode") + return "On" if int(value) == 2 else "Off" if int(value) == 1 else "Default" + except Exception: + return "Unknown" + + @staticmethod + def _estimate_temp_files_mb(max_entries: int = 5000) -> float: + roots = { + os.environ.get("TEMP", ""), + os.environ.get("TMP", ""), + str(Path(os.environ.get("SystemRoot", r"C:\Windows")) / "Temp"), + } + total = 0 + visited = 0 + for root in roots: + if not root: + continue + path = Path(root) + if not path.exists(): + continue + try: + for item in path.rglob("*"): + if visited >= max_entries: + break + visited += 1 + try: + if item.is_file(): + total += item.stat().st_size + except OSError: + continue + except OSError: + continue + return round(total / (1024 * 1024), 1) diff --git a/app/services/ai/triple_ai_engine.py b/app/services/ai/triple_ai_engine.py new file mode 100644 index 0000000..e8c48a8 --- /dev/null +++ b/app/services/ai/triple_ai_engine.py @@ -0,0 +1,1268 @@ +"""HyperBoostX Triple AI Engine with cloud-optional, local-safe fallback.""" + +import json +import os +import re +import time +import uuid +from datetime import datetime, timezone +from typing import Any, Dict, List, Tuple + +try: + import requests +except ImportError: # optional cloud dependency; local fallback still works + requests = None + +from core.config import Config +from core.logger import Logger +from services.ai.knowledge_base_service import KnowledgeBaseService +from services.ai.pc_scanner_service import PcScannerService +from services.optimization.tweak_service import TweakService + + +logger = Logger.get_logger(__name__) + + +class TripleAiEngine: + """AI Assistant, AI Analyzer, AI Safety Guard, and RAG-backed local fallback.""" + + ASSISTANT_MODEL = Config.AI_ASSISTANT_MODEL + ANALYZER_MODEL = Config.AI_ANALYZER_MODEL + SAFETY_MODEL = Config.AI_SAFETY_MODEL + EMBED_MODEL = Config.AI_EMBED_MODEL + DEFAULT_MODEL = Config.NVIDIA_DEFAULT_MODEL + FALLBACK_MODEL = Config.NVIDIA_FALLBACK_MODEL + + BLOCKED_TWEAK_IDS = { + "disable_defender", + "disable_windows_security", + "disable_updates", + "disable_windows_update", + "auto_overclock", + "auto_undervolt", + "voltage_change", + "bios_uefi_change", + "delete_windows_service_permanent", + "irreversible_registry_edit", + } + + BLOCKED_TERMS = ( + "overclock", + "undervolt", + "voltage", + "bios", + "uefi", + "disable windows security", + "disable defender", + "disable firewall", + "disable windows update permanent", + "permanently disable windows update", + "delete service", + "remove service", + "guaranteed fps", + "pasti naik", + ) + + def __init__( + self, + knowledge_base: KnowledgeBaseService | None = None, + system_info_service: Any | None = None, + monitor_service: Any | None = None, + startup_service: Any | None = None, + tweak_service: Any | None = None, + ): + self.knowledge_base = knowledge_base or KnowledgeBaseService() + self.reports_dir = Config.DATA_DIR / "performance-reports" + self.reports_dir.mkdir(parents=True, exist_ok=True) + self.system_info_service = system_info_service + self.monitor_service = monitor_service + self.startup_service = startup_service + self.tweak_service = tweak_service + self.scanner = PcScannerService() + + def scan_pc(self) -> Dict[str, Any]: + """Run Scan My PC and return a sanitized MVP scan contract.""" + if self.system_info_service or self.monitor_service or self.startup_service: + return self._scan_with_injected_services() + return self.scanner.scan_pc() + + def analyze(self, scan_result: Dict[str, Any], user_goal: str = "gaming", game: str = "") -> Dict[str, Any]: + """Compatibility wrapper for the AI Analyzer role.""" + result = self.analyze_scan(scan_result.get("scan_id", ""), scan_result, user_goal) + result["role"] = "AI Analyzer" + if game: + result["game_optimization"] = self.optimize_game(game, scan_result) + return result + + def assistant_response( + self, + scan_result: Dict[str, Any], + analysis_result: Dict[str, Any], + safety_result: Dict[str, Any], + ) -> Dict[str, Any]: + """Compatibility wrapper for the AI Assistant role.""" + result = self.assistant_summary(scan_result, analysis_result, safety_result) + result["role"] = "AI Assistant" + return result + + def run_full_flow(self, user_goal: str = "gaming", game: str = "") -> Dict[str, Any]: + """Run Scan -> Analyze -> Safety -> Assistant -> Report without applying tweaks.""" + scan = self.scan_pc() + analysis = self.analyze(scan, user_goal=user_goal, game=game) + safety = self.safety_check(analysis.get("recommendations", [])) + safety["role"] = "AI Safety Guard" + assistant = self.assistant_response(scan, analysis, safety) + report = self.create_performance_report(scan, analysis, safety, assistant) + return { + "scan": scan, + "analysis": analysis, + "safety": safety, + "assistant": assistant, + "report": report, + } + + def apply_safe_tweaks(self, approved_tweaks: List[Dict[str, Any]], user_approved: bool = False) -> Dict[str, Any]: + """Apply only Safety Guard approved, reversible, auto-apply tweaks.""" + if not user_approved: + logger.info("user approval missing for safe tweak apply") + return { + "success": False, + "applied": [], + "failed": [], + "blocked": [], + "backup_id": "", + "error": "User approval is required before applying tweaks.", + } + + safety = self.safety_check(approved_tweaks) + tweak_service = self.tweak_service + if tweak_service is None: + from services.optimization.tweak_service import TweakService + + tweak_service = TweakService + + applied = [] + failed = [] + for item in safety.get("approved", []): + if not item.get("can_auto_apply"): + continue + tweak_id = item.get("tweak_id") or item.get("id") + if not tweak_id: + continue + logger.info("user approval received for tweak: %s", tweak_id) + result = tweak_service.apply_tweak(tweak_id, confirmed=True) + record = {"tweak_id": tweak_id, "result": result} + if result.get("success"): + applied.append(record) + logger.info("tweak applied: %s", tweak_id) + else: + failed.append(record) + logger.warning("tweak failed: %s", tweak_id) + + backup_id = "" + for item in applied: + backup_id = item.get("result", {}).get("restore_timestamp") or backup_id + + return { + "success": bool(applied) and not failed, + "applied": applied, + "failed": failed, + "blocked": safety.get("blocked", []), + "warnings": safety.get("warnings", []), + "backup_id": backup_id, + "safety": safety, + } + + def revert_tweaks(self, backup_id: str = "", tweak_ids: List[str] | None = None) -> Dict[str, Any]: + """Revert tweaks by explicit tweak IDs using their latest restore points.""" + tweak_service = self.tweak_service + if tweak_service is None: + from services.optimization.tweak_service import TweakService + + tweak_service = TweakService + + reverted = [] + failed = [] + for tweak_id in tweak_ids or []: + result = tweak_service.revert_tweak(tweak_id) + if result.get("success"): + reverted.append({"tweak_id": tweak_id, "result": result}) + else: + failed.append({"tweak_id": tweak_id, "result": result}) + return {"reverted": reverted, "failed": failed, "backup_id": backup_id} + + def create_performance_report( + self, + scan_result: Dict[str, Any], + analysis_result: Dict[str, Any], + safety_result: Dict[str, Any], + assistant_result: Dict[str, Any] | None = None, + ) -> Dict[str, Any]: + report = self.create_report(scan_result, analysis_result, safety_result) + if assistant_result: + report["assistant_summary"] = assistant_result.get("message", "") + return report + + def _kb_recommendation(self, tweak_id: str) -> Dict[str, Any]: + kb_tweak = self.knowledge_base.find_tweak(tweak_id) + if not kb_tweak: + return self._rec(tweak_id, tweak_id.replace("_", " ").title(), "", "low", "Local recommendation.") + return self._rec_from_tweak( + tweak_id, + kb_tweak.get("name", tweak_id), + kb_tweak.get("description", ""), + "medium", + ) + + def _cloud_enabled(self) -> bool: + return ( + str(os.environ.get("AI_CLOUD_ENABLED", "true")).lower() in {"1", "true", "yes", "on"} + and requests is not None + and bool(os.environ.get("NVIDIA_API_KEY", "").strip()) + ) + + def analyze_scan(self, scan_id: str, scan_result: Dict[str, Any], user_goal: str = "safe_boost") -> Dict[str, Any]: + logger.info("AI Analyzer request: scan=%s goal=%s", scan_id, user_goal) + local_result = self._local_analyze(scan_id, scan_result, user_goal) + cloud_result = self._try_cloud_json( + model=os.environ.get("AI_ANALYZER_MODEL", self.ANALYZER_MODEL), + system_prompt=( + "You are HyperBoostX AI Analyzer. Return only JSON with issues, " + "recommendations, confidence, health_score, and gaming_readiness_score. " + "Never recommend dangerous tweaks. All recommendations must include " + "risk_level, reversible, requires_backup, requires_restore_point, " + "can_auto_apply, user_approval_required, and expected_impact." + ), + payload={ + "scan_id": scan_id, + "scan_result": self._sanitize_for_cloud(scan_result), + "user_goal": user_goal, + "knowledge_context": local_result.get("rag_context", []), + }, + ) + if self._valid_analysis(cloud_result): + merged = self._normalize_analysis(cloud_result, local_result) + logger.info("AI Analyzer result: cloud recommendations=%s", len(merged.get("recommendations", []))) + return merged + + logger.info("AI Analyzer result: local fallback recommendations=%s", len(local_result.get("recommendations", []))) + return local_result + + def _scan_with_injected_services(self) -> Dict[str, Any]: + """Build a scan from injected test/service dependencies.""" + system_info = self.system_info_service + monitor = self.monitor_service + startup = self.startup_service + stats = self._safe_dependency_call(monitor, "get_current_stats", {}) + cpu = self._safe_dependency_call(system_info, "get_cpu_info", {}) + memory = self._safe_dependency_call(system_info, "get_memory_info", {}) + disk = self._safe_dependency_call(system_info, "get_disk_info", {}) + system_drive = self._safe_dependency_call(system_info, "get_system_drive_info", {}) + os_info = self._safe_dependency_call(system_info, "get_os_info", {}) + gpu_info = self._safe_dependency_call(system_info, "get_gpu_info", {}) + startup_items = self._safe_dependency_call(startup, "get_startup_items", []) + processes = self._safe_dependency_call(monitor, "get_process_list", []) + + gpus = gpu_info.get("gpus") or [] + primary_gpu = gpus[0] if gpus else {} + gpu_name = primary_gpu.get("name") or (stats.get("gpu") or {}).get("name") or "Unknown" + vram = primary_gpu.get("vram") or 0 + vram_gb = round(float(vram or 0) / (1024**3), 2) if vram else round(float((stats.get("gpu") or {}).get("memory_total_mb") or 0) / 1024, 2) + free_gb = 0.0 + for item in (disk or {}).values(): + if isinstance(item, dict) and item.get("free"): + free_gb = round(float(item.get("free") or 0) / (1024**3), 2) + break + + scan = { + "scan_id": f"scan-test-{int(time.time())}", + "timestamp": datetime.now(timezone.utc).isoformat(), + "hardware": { + "cpu_name": cpu.get("processor", "Unknown"), + "gpu_name": gpu_name, + "ram_total_gb": round(float(memory.get("total") or 0) / (1024**3), 2), + "storage_type": system_drive.get("storage_class", "Unknown"), + "cpu": { + "name": cpu.get("processor", "Unknown"), + "cores": cpu.get("cores", 0), + "threads": cpu.get("threads", 0), + "usage_percent": stats.get("cpu", 0), + }, + "gpu": { + "name": gpu_name, + "driver_version": primary_gpu.get("driver_version", "Unknown"), + "vram_gb": vram_gb, + "usage_percent": (stats.get("gpu") or {}).get("load", 0), + "temperature_c": (stats.get("gpu") or {}).get("temperature", 0), + }, + "ram": { + "total_gb": round(float(memory.get("total") or 0) / (1024**3), 2), + "usage_percent": stats.get("memory", 0), + "speed_mhz": memory.get("speed_mhz", 0), + }, + "storage": { + "type": system_drive.get("storage_class", "Unknown"), + "free_gb": free_gb, + "usage_percent": stats.get("disk", 0), + }, + }, + "windows": { + "version": os_info.get("version", "Unknown"), + "build_number": os_info.get("release", "Unknown"), + "power_plan": "Balanced", + "game_mode": "Unknown", + "hags": "Unknown", + "startup_apps": startup_items[:20], + "background_apps_heavy": processes[:12], + "temporary_files_size_mb": 0, + }, + "nvidia": { + "is_nvidia": "nvidia" in gpu_name.lower() or "rtx" in gpu_name.lower() or "gtx" in gpu_name.lower(), + "is_rtx": "rtx" in gpu_name.lower(), + "gpu_name": gpu_name, + "driver_version": primary_gpu.get("driver_version", "Unknown"), + "vram_gb": vram_gb, + }, + "apps": { + "startup_count": len(startup_items), + "startup_high_impact": sum(1 for item in startup_items if item.get("impact") == "High"), + "background_process_count": stats.get("processes", 0), + "top_background_apps": processes[:8], + }, + "performance": { + "cpu_usage_percent": stats.get("cpu", 0), + "ram_usage_percent": stats.get("memory", 0), + "disk_usage_percent": stats.get("disk", 0), + "gpu_usage_percent": (stats.get("gpu") or {}).get("load", 0), + "gpu_temperature_c": (stats.get("gpu") or {}).get("temperature", 0), + "processes": stats.get("processes", 0), + }, + } + from services.ai.pc_scanner_service import PcScannerService as _Scanner + + scan["scores"] = _Scanner.calculate_scores(scan) + return scan + + @staticmethod + def _safe_dependency_call(service: Any, method_name: str, default: Any) -> Any: + if not service: + return default + try: + method = getattr(service, method_name) + return method() + except TypeError: + try: + method = getattr(service, method_name) + return method(limit=15) + except Exception: + return default + except Exception: + return default + + def safety_check(self, recommendations: List[Dict[str, Any]]) -> Dict[str, Any]: + logger.info("Safety Guard decision requested: recommendations=%s", len(recommendations or [])) + approved: List[Dict[str, Any]] = [] + blocked: List[Dict[str, Any]] = [] + warnings: List[Dict[str, Any]] = [] + + for item in recommendations or []: + decision = self.evaluate_recommendation(item) + guarded = dict(item) + guarded.update( + { + "risk_level": decision["risk_level"], + "safety_status": decision["status"], + "safety_reason": decision["reason"], + "requires_backup": decision["requires_backup"], + "requires_restore_point": decision["requires_restore_point"], + "reversible": decision["reversible"], + "can_auto_apply": decision["can_auto_apply"], + "user_approval_required": True, + } + ) + if decision["status"] == "approved": + approved.append(guarded) + elif decision["status"] == "blocked": + blocked.append(guarded) + else: + warnings.append(guarded) + + result = { + "approved": approved, + "blocked": blocked, + "warnings": warnings, + "summary": { + "approved_count": len(approved), + "blocked_count": len(blocked), + "warning_count": len(warnings), + "gate": "pass" if approved or warnings else "blocked", + }, + "models": { + "safety": os.environ.get("AI_SAFETY_MODEL", self.SAFETY_MODEL), + }, + } + logger.info( + "Safety Guard decision: approved=%s warning=%s blocked=%s", + len(approved), + len(warnings), + len(blocked), + ) + return result + + def evaluate_recommendation(self, recommendation: Dict[str, Any]) -> Dict[str, Any]: + tweak_id = (recommendation.get("tweak_id") or recommendation.get("id") or "").strip().lower() + title = recommendation.get("title") or recommendation.get("name") or "" + description = recommendation.get("description") or recommendation.get("reason") or "" + risk_level = (recommendation.get("risk_level") or recommendation.get("risk") or "low").strip().lower() + text = f"{tweak_id} {title} {description}".lower() + + kb_tweak = self.knowledge_base.find_tweak(tweak_id) + if kb_tweak: + risk_level = (kb_tweak.get("risk_level") or risk_level).lower() + + reversible = bool(recommendation.get("reversible", kb_tweak.get("reversible", True))) + requires_backup = bool(recommendation.get("requires_backup", kb_tweak.get("requires_backup", risk_level in {"low", "medium", "high"}))) + requires_restore = bool(recommendation.get("requires_restore_point", kb_tweak.get("requires_restore_point", risk_level in {"medium", "high"}))) + + if tweak_id in self.BLOCKED_TWEAK_IDS or risk_level == "blocked" or any(term in text for term in self.BLOCKED_TERMS): + return self._decision( + "blocked", + "blocked", + "Blocked by HyperBoostX Safety Guard policy.", + requires_backup=True, + requires_restore_point=True, + reversible=False, + can_auto_apply=False, + ) + + if not reversible: + return self._decision( + "blocked", + "blocked", + "Tweak is not reversible, so it cannot be applied by HyperBoostX.", + requires_backup=True, + requires_restore_point=True, + reversible=False, + can_auto_apply=False, + ) + + if risk_level == "high": + return self._decision( + "warning", + "high", + "High-risk actions are manual review only and are not auto-applied.", + requires_backup=True, + requires_restore_point=True, + reversible=reversible, + can_auto_apply=False, + ) + + if risk_level == "medium" and not requires_backup: + return self._decision( + "blocked", + "blocked", + "Medium-risk tweak does not declare a backup path.", + requires_backup=True, + requires_restore_point=requires_restore, + reversible=reversible, + can_auto_apply=False, + ) + + can_auto_apply = bool(recommendation.get("can_auto_apply", kb_tweak.get("can_auto_apply", risk_level == "low"))) + if risk_level == "medium": + can_auto_apply = can_auto_apply and requires_backup + + return self._decision( + "approved", + risk_level if risk_level in {"low", "medium"} else "low", + "Approved after safety validation. User approval is still required.", + requires_backup=requires_backup, + requires_restore_point=requires_restore, + reversible=reversible, + can_auto_apply=can_auto_apply, + ) + + def assistant_summary( + self, + scan_result: Dict[str, Any], + analysis_result: Dict[str, Any], + safety_result: Dict[str, Any], + ) -> Dict[str, Any]: + logger.info("AI Assistant response requested") + local_text = self._local_assistant_summary(scan_result, analysis_result, safety_result) + return { + "message": local_text, + "status": self._status_snapshot(scan_result), + "actions": ["Apply Safe Boost", "Detail", "Skip", "Revert"], + "models": { + "assistant": os.environ.get("AI_ASSISTANT_MODEL", self.ASSISTANT_MODEL), + "analyzer": os.environ.get("AI_ANALYZER_MODEL", self.ANALYZER_MODEL), + "safety": os.environ.get("AI_SAFETY_MODEL", self.SAFETY_MODEL), + "embedding": os.environ.get("AI_EMBED_MODEL", self.EMBED_MODEL), + }, + } + + def optimize_game(self, game_name: str, scan_result: Dict[str, Any] | None = None) -> Dict[str, Any]: + scan_result = scan_result or {} + game = self.knowledge_base.find_game(game_name) + if not game: + game = { + "game": game_name or "Unknown game", + "engine": "Unknown", + "profile": "general-gaming", + "dlss_support": False, + "reflex_support": False, + "frame_generation_support": False, + "recommended_low_vram": ["Use lower textures", "cap FPS to a stable target"], + "recommended_mid_vram": ["Use medium/high textures if VRAM has headroom", "review overlays"], + "known_issues": ["No internal game profile found yet."], + } + + nvidia = scan_result.get("nvidia") or {} + vram = float(nvidia.get("vram_gb") or (scan_result.get("hardware", {}).get("gpu", {}).get("vram_gb") or 0)) + settings = game.get("recommended_low_vram") if vram and vram < 6 else game.get("recommended_mid_vram") + settings = settings or [] + recommendations = [ + { + "setting": "Recommended preset", + "value": "Competitive/Stable" if game.get("profile", "").startswith("latency") else "Balanced", + "expected_impact": "medium", + "risk_level": "low", + }, + { + "setting": "Texture quality", + "value": "Lower texture tier" if vram and vram < 6 else "Use higher texture only if VRAM headroom exists", + "expected_impact": "medium", + "risk_level": "low", + }, + { + "setting": "DLSS", + "value": self._feature_value(game.get("dlss_support"), nvidia.get("is_rtx"), "Quality/Balanced", "Off or unavailable"), + "expected_impact": "medium" if game.get("dlss_support") and nvidia.get("is_rtx") else "low", + "risk_level": "low", + }, + { + "setting": "NVIDIA Reflex", + "value": self._feature_value(game.get("reflex_support"), nvidia.get("is_nvidia"), "On", "Unavailable or game default"), + "expected_impact": "medium" if game.get("reflex_support") and nvidia.get("is_nvidia") else "low", + "risk_level": "low", + }, + { + "setting": "V-Sync / frame cap", + "value": "Use frame cap near stable refresh target; avoid guaranteed FPS claims.", + "expected_impact": "medium", + "risk_level": "low", + }, + ] + return { + "game": game.get("game"), + "engine": game.get("engine"), + "profile": game.get("profile"), + "recommendations": recommendations, + "setting_notes": settings, + "known_issues": game.get("known_issues") or [], + "risk_level": "low", + "manual_apply": True, + "disclaimer": "Results depend on game, driver, hardware, and current system load.", + } + + def create_report( + self, + scan_result: Dict[str, Any], + analysis_result: Dict[str, Any], + safety_result: Dict[str, Any], + apply_result: Dict[str, Any] | None = None, + reverted_result: Dict[str, Any] | None = None, + ) -> Dict[str, Any]: + report_id = f"report-{int(time.time())}" + report = { + "report_id": report_id, + "timestamp": datetime.now(timezone.utc).isoformat(), + "scan_id": scan_result.get("scan_id"), + "pc_health_score": (scan_result.get("scores") or {}).get("pc_health_score", 0), + "gaming_readiness_score": (scan_result.get("scores") or {}).get("gaming_readiness_score", 0), + "issues": analysis_result.get("issues", []), + "recommendations": analysis_result.get("recommendations", []), + "safety": { + "approved": len(safety_result.get("approved", [])), + "blocked": len(safety_result.get("blocked", [])), + "warnings": len(safety_result.get("warnings", [])), + }, + "applied": apply_result or {}, + "reverted": reverted_result or {}, + "language_policy": "No guaranteed FPS increase. HyperBoostX reports potential stability/latency benefits only.", + } + path = self.reports_dir / f"{report_id}.json" + try: + path.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8") + except Exception as exc: + logger.error("Failed to write performance report: %s", exc) + return report + + def load_report(self, report_id: str) -> Dict[str, Any]: + safe_id = re.sub(r"[^a-zA-Z0-9_-]", "", report_id or "") + if not safe_id: + return {} + path = self.reports_dir / f"{safe_id}.json" + if not path.exists(): + return {} + try: + return json.loads(path.read_text(encoding="utf-8")) + except Exception: + return {} + + def _local_analyze(self, scan_id: str, scan_result: Dict[str, Any], user_goal: str) -> Dict[str, Any]: + hardware = scan_result.get("hardware") or {} + windows = scan_result.get("windows") or {} + apps = scan_result.get("apps") or {} + performance = scan_result.get("performance") or {} + nvidia = scan_result.get("nvidia") or {} + scores = scan_result.get("scores") or {} + + issues: List[Dict[str, Any]] = [] + recommendations: List[Dict[str, Any]] = [] + + ram_usage = float(performance.get("ram_usage_percent") or 0) + cpu_usage = float(performance.get("cpu_usage_percent") or 0) + gpu_usage = float(performance.get("gpu_usage_percent") or 0) + disk_usage = float(performance.get("disk_usage_percent") or 0) + startup_high = int(apps.get("startup_high_impact") or 0) + process_count = int(apps.get("background_process_count") or performance.get("processes") or 0) + power_plan = str(windows.get("power_plan", "Unknown")) + game_mode = str(windows.get("game_mode", "Unknown")) + + if "high performance" not in power_plan.lower() and "ultimate" not in power_plan.lower(): + issues.append(self._issue("power_plan_not_optimized", "medium", 0.78, "Power plan is not performance-oriented.")) + recommendations.append(self._rec_from_tweak("optimize_power", "Set performance power plan", "Can help latency and frame pacing when Windows is power-limited.", "medium")) + + if game_mode.lower() in {"off", "disabled"}: + issues.append(self._issue("game_mode_disabled", "low", 0.72, "Windows Game Mode appears disabled.")) + recommendations.append(self._rec("enable_game_mode", "Enable Windows Game Mode", "Turns on Windows Game Mode for gaming sessions.", "low", "Can help Windows prioritize games.", expected="low")) + + if ram_usage >= 78: + issues.append(self._issue("ram_pressure", "medium", 0.82, "RAM usage is high and can cause stutter.")) + recommendations.append(self._rec("startup_review", "Review heavy startup apps", "Startup/background apps may be using RAM before gaming.", "low", "Review and disable only non-essential apps with approval.", can_apply=False, expected="medium")) + + if startup_high >= 2 or process_count >= 180: + issues.append(self._issue("background_apps_heavy", "medium", 0.8, "Heavy startup/background app load detected.")) + recommendations.append(self._rec("background_apps_review", "Close or disable non-essential background apps", "Review non-essential apps instead of force-closing protected tools.", "low", "Can reduce RAM pressure and background spikes.", can_apply=False, expected="medium")) + + if disk_usage >= 85: + issues.append(self._issue("storage_low_free_space", "medium", 0.76, "System drive free space is low.")) + recommendations.append(self._rec("cleanup_temp_files", "Clean temporary files with approval", "Clean only temporary/cache locations, never personal files.", "low", "Can reduce storage pressure.", can_apply=False, expected="low")) + + if cpu_usage >= 70 and gpu_usage and gpu_usage < 45: + issues.append(self._issue("possible_cpu_or_engine_limit", "medium", 0.7, "GPU usage is low while CPU usage is high; game engine/CPU/background load may be limiting FPS.")) + + if not nvidia.get("is_nvidia"): + issues.append(self._issue("nvidia_limited_support", "low", 0.9, "NVIDIA GPU was not detected; NVIDIA-specific recommendations are limited.")) + elif nvidia.get("driver_version") in {"", "Unknown", None}: + issues.append(self._issue("nvidia_driver_unknown", "low", 0.62, "NVIDIA driver version could not be read.")) + + if not issues: + issues.append(self._issue("pc_status_balanced", "low", 0.68, "No major performance bottleneck was detected in the basic scan.")) + + rag_context = self.knowledge_base.search( + " ".join([user_goal, *[issue["issue_type"] for issue in issues], str(hardware.get("gpu", {}).get("name", ""))]), + limit=6, + ) + + return { + "scan_id": scan_id, + "issues": issues, + "recommendations": self._dedupe_recommendations(recommendations), + "confidence": round(sum(issue["confidence"] for issue in issues) / max(len(issues), 1), 2), + "health_score": scores.get("pc_health_score", 0), + "gaming_readiness_score": scores.get("gaming_readiness_score", 0), + "rag_context": rag_context, + "ai_mode": "local_rule_based_fallback", + "models": { + "analyzer": os.environ.get("AI_ANALYZER_MODEL", self.ANALYZER_MODEL), + "embedding": os.environ.get("AI_EMBED_MODEL", self.EMBED_MODEL), + }, + } + + def _local_assistant_summary(self, scan_result: Dict[str, Any], analysis_result: Dict[str, Any], safety_result: Dict[str, Any]) -> str: + status = self._status_snapshot(scan_result) + issues = analysis_result.get("issues", [])[:3] + approved = safety_result.get("approved", [])[:4] + blocked = safety_result.get("blocked", [])[:3] + risk = self._overall_risk(safety_result) + + lines = [ + "Status PC:", + f"* CPU: {status['cpu']}", + f"* GPU: {status['gpu']}", + f"* RAM: {status['ram']}", + f"* Driver: {status['driver']}", + f"* Storage: {status['storage']}", + f"* Power Plan: {status['power_plan']}", + f"* Background Apps: {status['background_apps']}", + "", + "Masalah utama:", + f"* {issues[0]['reason'] if issues else 'Tidak ada masalah besar yang terdeteksi dari basic scan.'}", + "", + "Penyebab kemungkinan:", + ] + for index, issue in enumerate(issues, start=1): + lines.append(f"{index}. {issue.get('reason', issue.get('issue_type', 'Unknown'))}") + + lines.extend(["", "Rekomendasi aman:"]) + if approved: + for index, item in enumerate(approved, start=1): + lines.append(f"{index}. {item.get('title', item.get('tweak_id', 'Recommendation'))} - {item.get('risk_level', 'low').upper()}, bisa di-revert: {str(item.get('reversible', True)).lower()}.") + else: + lines.append("1. Tidak ada tweak otomatis yang disarankan. Gunakan review manual dan scan ulang setelah kondisi berubah.") + + if blocked: + lines.extend(["", "Diblokir Safety Guard:"]) + for item in blocked: + lines.append(f"* {item.get('title', item.get('tweak_id', 'Blocked tweak'))}: {item.get('safety_reason', 'Blocked by policy')}") + + lines.extend( + [ + "", + f"Risk level: {risk.upper()}", + "", + "Aksi:", + "* Apply Safe Boost", + "* Detail", + "* Skip", + "* Revert", + "", + "Catatan: rekomendasi ini berpotensi membantu stabilitas, stutter, atau latency. HyperBoostX tidak mengklaim FPS pasti naik.", + ] + ) + return "\n".join(lines) + + def _try_cloud_json(self, model: str, system_prompt: str, payload: Dict[str, Any]) -> Dict[str, Any]: + cloud_flag = os.environ.get("AI_CLOUD_ENABLED") + cloud_enabled = Config.AI_CLOUD_ENABLED if cloud_flag is None else str(cloud_flag).lower() in {"1", "true", "yes", "on"} + if not cloud_enabled: + return {} + api_key = os.environ.get("NVIDIA_API_KEY", "").strip() + if not api_key or requests is None: + return {} + base_url = os.environ.get("NVIDIA_BASE_URL", Config.NVIDIA_BASE_URL).rstrip("/") + timeout_ms = Config.AI_TIMEOUT_MS + retries = Config.AI_MAX_RETRIES + endpoint = Config.NVIDIA_CHAT_ENDPOINT if Config.NVIDIA_CHAT_ENDPOINT.startswith("/") else f"/{Config.NVIDIA_CHAT_ENDPOINT}" + candidate_models = [model] + if Config.AI_MODEL_AUTO_FALLBACK and model != Config.NVIDIA_FALLBACK_MODEL: + candidate_models.append(Config.NVIDIA_FALLBACK_MODEL) + + for candidate_model in candidate_models: + body = { + "model": candidate_model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, + ], + "temperature": 0.1, + "max_tokens": 1800, + } + for attempt in range(max(retries, 0) + 1): + try: + response = requests.post( + f"{base_url}{endpoint}", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + data=json.dumps(body), + timeout=max(1, timeout_ms / 1000), + ) + response.raise_for_status() + content = response.json()["choices"][0]["message"]["content"] + return self._parse_json_object(content) + except Exception as exc: + logger.warning("AI cloud call failed for %s on attempt %s: %s", candidate_model, attempt + 1, type(exc).__name__) + return {} + + @staticmethod + def _parse_json_object(text: str) -> Dict[str, Any]: + if not text: + return {} + try: + return json.loads(text) + except Exception: + match = re.search(r"\{.*\}", text, flags=re.DOTALL) + if not match: + return {} + try: + return json.loads(match.group(0)) + except Exception: + return {} + + @staticmethod + def _valid_analysis(payload: Dict[str, Any]) -> bool: + return isinstance(payload, dict) and isinstance(payload.get("issues"), list) and isinstance(payload.get("recommendations"), list) + + def _normalize_analysis(self, payload: Dict[str, Any], fallback: Dict[str, Any]) -> Dict[str, Any]: + normalized = dict(fallback) + normalized["issues"] = payload.get("issues") or fallback.get("issues", []) + normalized["recommendations"] = self._dedupe_recommendations(payload.get("recommendations") or fallback.get("recommendations", [])) + normalized["confidence"] = float(payload.get("confidence") or fallback.get("confidence") or 0) + normalized["health_score"] = payload.get("health_score", fallback.get("health_score", 0)) + normalized["gaming_readiness_score"] = payload.get("gaming_readiness_score", fallback.get("gaming_readiness_score", 0)) + normalized["ai_mode"] = "cloud_with_local_guardrails" + return normalized + + @staticmethod + def _sanitize_for_cloud(scan_result: Dict[str, Any]) -> Dict[str, Any]: + allowed = { + "scan_id", + "timestamp", + "hardware", + "windows", + "nvidia", + "apps", + "performance", + "scores", + "privacy", + } + return {key: value for key, value in scan_result.items() if key in allowed} + + @staticmethod + def _issue(issue_type: str, severity: str, confidence: float, reason: str) -> Dict[str, Any]: + return { + "issue_type": issue_type, + "severity": severity, + "confidence": confidence, + "reason": reason, + } + + def _rec_from_tweak(self, tweak_id: str, title: str, description: str, expected: str) -> Dict[str, Any]: + kb_tweak = self.knowledge_base.find_tweak(tweak_id) + return self._rec( + tweak_id=tweak_id, + title=title or kb_tweak.get("name", tweak_id), + description=description or kb_tweak.get("description", ""), + risk=kb_tweak.get("risk_level", "low"), + reason=kb_tweak.get("notes", description), + backup=kb_tweak.get("requires_backup", True), + restore=kb_tweak.get("requires_restore_point", False), + reversible=kb_tweak.get("reversible", True), + can_apply=kb_tweak.get("can_auto_apply", True), + expected=expected, + ) + + @staticmethod + def _rec( + tweak_id: str, + title: str, + description: str, + risk: str, + reason: str, + backup: bool = True, + restore: bool = False, + reversible: bool = True, + can_apply: bool = True, + expected: str = "medium", + ) -> Dict[str, Any]: + return { + "tweak_id": tweak_id, + "title": title, + "description": description, + "risk_level": risk, + "reason": reason, + "requires_backup": backup, + "requires_restore_point": restore, + "reversible": reversible, + "can_auto_apply": can_apply, + "user_approval_required": True, + "expected_impact": expected, + } + + @staticmethod + def _dedupe_recommendations(recommendations: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + seen = set() + deduped = [] + for item in recommendations: + key = item.get("tweak_id") or item.get("title") or json.dumps(item, sort_keys=True) + if key in seen: + continue + seen.add(key) + deduped.append(item) + return deduped + + @staticmethod + def _decision( + status: str, + risk_level: str, + reason: str, + requires_backup: bool, + requires_restore_point: bool, + reversible: bool, + can_auto_apply: bool, + ) -> Dict[str, Any]: + return { + "status": status, + "risk_level": risk_level, + "reason": reason, + "requires_backup": requires_backup, + "requires_restore_point": requires_restore_point, + "reversible": reversible, + "can_auto_apply": can_auto_apply, + } + + @staticmethod + def _status_snapshot(scan_result: Dict[str, Any]) -> Dict[str, str]: + hardware = scan_result.get("hardware") or {} + windows = scan_result.get("windows") or {} + apps = scan_result.get("apps") or {} + cpu = hardware.get("cpu") or {} + gpu = hardware.get("gpu") or {} + ram = hardware.get("ram") or {} + storage = hardware.get("storage") or {} + return { + "cpu": f"{cpu.get('name', 'Unknown')} ({cpu.get('usage_percent', 0)}% usage)", + "gpu": f"{gpu.get('name', 'Unknown')} ({gpu.get('vram_gb', 0)} GB VRAM)", + "ram": f"{ram.get('total_gb', 0)} GB total, {ram.get('usage_percent', 0)}% used", + "driver": gpu.get("driver_version", "Unknown"), + "storage": f"{storage.get('type', 'Unknown')}, {storage.get('free_gb', 0)} GB free", + "power_plan": windows.get("power_plan", "Unknown"), + "background_apps": f"{apps.get('background_process_count', 0)} processes, {apps.get('startup_high_impact', 0)} high-impact startup items", + } + + @staticmethod + def _overall_risk(safety_result: Dict[str, Any]) -> str: + levels = [ + str(item.get("risk_level", "low")).lower() + for item in (safety_result.get("approved") or []) + (safety_result.get("warnings") or []) + ] + if "high" in levels: + return "high" + if "medium" in levels: + return "medium" + return "low" + + @staticmethod + def _feature_value(game_support: Any, gpu_support: Any, supported: str, unsupported: str) -> str: + return supported if bool(game_support) and bool(gpu_support) else unsupported + + +class TripleAIEngine: + """Compatibility facade used by Flask, WPF, and tests. + + `TripleAiEngine` is the cloud-optional intelligence core. This facade owns + the product flow and safe tweak/revert bridge expected by the existing app. + """ + + ASSISTANT_MODEL = TripleAiEngine.ASSISTANT_MODEL + ANALYZER_MODEL = TripleAiEngine.ANALYZER_MODEL + SAFETY_MODEL = TripleAiEngine.SAFETY_MODEL + EMBED_MODEL = TripleAiEngine.EMBED_MODEL + + def __init__( + self, + knowledge_base: KnowledgeBaseService | None = None, + system_info_service: Any | None = None, + monitor_service: Any | None = None, + startup_service: Any | None = None, + tweak_service: Any | None = None, + ): + self.core = TripleAiEngine(knowledge_base=knowledge_base) + self.scanner = PcScannerService() + self.system_info_service = system_info_service + self.monitor_service = monitor_service + self.startup_service = startup_service + self.tweak_service = tweak_service or TweakService() + self.storage_dir = Config.DATA_DIR / "triple_ai" + self.storage_dir.mkdir(parents=True, exist_ok=True) + + def scan_pc(self) -> Dict[str, Any]: + logger.info("Triple AI scan started") + if self.system_info_service or self.monitor_service or self.startup_service: + scan = self._scan_from_injected_services() + else: + scan = self.scanner.scan_pc() + scan = self._add_legacy_scan_fields(scan) + logger.info("Triple AI scan completed: %s", scan.get("scan_id", "unknown")) + return scan + + def analyze(self, scan_result: Dict[str, Any], user_goal: str = "gaming", game: str = "") -> Dict[str, Any]: + result = self.core.analyze_scan( + scan_result.get("scan_id") or f"scan-{uuid.uuid4().hex[:8]}", + scan_result, + user_goal or "gaming", + ) + result.update( + { + "engine": "HyperBoostX Triple AI Engine", + "role": "AI Analyzer", + "model_target": os.environ.get("AI_ANALYZER_MODEL", self.ANALYZER_MODEL), + "game": game or "", + } + ) + for issue in result.get("issues", []): + if "description" not in issue and "reason" in issue: + issue["description"] = issue["reason"] + return result + + def safety_check(self, recommendations: List[Dict[str, Any]]) -> Dict[str, Any]: + result = self.core.safety_check(recommendations or []) + result.update( + { + "engine": "HyperBoostX Triple AI Engine", + "role": "AI Safety Guard", + "model_target": os.environ.get("AI_SAFETY_MODEL", self.SAFETY_MODEL), + } + ) + return result + + def assistant_response( + self, + scan_result: Dict[str, Any], + analysis_result: Dict[str, Any], + safety_result: Dict[str, Any], + ) -> Dict[str, Any]: + result = self.core.assistant_summary(scan_result, analysis_result, safety_result) + result.update( + { + "engine": "HyperBoostX Triple AI Engine", + "role": "AI Assistant", + "model_target": os.environ.get("AI_ASSISTANT_MODEL", self.ASSISTANT_MODEL), + "tagline": "Scan. Analyze. Boost. Revert.", + "risk_level": self.core._overall_risk(safety_result).title(), + "status_pc": result.get("status", {}), + "aksi": result.get("actions", ["Apply Safe Boost", "Detail", "Skip", "Revert"]), + "blocked_count": len(safety_result.get("blocked") or []), + "manual_review_count": len(safety_result.get("warnings") or []), + } + ) + return result + + def run_full_flow(self, user_goal: str = "gaming", game: str = "") -> Dict[str, Any]: + scan = self.scan_pc() + analysis = self.analyze(scan, user_goal=user_goal, game=game) + safety = self.safety_check(analysis.get("recommendations", [])) + assistant = self.assistant_response(scan, analysis, safety) + report = self.create_performance_report(scan, analysis, safety, assistant) + return { + "scan": scan, + "analysis": analysis, + "safety": safety, + "assistant": assistant, + "report": report, + } + + def optimize_game(self, game_name: str, scan_result: Dict[str, Any] | None = None) -> Dict[str, Any]: + return self.core.optimize_game(game_name, scan_result) + + def _kb_recommendation(self, tweak_id: str) -> Dict[str, Any]: + return self.core._kb_recommendation(tweak_id) + + def apply_safe_tweaks(self, approved_tweaks: List[Dict[str, Any]], user_approved: bool) -> Dict[str, Any]: + logger.info("Safe Tweak Engine apply request") + if not user_approved: + return { + "success": False, + "applied": [], + "failed": [], + "backup_id": "", + "error": "User approval is required before applying tweaks.", + } + + safety = self.safety_check(approved_tweaks) + applied: List[Dict[str, Any]] = [] + failed: List[Dict[str, Any]] = [] + backup_id = f"apply-{uuid.uuid4().hex[:12]}" + + for item in safety.get("approved", []): + tweak_id = item.get("tweak_id") + if not tweak_id or not item.get("can_auto_apply", False): + failed.append({"tweak_id": tweak_id or "unknown", "error": "Tweak is not auto-applicable."}) + continue + + result = self.tweak_service.apply_tweak(tweak_id) + if result.get("success"): + applied.append({"tweak_id": tweak_id, "result": result}) + else: + failed.append({"tweak_id": tweak_id, "error": result.get("error", "Apply failed.")}) + if item.get("risk_level") != "low": + break + + payload = { + "success": bool(applied) and not failed, + "applied": applied, + "failed": failed, + "backup_id": backup_id if applied else "", + "warnings": safety.get("warnings", []), + "blocked": safety.get("blocked", []), + } + self._write_json("applies", backup_id, payload) + return payload + + def revert_tweaks(self, backup_id: str = "", tweak_ids: List[str] | None = None) -> Dict[str, Any]: + logger.info("Revert started for backup_id=%s", self._safe_label(backup_id)) + ids = list(tweak_ids or []) + if not ids and backup_id: + previous = self._read_json("applies", backup_id) + ids = [item.get("tweak_id") for item in previous.get("applied", []) if item.get("tweak_id")] + + reverted: List[Dict[str, Any]] = [] + failed: List[Dict[str, Any]] = [] + for tweak_id in ids: + result = self.tweak_service.revert_tweak(tweak_id) + if result.get("success"): + reverted.append({"tweak_id": tweak_id, "result": result}) + else: + failed.append({"tweak_id": tweak_id, "error": result.get("error", "Revert failed.")}) + + return { + "success": not failed, + "reverted": reverted, + "failed": failed, + "backup_id": backup_id or "", + } + + def create_performance_report( + self, + scan_result: Dict[str, Any], + analysis_result: Dict[str, Any], + safety_result: Dict[str, Any], + assistant_result: Dict[str, Any] | None = None, + ) -> Dict[str, Any]: + report = self.core.create_report(scan_result, analysis_result, safety_result) + if assistant_result: + report["assistant_summary"] = assistant_result.get("message", "") + return report + + @staticmethod + def _cloud_enabled() -> bool: + cloud_flag = os.environ.get("AI_CLOUD_ENABLED") + cloud_enabled = Config.AI_CLOUD_ENABLED if cloud_flag is None else str(cloud_flag).lower() in {"1", "true", "yes", "on"} + return cloud_enabled and requests is not None and bool(os.environ.get("NVIDIA_API_KEY", "").strip()) + + def _scan_from_injected_services(self) -> Dict[str, Any]: + stats = self._safe_call(lambda: self.monitor_service.get_current_stats(), {}) if self.monitor_service else {} + cpu = self._safe_call(lambda: self.system_info_service.get_cpu_info(), {}) if self.system_info_service else {} + memory = self._safe_call(lambda: self.system_info_service.get_memory_info(), {}) if self.system_info_service else {} + disk = self._safe_call(lambda: self.system_info_service.get_disk_info(), {}) if self.system_info_service else {} + system_drive = self._safe_call(lambda: self.system_info_service.get_system_drive_info(), {}) if self.system_info_service else {} + os_info = self._safe_call(lambda: self.system_info_service.get_os_info(), {}) if self.system_info_service else {} + gpu_info = self._safe_call(lambda: self.system_info_service.get_gpu_info(), {}) if self.system_info_service else {} + startup_items = self._safe_call(lambda: self.startup_service.get_startup_items(), []) if self.startup_service else [] + processes = self._safe_call(lambda: self.monitor_service.get_process_list(limit=15), []) if self.monitor_service else [] + + first_gpu = (gpu_info.get("gpus") or [{}])[0] + live_gpu = stats.get("gpu") or {} + vram_gb = self._bytes_to_gb(first_gpu.get("vram")) or round(float(live_gpu.get("memory_total_mb") or 0) / 1024, 2) + disk_first = next(iter(disk.values()), {}) if isinstance(disk, dict) and disk else {} + scan = { + "scan_id": f"scan-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:8]}", + "timestamp": datetime.now(timezone.utc).isoformat(), + "hardware": { + "cpu": { + "name": cpu.get("processor", "Unknown"), + "cores": cpu.get("cores", 0), + "threads": cpu.get("threads", 0), + "usage_percent": stats.get("cpu", 0), + }, + "gpu": { + "name": first_gpu.get("name") or live_gpu.get("name") or "Unknown", + "driver_version": first_gpu.get("driver_version", "Unknown"), + "driver_date": first_gpu.get("driver_date", "Unknown"), + "vram_gb": vram_gb, + "usage_percent": live_gpu.get("load", 0), + "temperature_c": live_gpu.get("temperature", 0), + }, + "ram": { + "total_gb": self._bytes_to_gb(memory.get("total")) or float(stats.get("memory_total_gb") or 0), + "available_gb": self._bytes_to_gb(memory.get("available")), + "usage_percent": stats.get("memory", memory.get("percent", 0)), + "speed_mhz": memory.get("speed_mhz", 0), + }, + "storage": { + "system_drive": system_drive.get("drive_letter", "C"), + "type": system_drive.get("storage_class", "Unknown"), + "model": system_drive.get("model", "Unknown"), + "free_gb": self._bytes_to_gb(disk_first.get("free")), + "usage_percent": stats.get("disk", 0), + }, + }, + "windows": { + "version": os_info.get("release", "Unknown"), + "build_number": os_info.get("version", "Unknown"), + "power_plan": "Unknown", + "game_mode": "Unknown", + "hags": "Unknown", + }, + "nvidia": { + "is_nvidia": any(token in (first_gpu.get("name") or "").lower() for token in ("nvidia", "geforce", "rtx", "gtx")), + "is_rtx": "rtx" in (first_gpu.get("name") or "").lower(), + "gpu_name": first_gpu.get("name") or live_gpu.get("name") or "Unknown", + "driver_version": first_gpu.get("driver_version", "Unknown"), + "driver_date": first_gpu.get("driver_date", "Unknown"), + "vram_gb": vram_gb, + }, + "apps": { + "startup_count": len(startup_items), + "startup_high_impact": sum(1 for item in startup_items if str(item.get("impact", "")).lower() == "high"), + "background_process_count": stats.get("processes", len(processes)), + "top_background_apps": processes[:8], + }, + "performance": { + "cpu_usage_percent": stats.get("cpu", 0), + "ram_usage_percent": stats.get("memory", 0), + "disk_usage_percent": stats.get("disk", 0), + "gpu_usage_percent": live_gpu.get("load", 0), + "gpu_temperature_c": live_gpu.get("temperature", 0), + "processes": stats.get("processes", len(processes)), + }, + "privacy": { + "cloud_payload_note": "HyperBoostX only sends sanitized scan payloads when AI Cloud Analysis is enabled.", + "personal_paths_included": False, + "api_key_logged": False, + }, + } + scan["scores"] = PcScannerService.calculate_scores(scan) + return scan + + @staticmethod + def _add_legacy_scan_fields(scan: Dict[str, Any]) -> Dict[str, Any]: + hardware = scan.setdefault("hardware", {}) + cpu = hardware.get("cpu") or {} + gpu = hardware.get("gpu") or {} + ram = hardware.get("ram") or {} + storage = hardware.get("storage") or {} + hardware.setdefault("cpu_name", cpu.get("name", "Unknown")) + hardware.setdefault("gpu_name", gpu.get("name", "Unknown")) + hardware.setdefault("ram_total_gb", ram.get("total_gb", 0)) + hardware.setdefault("ram_speed_mhz", ram.get("speed_mhz", 0)) + hardware.setdefault("vram_mb", int(float(gpu.get("vram_gb") or 0) * 1024)) + hardware.setdefault("storage_type", storage.get("type", "Unknown")) + hardware.setdefault("storage_free_gb", storage.get("free_gb", 0)) + return scan + + @staticmethod + def _safe_call(callback, default): + try: + return callback() + except Exception as exc: + logger.debug("Triple AI compatibility probe skipped: %s", type(exc).__name__) + return default + + @staticmethod + def _bytes_to_gb(value: Any) -> float: + try: + return round(float(value or 0) / (1024 ** 3), 2) + except Exception: + return 0.0 + + @staticmethod + def _safe_label(value: str) -> str: + return re.sub(r"[^a-zA-Z0-9_.:-]", "_", str(value or ""))[:80] + + def _write_json(self, folder: str, name: str, payload: Dict[str, Any]) -> None: + target_dir = self.storage_dir / folder + target_dir.mkdir(parents=True, exist_ok=True) + target = target_dir / f"{self._safe_label(name)}.json" + target.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8") + + def _read_json(self, folder: str, name: str) -> Dict[str, Any]: + target = self.storage_dir / folder / f"{self._safe_label(name)}.json" + if not target.exists(): + return {} + try: + return json.loads(target.read_text(encoding="utf-8")) + except Exception: + return {} diff --git a/app/services/optimization/booster_service.py b/app/services/optimization/booster_service.py index cab827a..46512e3 100644 --- a/app/services/optimization/booster_service.py +++ b/app/services/optimization/booster_service.py @@ -2,12 +2,13 @@ import os import time -from typing import Dict, Any, List +from typing import Dict, Any, List, Optional import psutil import winreg from core.logger import Logger from core.profiles import ProfileManager +from core.restore import RestoreManager, RestorePoint from utils.shell import ShellUtil from utils.registry import RegistryUtil @@ -21,6 +22,7 @@ class BoosterService: _last_profile_id = "" _last_profile_started_at = 0.0 _profile_cooldown_seconds = 5.0 + _current_restore_point: Optional[RestorePoint] = None # Non-essential processes to potentially close NON_ESSENTIAL_PROCESSES = [ @@ -149,7 +151,7 @@ def apply_profile(profile_id: str) -> Dict[str, Any]: and BoosterService._last_profile_id == normalized_profile and now - BoosterService._last_profile_started_at < BoosterService._profile_cooldown_seconds ): - logger.warning( + logger.info( "Skipped duplicate booster apply for profile '%s' inside cooldown window.", normalized_profile ) @@ -168,11 +170,23 @@ def apply_profile(profile_id: str) -> Dict[str, Any]: return {"success": False, "error": f"Profile not found: {profile_id}"} results = [] - - # Apply each setting in the profile - for setting, enabled in profile.settings.items(): - if enabled: - results.append(BoosterService._apply_setting(setting)) + restore_point = RestoreManager.create_restore_point( + f"profile_{normalized_profile}", + f"Backup before applying booster profile {normalized_profile}" + ) + previous_restore_point = BoosterService._current_restore_point + BoosterService._current_restore_point = restore_point + + try: + # Apply each setting in the profile + for setting, enabled in profile.settings.items(): + if enabled: + results.append(BoosterService._apply_setting(setting)) + finally: + BoosterService._current_restore_point = previous_restore_point + + if restore_point.registry or restore_point.settings or restore_point.files: + RestoreManager.save_restore_point(restore_point) success_count = sum(1 for r in results if r["success"]) total_count = len(results) @@ -187,6 +201,10 @@ def apply_profile(profile_id: str) -> Dict[str, Any]: "message": f"Profile '{profile.name}' applied successfully", "applied_settings": success_count, "total_settings": total_count, + "restore_point": restore_point.name, + "restore_timestamp": restore_point.timestamp, + "registry_backups": len(restore_point.registry), + "settings_backups": len(restore_point.settings), "results": results } @@ -195,7 +213,16 @@ def apply_profile(profile_id: str) -> Dict[str, Any]: f"Profile '{profile.name}' applied with limited access. " f"{success_count}/{total_count} settings succeeded." ) - logger.warning(f"{warning} Restricted settings: {failed_settings}") + expected_restriction_codes = {"admin_required", "feature_unavailable"} + expected_restrictions = all( + r.get("reason_code") in expected_restriction_codes + for r in results + if not r["success"] + ) + if expected_restrictions: + logger.info("%s Restricted settings: %s", warning, failed_settings) + else: + logger.warning("%s Restricted settings: %s", warning, failed_settings) return { "success": True, "partial_success": True, @@ -203,15 +230,24 @@ def apply_profile(profile_id: str) -> Dict[str, Any]: "warning": "Some tweaks need Administrator privileges or are unavailable on this Windows setup.", "applied_settings": success_count, "total_settings": total_count, + "restore_point": restore_point.name, + "restore_timestamp": restore_point.timestamp, + "registry_backups": len(restore_point.registry), + "settings_backups": len(restore_point.settings), "restricted_settings": failed_settings, "failed_settings": failed_settings, "results": results } + if restore_point.registry or restore_point.settings or restore_point.files: + RestoreManager.restore(restore_point) + return { "success": False, "partial_success": False, "error": f"Profile could not be applied: 0/{total_count} settings successful", + "restore_point": restore_point.name, + "restore_timestamp": restore_point.timestamp, "failed_settings": failed_settings, "results": results } @@ -352,11 +388,46 @@ def _build_failed_setting_result(setting: str) -> Dict[str, Any]: reason_code="apply_failed", message=f"{display_name} could not be applied on this machine." ) + + @staticmethod + def _set_registry_with_profile_backup( + path: str, + key: str, + value: Any, + value_type=winreg.REG_SZ, + hkey=winreg.HKEY_LOCAL_MACHINE, + ) -> bool: + restore_point = BoosterService._current_restore_point + if restore_point is not None: + backup_ok = RestoreManager.backup_registry( + restore_point, + hkey, + path, + key, + value, + value_type, + ) + if not backup_ok: + return False + + return RegistryUtil.set_value(path, key, value, value_type, hkey=hkey) + + @staticmethod + def _set_power_plan_with_profile_backup(scheme_guid: str) -> bool: + restore_point = BoosterService._current_restore_point + if restore_point is not None and not RestoreManager.backup_power_plan(restore_point, scheme_guid): + return False + + success, _ = ShellUtil.execute_command( + f"powercfg /setactive {scheme_guid}", + admin=True + ) + return success @staticmethod def _disable_background_apps() -> bool: - """Close non-essential background applications.""" - closed_count = 0 + """Preview non-essential background applications instead of closing them implicitly.""" + preview_targets = [] for proc in psutil.process_iter(['pid', 'name']): try: @@ -364,12 +435,17 @@ def _disable_background_apps() -> bool: if not BoosterService.should_close_process(process_name): continue - proc.kill() - closed_count += 1 - logger.info(f"Closed process: {proc.info['name']}") + preview_targets.append(proc.info.get('name') or process_name) except (psutil.NoSuchProcess, psutil.AccessDenied): pass - logger.info(f"Closed {closed_count} background applications") + + if preview_targets: + logger.info( + "Background app close preview only. Matching targets: %s", + sorted(set(preview_targets), key=str.lower) + ) + else: + logger.info("Background app close preview found no matching targets.") return True @staticmethod @@ -401,7 +477,7 @@ def _set_high_cpu_priority() -> bool: @staticmethod def _disable_visual_effects() -> bool: """Disable visual effects for performance.""" - return RegistryUtil.set_value( + return BoosterService._set_registry_with_profile_backup( r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VisualEffects", "VisualFXSetting", 2, # Adjust for best performance @@ -413,7 +489,7 @@ def _disable_visual_effects() -> bool: def _increase_timer_resolution() -> bool: """Increase timer resolution for better responsiveness.""" # This requires calling timeBeginPeriod(1) - we'll use a registry approach - return RegistryUtil.set_value( + return BoosterService._set_registry_with_profile_backup( BoosterService.REG_PATHS["timer_resolution"], "GlobalTimerResolutionRequests", 1, @@ -423,7 +499,7 @@ def _increase_timer_resolution() -> bool: @staticmethod def _disable_xbox_overlay() -> bool: """Disable Xbox Game Bar overlay.""" - return RegistryUtil.set_value( + return BoosterService._set_registry_with_profile_backup( BoosterService.REG_PATHS["xbox_overlay"], "AppCaptureEnabled", 0, @@ -435,20 +511,19 @@ def _disable_xbox_overlay() -> bool: def _optimize_gpu_performance() -> bool: """Optimize GPU for performance.""" # Set power scheme to high performance for GPU - success, _ = ShellUtil.execute_command("powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c", admin=True) - return success + return BoosterService._set_power_plan_with_profile_backup("8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c") @staticmethod def _optimize_frame_times() -> bool: """Optimize for stable frame times.""" # Disable dynamic tick and other timing optimizations - success1 = RegistryUtil.set_value( + success1 = BoosterService._set_registry_with_profile_backup( r"SYSTEM\CurrentControlSet\Control\Power\PowerSettings\54533251-82be-4824-96c1-47b60b740d00\0cc5b647-c1df-4637-891a-dec35c318583", "ValueMax", 0, winreg.REG_DWORD ) - success2 = RegistryUtil.set_value( + success2 = BoosterService._set_registry_with_profile_backup( r"SYSTEM\CurrentControlSet\Control\Power\PowerSettings\54533251-82be-4824-96c1-47b60b740d00\0cc5b647-c1df-4637-891a-dec35c318583", "ValueMin", 0, @@ -469,7 +544,7 @@ def _reduce_network_latency() -> bool: @staticmethod def _enable_background_recording() -> bool: """Enable background recording for streaming.""" - return RegistryUtil.set_value( + return BoosterService._set_registry_with_profile_backup( BoosterService.REG_PATHS["xbox_overlay"], "HistoricalCaptureEnabled", 1, @@ -480,14 +555,14 @@ def _enable_background_recording() -> bool: @staticmethod def _disable_background_recording() -> bool: """Disable Xbox/Game Bar background recording to protect streaming encoder stability.""" - success_capture = RegistryUtil.set_value( + success_capture = BoosterService._set_registry_with_profile_backup( BoosterService.REG_PATHS["xbox_overlay"], "AppCaptureEnabled", 0, winreg.REG_DWORD, hkey=winreg.HKEY_CURRENT_USER ) - success_history = RegistryUtil.set_value( + success_history = BoosterService._set_registry_with_profile_backup( BoosterService.REG_PATHS["xbox_overlay"], "HistoricalCaptureEnabled", 0, @@ -499,13 +574,12 @@ def _disable_background_recording() -> bool: @staticmethod def _set_balanced_performance() -> bool: """Set balanced performance power plan.""" - success, _ = ShellUtil.execute_command("powercfg /setactive 381b4222-f694-41f0-9685-ff5bb260df2e", admin=True) - return success + return BoosterService._set_power_plan_with_profile_backup("381b4222-f694-41f0-9685-ff5bb260df2e") @staticmethod def _enable_indexing() -> bool: """Enable Windows Search indexing.""" - return RegistryUtil.set_value( + return BoosterService._set_registry_with_profile_backup( BoosterService.REG_PATHS["indexing"], "Start", 2, # Automatic @@ -515,7 +589,7 @@ def _enable_indexing() -> bool: @staticmethod def _enable_visual_effects() -> bool: """Enable normal visual effects.""" - return RegistryUtil.set_value( + return BoosterService._set_registry_with_profile_backup( r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VisualEffects", "VisualFXSetting", 1, # Let Windows choose @@ -535,8 +609,7 @@ def _optimize_network() -> bool: @staticmethod def _reduce_cpu_frequency() -> bool: """Reduce CPU frequency for battery saving.""" - success, _ = ShellUtil.execute_command("powercfg /setactive a1841308-3541-4fab-bc81-f71556f20b4a", admin=True) - return success + return BoosterService._set_power_plan_with_profile_backup("a1841308-3541-4fab-bc81-f71556f20b4a") @staticmethod def _dim_display() -> bool: @@ -550,7 +623,7 @@ def _dim_display() -> bool: @staticmethod def _disable_background_sync() -> bool: """Disable background sync and delivery optimization.""" - return RegistryUtil.set_value( + return BoosterService._set_registry_with_profile_backup( BoosterService.REG_PATHS["background_sync"], "DODownloadMode", 0, # Disabled @@ -560,5 +633,4 @@ def _disable_background_sync() -> bool: @staticmethod def _set_low_power_mode() -> bool: """Set power saver mode.""" - success, _ = ShellUtil.execute_command("powercfg /setactive a1841308-3541-4fab-bc81-f71556f20b4a", admin=True) - return success + return BoosterService._set_power_plan_with_profile_backup("a1841308-3541-4fab-bc81-f71556f20b4a") diff --git a/app/services/optimization/tweak_service.py b/app/services/optimization/tweak_service.py index c25b346..dbdd631 100644 --- a/app/services/optimization/tweak_service.py +++ b/app/services/optimization/tweak_service.py @@ -3,6 +3,7 @@ import winreg from typing import List, Dict, Any, Optional from core.logger import Logger +from core.permissions import Permissions from utils.registry import RegistryUtil from utils.shell import ShellUtil from core.restore import RestoreManager, RestorePoint @@ -18,60 +19,95 @@ class TweakService: { "id": "disable_defender", "name": "Disable Windows Defender", - "description": "Disables Windows Defender real-time protection", - "risk": "High", + "description": "Blocked by HyperBoostX Safety Guard; Windows Security must stay enabled.", + "risk": "Blocked", + "risk_level": "blocked", "category": "Security", - "requires_admin": True + "requires_admin": True, + "can_auto_apply": False, + "reversible": False }, { "id": "optimize_visual", "name": "Optimize Visual Effects", "description": "Disables unnecessary visual effects for better performance", "risk": "Low", + "risk_level": "low", "category": "Performance", - "requires_admin": False + "requires_admin": False, + "can_auto_apply": True, + "reversible": True + }, + { + "id": "enable_game_mode", + "name": "Enable Windows Game Mode", + "description": "Enables Windows Game Mode for gaming sessions", + "risk": "Low", + "risk_level": "low", + "category": "Gaming", + "requires_admin": False, + "can_auto_apply": True, + "reversible": True }, { "id": "disable_telemetry", "name": "Disable Telemetry", "description": "Disables Windows telemetry and data collection", "risk": "Medium", + "risk_level": "medium", "category": "Privacy", - "requires_admin": True + "requires_admin": True, + "can_auto_apply": True, + "reversible": True }, { "id": "disable_xbox", "name": "Disable Xbox Game Bar", "description": "Disables Xbox Game Bar and related overlays", "risk": "Low", + "risk_level": "low", "category": "Gaming", - "requires_admin": False + "requires_admin": False, + "can_auto_apply": True, + "reversible": True }, { "id": "disable_updates", "name": "Disable Auto Updates", - "description": "Disables automatic Windows updates", - "risk": "High", + "description": "Blocked by HyperBoostX Safety Guard; permanent Windows Update disable is not allowed.", + "risk": "Blocked", + "risk_level": "blocked", "category": "Maintenance", - "requires_admin": True + "requires_admin": True, + "can_auto_apply": False, + "reversible": False }, { "id": "disable_superfetch", "name": "Disable Superfetch/SysMain", "description": "Disables Superfetch service to reduce disk activity", "risk": "Medium", + "risk_level": "medium", "category": "Performance", - "requires_admin": True + "requires_admin": True, + "can_auto_apply": True, + "reversible": True }, { "id": "optimize_power", "name": "Optimize Power Settings", "description": "Sets power plan to high performance", "risk": "Low", + "risk_level": "low", "category": "Performance", - "requires_admin": True + "requires_admin": True, + "can_auto_apply": True, + "reversible": True } ] + + HIGH_RISK_TWEAKS = {"disable_defender", "disable_updates"} + BLOCKED_TWEAKS = {"disable_defender", "disable_updates"} # Registry paths for tweaks REG_PATHS = { @@ -100,11 +136,47 @@ def get_tweak_info(tweak_id: str) -> Optional[Dict[str, Any]]: return None @staticmethod - def apply_tweak(tweak_id: str) -> Dict[str, Any]: + def apply_tweak(tweak_id: str, expert_mode: bool = False, confirmed: bool = False) -> Dict[str, Any]: """Apply a tweak with backup and error handling.""" logger.info(f"Applying tweak: {tweak_id}") try: + tweak = TweakService.get_tweak_info(tweak_id) + if not tweak: + return {"success": False, "error": f"Unknown tweak: {tweak_id}"} + + if tweak_id in TweakService.BLOCKED_TWEAKS: + logger.warning("Safety Guard blocked tweak: %s", tweak_id) + return { + "success": False, + "error": f"Tweak {tweak_id} is blocked by HyperBoostX Safety Guard.", + "safety_status": "blocked", + "risk_level": "blocked", + "requires_expert_mode": True, + "can_auto_apply": False, + "reversible": False, + } + + if tweak_id in TweakService.HIGH_RISK_TWEAKS: + if not expert_mode: + return { + "success": False, + "error": f"Tweak {tweak_id} is high risk and requires Expert Mode.", + "requires_expert_mode": True, + } + if not confirmed: + return { + "success": False, + "error": f"Tweak {tweak_id} requires explicit double confirmation.", + "requires_confirmation": True, + } + if not Permissions.is_admin(): + return { + "success": False, + "error": f"Tweak {tweak_id} requires Administrator privileges.", + "requires_admin": True, + } + # Create restore point restore_point = RestoreManager.create_restore_point( f"tweak_{tweak_id}", @@ -117,6 +189,8 @@ def apply_tweak(tweak_id: str) -> Dict[str, Any]: success = TweakService._apply_disable_defender(restore_point) elif tweak_id == "optimize_visual": success = TweakService._apply_optimize_visual(restore_point) + elif tweak_id == "enable_game_mode": + success = TweakService._apply_enable_game_mode(restore_point) elif tweak_id == "disable_telemetry": success = TweakService._apply_disable_telemetry(restore_point) elif tweak_id == "disable_xbox": @@ -131,8 +205,16 @@ def apply_tweak(tweak_id: str) -> Dict[str, Any]: return {"success": False, "error": f"Unknown tweak: {tweak_id}"} if success: + RestoreManager.save_restore_point(restore_point) logger.info(f"Successfully applied tweak: {tweak_id}") - return {"success": True, "message": f"Tweak {tweak_id} applied successfully"} + return { + "success": True, + "message": f"Tweak {tweak_id} applied successfully", + "restore_point": restore_point.name, + "restore_timestamp": restore_point.timestamp, + "registry_backups": len(restore_point.registry), + "settings_backups": len(restore_point.settings), + } else: # Attempt to restore if application failed RestoreManager.restore(restore_point) @@ -148,43 +230,78 @@ def revert_tweak(tweak_id: str) -> Dict[str, Any]: logger.info(f"Reverting tweak: {tweak_id}") try: - # Find the latest restore point for this tweak - # For now, we'll need to implement restore point management - # This is a simplified version - return {"success": True, "message": f"Tweak {tweak_id} reverted successfully"} + if not TweakService.get_tweak_info(tweak_id): + return {"success": False, "error": f"Unknown tweak: {tweak_id}"} + + restore_point = RestoreManager.find_latest_restore_point(f"tweak_{tweak_id}") + if not restore_point: + return { + "success": False, + "error": f"No restore backup found for tweak: {tweak_id}", + } + + if not restore_point.registry and not restore_point.files and not restore_point.settings: + return { + "success": False, + "error": f"Restore backup for {tweak_id} has no restorable entries.", + } + + restored = RestoreManager.restore(restore_point) + if not restored: + return { + "success": False, + "error": f"Failed to revert tweak: {tweak_id}", + "restore_timestamp": restore_point.timestamp, + } + + return { + "success": True, + "message": f"Tweak {tweak_id} reverted successfully", + "restore_timestamp": restore_point.timestamp, + "registry_restored": len(restore_point.registry), + "settings_restored": len(restore_point.settings), + } except Exception as e: logger.error(f"Error reverting tweak {tweak_id}: {e}") return {"success": False, "error": str(e)} + + @staticmethod + def _set_registry_with_backup( + restore_point: RestorePoint, + path: str, + key: str, + value: Any, + value_type=winreg.REG_SZ, + hkey=winreg.HKEY_LOCAL_MACHINE, + ) -> bool: + backup_ok = RestoreManager.backup_registry( + restore_point, + hkey, + path, + key, + value, + value_type, + ) + if not backup_ok: + return False + + return RegistryUtil.set_value(path, key, value, value_type, hkey=hkey) @staticmethod def _apply_disable_defender(restore_point: RestorePoint) -> bool: """Disable Windows Defender real-time protection.""" try: - # Backup current settings - current_value = RegistryUtil.get_value( - TweakService.REG_PATHS["defender_realtime"], - "DisableRealtimeMonitoring" - ) - if current_value is not None: - restore_point.files[f"reg:{TweakService.REG_PATHS['defender_realtime']}\\DisableRealtimeMonitoring"] = str(current_value) - # Disable real-time monitoring - success1 = RegistryUtil.set_value( + success1 = TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["defender_realtime"], "DisableRealtimeMonitoring", 1, winreg.REG_DWORD ) - # Disable reporting - current_reporting = RegistryUtil.get_value( - TweakService.REG_PATHS["defender_reporting"], - "UILockdown" - ) - if current_reporting is not None: - restore_point.files[f"reg:{TweakService.REG_PATHS['defender_reporting']}\\UILockdown"] = str(current_reporting) - - success2 = RegistryUtil.set_value( + success2 = TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["defender_reporting"], "UILockdown", 1, @@ -200,53 +317,49 @@ def _apply_disable_defender(restore_point: RestorePoint) -> bool: def _apply_optimize_visual(restore_point: RestorePoint) -> bool: """Optimize visual effects for performance.""" try: - # Backup current visual effects setting - current_value = RegistryUtil.get_value( - TweakService.REG_PATHS["visual_effects"], - "VisualFXSetting" - ) - if current_value is not None: - restore_point.files[f"reg:{TweakService.REG_PATHS['visual_effects']}\\VisualFXSetting"] = str(current_value) - # Set to "Adjust for best performance" (value = 2) - return RegistryUtil.set_value( + return TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["visual_effects"], "VisualFXSetting", 2, - winreg.REG_DWORD + winreg.REG_DWORD, + hkey=winreg.HKEY_CURRENT_USER ) except Exception as e: logger.error(f"Failed to optimize visual effects: {e}") return False + + @staticmethod + def _apply_enable_game_mode(restore_point: RestorePoint) -> bool: + """Enable Windows Game Mode for the current user.""" + try: + return TweakService._set_registry_with_backup( + restore_point, + r"Software\Microsoft\GameBar", + "AutoGameModeEnabled", + 1, + winreg.REG_DWORD, + hkey=winreg.HKEY_CURRENT_USER + ) + except Exception as e: + logger.error(f"Failed to enable Game Mode: {e}") + return False @staticmethod def _apply_disable_telemetry(restore_point: RestorePoint) -> bool: """Disable Windows telemetry.""" try: - # Disable telemetry via policy - current_policy = RegistryUtil.get_value( - TweakService.REG_PATHS["telemetry_policy"], - "AllowTelemetry" - ) - if current_policy is not None: - restore_point.files[f"reg:{TweakService.REG_PATHS['telemetry_policy']}\\AllowTelemetry"] = str(current_policy) - - success1 = RegistryUtil.set_value( + success1 = TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["telemetry_policy"], "AllowTelemetry", 0, winreg.REG_DWORD ) - # Disable DiagTrack service consent - current_consent = RegistryUtil.get_value( - TweakService.REG_PATHS["telemetry_consent"], - "ShowedToastAtLevel" - ) - if current_consent is not None: - restore_point.files[f"reg:{TweakService.REG_PATHS['telemetry_consent']}\\ShowedToastAtLevel"] = str(current_consent) - - success2 = RegistryUtil.set_value( + success2 = TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["telemetry_consent"], "ShowedToastAtLevel", 0, @@ -262,17 +375,8 @@ def _apply_disable_telemetry(restore_point: RestorePoint) -> bool: def _apply_disable_xbox(restore_point: RestorePoint) -> bool: """Disable Xbox Game Bar overlay.""" try: - current_value = RegistryUtil.get_value( - TweakService.REG_PATHS["xbox_overlay"], - "AppCaptureEnabled", - hkey=winreg.HKEY_CURRENT_USER - ) - if current_value is not None: - restore_point.files[ - f"reg:{TweakService.REG_PATHS['xbox_overlay']}\\AppCaptureEnabled" - ] = str(current_value) - - return RegistryUtil.set_value( + return TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["xbox_overlay"], "AppCaptureEnabled", 0, @@ -287,16 +391,9 @@ def _apply_disable_xbox(restore_point: RestorePoint) -> bool: def _apply_disable_updates(restore_point: RestorePoint) -> bool: """Disable automatic Windows updates.""" try: - # Backup current AU options - current_au = RegistryUtil.get_value( - TweakService.REG_PATHS["updates_policy"], - "AUOptions" - ) - if current_au is not None: - restore_point.files[f"reg:{TweakService.REG_PATHS['updates_policy']}\\AUOptions"] = str(current_au) - # Set AUOptions to "Never check for updates" (value = 1) - return RegistryUtil.set_value( + return TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["updates_policy"], "AUOptions", 1, @@ -310,16 +407,9 @@ def _apply_disable_updates(restore_point: RestorePoint) -> bool: def _apply_disable_superfetch(restore_point: RestorePoint) -> bool: """Disable Superfetch/SysMain service.""" try: - # Backup current start value - current_start = RegistryUtil.get_value( - TweakService.REG_PATHS["superfetch"], - "Start" - ) - if current_start is not None: - restore_point.files[f"reg:{TweakService.REG_PATHS['superfetch']}\\Start"] = str(current_start) - # Set service to disabled (value = 4) - success = RegistryUtil.set_value( + success = TweakService._set_registry_with_backup( + restore_point, TweakService.REG_PATHS["superfetch"], "Start", 4, @@ -340,8 +430,12 @@ def _apply_optimize_power(restore_point: RestorePoint) -> bool: """Set power plan to high performance.""" try: # Use powercfg to set high performance plan + scheme_guid = "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c" + if not RestoreManager.backup_power_plan(restore_point, scheme_guid): + return False + success, output = ShellUtil.execute_command( - "powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c", + f"powercfg /setactive {scheme_guid}", admin=True ) return success diff --git a/app/utils/registry.py b/app/utils/registry.py index 8ead606..31943e1 100644 --- a/app/utils/registry.py +++ b/app/utils/registry.py @@ -78,7 +78,7 @@ def get_value(path: str, key: str, hkey=winreg.HKEY_LOCAL_MACHINE) -> Optional[A "error": str(e), } ) - logger.error( + logger.info( "Failed to get registry value %s at %s: access denied (%s)", key, RegistryUtil._format_location(path, hkey), @@ -131,7 +131,7 @@ def set_value(path: str, key: str, value: Any, value_type=winreg.REG_SZ, hkey=wi "error": str(e), } ) - logger.warning( + logger.info( "Failed to set registry value %s at %s: access denied. " "This tweak likely requires Administrator privileges. (%s)", key, @@ -151,7 +151,7 @@ def set_value(path: str, key: str, value: Any, value_type=winreg.REG_SZ, hkey=wi "error": str(e), } ) - logger.warning( + logger.info( "Failed to set registry value %s at %s: registry path is unavailable on this Windows setup. (%s)", key, RegistryUtil._format_location(path, hkey), diff --git a/app/utils/shell.py b/app/utils/shell.py index bd32f82..a09ed7d 100644 --- a/app/utils/shell.py +++ b/app/utils/shell.py @@ -4,6 +4,7 @@ """ import subprocess +import re from typing import Tuple from core.logger import Logger from core.permissions import Permissions @@ -15,6 +16,20 @@ class ShellUtil: """Shell command execution utility.""" + DEFAULT_TIMEOUT_SECONDS = 45 + ALLOWED_COMMAND_PATTERNS = ( + re.compile(r"^powercfg\s+/setactive\s+[0-9a-fA-F-]{36}$", re.IGNORECASE), + re.compile(r"^powercfg\s+/change\s+monitor-timeout-dc\s+(?:[1-9]\d{0,3}|0)$", re.IGNORECASE), + re.compile(r"^ipconfig\s+/flushdns$", re.IGNORECASE), + re.compile(r"^netsh\s+int(?:erface)?\s+tcp\s+set\s+global\s+(autotuninglevel=normal|chimney=disabled)$", re.IGNORECASE), + re.compile(r"^netsh\s+int(?:erface)?\s+ip\s+reset$", re.IGNORECASE), + re.compile(r"^netsh\s+winsock\s+reset$", re.IGNORECASE), + re.compile(r"^sfc\s+/scannow$", re.IGNORECASE), + re.compile(r"^dism\s+/online\s+/cleanup-image\s+/restorehealth$", re.IGNORECASE), + re.compile(r"^stop-service\s+-name\s+sysmain$", re.IGNORECASE), + re.compile(r"^write-output\s+['\"][^'\"]{1,120}['\"]$", re.IGNORECASE), + ) + @staticmethod def _powershell_args(command: str) -> list[str]: return [ @@ -28,36 +43,73 @@ def _powershell_args(command: str) -> list[str]: ] @staticmethod - def execute_command(command: str, admin: bool = False) -> Tuple[bool, str]: + def _is_allowed(command: str) -> bool: + normalized = " ".join((command or "").strip().split()) + return any(pattern.match(normalized) for pattern in ShellUtil.ALLOWED_COMMAND_PATTERNS) + + @staticmethod + def _describe_command(command: str) -> str: + normalized = " ".join((command or "").strip().split()) + if not normalized: + return "empty command" + + lower = normalized.lower() + if lower.startswith("powercfg"): + return "powercfg setactive" + if lower.startswith("ipconfig"): + return "ipconfig flushdns" + if lower.startswith("netsh"): + return "netsh network setting" + if lower.startswith("sfc"): + return "sfc scan" + if lower.startswith("dism"): + return "dism restorehealth" + if lower.startswith("stop-service"): + return "service control" + if lower.startswith("write-output"): + return "write-output" + + return normalized.split()[0] + + @staticmethod + def execute_command(command: str, admin: bool = False, timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS) -> Tuple[bool, str]: """Execute shell command.""" try: + command_description = ShellUtil._describe_command(command) + if not ShellUtil._is_allowed(command): + logger.warning("Blocked non-allowlisted shell command: %s", command_description) + return False, "Command is not allowed by HyperBoost X safety policy." + if admin and not Permissions.is_admin(): message = "This action requires administrator privileges. Run HyperBoost X as Administrator." - logger.warning(f"Admin command blocked without elevation: {command}") + logger.info("Admin command skipped without elevation: %s", command_description) return False, message - process = subprocess.Popen( + process = subprocess.run( ShellUtil._powershell_args(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, + timeout=max(1, int(timeout_seconds)), ) - - stdout, stderr = process.communicate() success = process.returncode == 0 + stdout = process.stdout + stderr = process.stderr if success: - logger.info(f"Command executed: {command}") + logger.info("Command executed: %s", command_description) else: stderr = (stderr or "").strip() stdout = (stdout or "").strip() details = stderr or stdout or "Command failed without output." - logger.error(f"Command failed: {command} - {details}") + logger.error("Command failed: %s - %s", command_description, details) output = (stdout or "").strip() if success else ((stderr or "").strip() or (stdout or "").strip()) return success, output + except subprocess.TimeoutExpired: + return False, f"Command timed out after {timeout_seconds} seconds." except Exception as e: - logger.error(f"Failed to execute command: {e}") + logger.error("Failed to execute shell command: %s", e) return False, str(e) @staticmethod diff --git a/dotnet-tests/HyperBoostX.Tests/AppConfigServiceTests.cs b/dotnet-tests/HyperBoostX.Tests/AppConfigServiceTests.cs index fbc544e..0e86bec 100644 --- a/dotnet-tests/HyperBoostX.Tests/AppConfigServiceTests.cs +++ b/dotnet-tests/HyperBoostX.Tests/AppConfigServiceTests.cs @@ -1,5 +1,8 @@ -using HyperBoostX.Services; +using System; using System.IO; +using System.Threading.Tasks; +using HyperBoostX.Services; +using Newtonsoft.Json; using Xunit; namespace HyperBoostX.Tests; @@ -7,33 +10,65 @@ namespace HyperBoostX.Tests; public class AppConfigServiceTests { [Fact] - public async Task SaveAndLoad_RoundTripsSettings() + public void PersistedSettingsState_DoesNotSerializePlaintextSecrets() { - var tempRoot = Path.Combine(Path.GetTempPath(), "hyperboostx-tests", Guid.NewGuid().ToString("N")); - var service = new AppConfigService(tempRoot); var config = new PersistedAppConfig { Settings = new PersistedSettingsState { - Theme = "Dark", - Language = "id-ID", - AutomationMode = "Safe Autonomous" + NvidiaApiKey = "nvapi-test-secret", + DiscordWebhookUrl = "https://discord.com/api/webhooks/123/secret", + DiscordUpdateWebhookUrl = "https://discord.com/api/webhooks/456/secret" } }; + var json = JsonConvert.SerializeObject(config, Formatting.Indented); + + Assert.DoesNotContain("NvidiaApiKey", json); + Assert.DoesNotContain("DiscordWebhookUrl", json); + Assert.DoesNotContain("DiscordUpdateWebhookUrl", json); + Assert.DoesNotContain("nvapi-test-secret", json); + Assert.DoesNotContain("/secret", json); + } + + [Fact] + public async Task LoadAsync_SanitizesLegacyPlaintextSecretsFromAppState() + { + var directory = Path.Combine(Path.GetTempPath(), "HyperBoostX.Tests", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(directory); + try { - await service.SaveAsync(config); + var path = Path.Combine(directory, "app-state.json"); + var legacyKeyName = "Open" + "AiApiKey"; + await File.WriteAllTextAsync(path, $$""" + { + "Settings": { + "Theme": "Dark", + "{{legacyKeyName}}": "legacy-secret", + "NvidiaApiKey": "nvapi-legacy-secret", + "DiscordWebhookUrl": "https://discord.com/api/webhooks/123/legacy", + "DiscordUpdateWebhookUrl": "https://discord.com/api/webhooks/456/legacy" + } + } + """); + + var service = new AppConfigService(directory); var loaded = await service.LoadAsync(); + var sanitized = await File.ReadAllTextAsync(path); Assert.Equal("Dark", loaded.Settings.Theme); - Assert.Equal("id-ID", loaded.Settings.Language); - Assert.Equal("Safe Autonomous", loaded.Settings.AutomationMode); + Assert.DoesNotContain(legacyKeyName, sanitized); + Assert.DoesNotContain("NvidiaApiKey", sanitized); + Assert.DoesNotContain("DiscordWebhookUrl", sanitized); + Assert.DoesNotContain("DiscordUpdateWebhookUrl", sanitized); + Assert.DoesNotContain("legacy-secret", sanitized); + Assert.DoesNotContain("nvapi-legacy-secret", sanitized); + Assert.DoesNotContain("/legacy", sanitized); } finally { - if (Directory.Exists(tempRoot)) - Directory.Delete(tempRoot, recursive: true); + Directory.Delete(directory, recursive: true); } } } diff --git a/dotnet-tests/HyperBoostX.Tests/FeatureAuditRegressionTests.cs b/dotnet-tests/HyperBoostX.Tests/FeatureAuditRegressionTests.cs index 22e1afb..376c063 100644 --- a/dotnet-tests/HyperBoostX.Tests/FeatureAuditRegressionTests.cs +++ b/dotnet-tests/HyperBoostX.Tests/FeatureAuditRegressionTests.cs @@ -3,6 +3,8 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; +using HyperBoostX.Services; +using Newtonsoft.Json.Linq; using Xunit; namespace HyperBoostX.Tests; @@ -18,7 +20,7 @@ public async Task Critical_feature_audit_suites_complete_without_failures() try { app = new Application(); - var window = new HyperBoostX.MainWindow(); + var window = new HyperBoostX.MainWindow(new FeatureAuditBackendClientStub()); await InvokePrivateTaskAsync(window, "RunTestingSuiteAsync", "Integration"); var integrationFailures = GetFailureCount(window); @@ -42,6 +44,119 @@ public async Task Critical_feature_audit_suites_complete_without_failures() $"Expected no failures, got Integration={result.integrationFailures}, UI Flow={result.uiFlowFailures}, Performance={result.performanceFailures}."); } + private sealed class FeatureAuditBackendClientStub : IHyperBoostBackendClient + { + public Task HealthCheckAsync() => Task.FromResult(true); + + public Task GetSystemInfoAsync() => Json(new JObject + { + ["os"] = "Windows test host", + ["cpu"] = "Test CPU", + ["memory_gb"] = 16, + ["disk"] = "SSD" + }); + + public Task GetSystemStatsAsync() => Json(new JObject + { + ["cpu"] = 12, + ["cpu_percent"] = 12, + ["memory"] = 38, + ["memory_percent"] = 38, + ["disk"] = 42, + ["disk_percent"] = 42, + ["process_count"] = 64 + }); + + public Task GetTweaksAsync() => Json(new JObject { ["tweaks"] = new JArray() }); + + public Task ApplyTweakAsync(string tweakId, bool expertMode = false, bool confirmed = false) => + Json(SuccessPayload("tweak", tweakId)); + + public Task GetBoosterProfilesAsync() => Json(new JObject { ["profiles"] = new JArray() }); + + public Task ApplyBoosterAsync(string profile) => Json(SuccessPayload("profile", profile)); + + public Task GetDriversAsync() => Json(new JObject { ["drivers"] = new JArray() }); + + public Task CheckDriverUpdatesAsync() => Json(new JObject { ["updates"] = new JArray() }); + + public Task RunSfcAsync() => Json(SuccessPayload("action", "sfc")); + + public Task CleanupAsync(string scope = "") => Json(new JObject + { + ["success"] = true, + ["scope"] = scope ?? "safe", + ["freed_bytes"] = 0 + }); + + public Task RunDismAsync() => Json(SuccessPayload("action", "dism")); + + public Task GetStartupItemsAsync() => Json(new JObject + { + ["startup_items"] = new JArray(), + ["items"] = new JArray() + }); + + public Task GetProcessesAsync() => Json(new JObject + { + ["processes"] = new JArray + { + new JObject + { + ["name"] = "explorer.exe", + ["memory_mb"] = 128 + } + } + }); + + public Task TestDnsAsync() => Json(new JObject { ["success"] = true, ["latency_ms"] = 12 }); + + public Task FlushDnsAsync() => Json(SuccessPayload("action", "flush_dns")); + + public Task OptimizeTcpAsync() => Json(SuccessPayload("action", "optimize_tcp")); + + public Task ResetNetworkAsync() => Json(SuccessPayload("action", "reset_network")); + + public Task RunTripleAiFlowAsync(string userGoal = "gaming", string game = "") => Json(new JObject + { + ["assistant"] = new JObject + { + ["message"] = "Triple AI test summary", + ["risk_level"] = "Low" + }, + ["analysis"] = new JObject + { + ["issues"] = new JArray(), + ["recommendations"] = new JArray() + }, + ["safety"] = new JObject + { + ["approved"] = new JArray(), + ["warnings"] = new JArray(), + ["blocked"] = new JArray() + }, + ["report"] = new JObject + { + ["pc_health_score"] = 92, + ["gaming_readiness_score"] = 88 + } + }); + + public Task ApplyTripleAiTweaksAsync(dynamic approvedTweaks, bool userApproved) => + Json(new JObject { ["success"] = userApproved }); + + public Task RevertTripleAiTweaksAsync(string backupId, object tweakIds) => + Json(new JObject { ["success"] = true, ["backup_id"] = backupId ?? "" }); + + private static JObject SuccessPayload(string key, string value) => new() + { + ["success"] = true, + [key] = value + }; + + private static Task Json(JToken token) => Task.FromResult(token); + } + private static async Task InvokePrivateTaskAsync(object instance, string methodName, params object[] args) { var method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic); diff --git a/dotnet-tests/HyperBoostX.Tests/LogAlertSignatureTests.cs b/dotnet-tests/HyperBoostX.Tests/LogAlertSignatureTests.cs new file mode 100644 index 0000000..a0b76b7 --- /dev/null +++ b/dotnet-tests/HyperBoostX.Tests/LogAlertSignatureTests.cs @@ -0,0 +1,49 @@ +using System.Reflection; +using HyperBoostX; +using Xunit; + +namespace HyperBoostX.Tests; + +public class LogAlertSignatureTests +{ + [Fact] + public void BuildLogAlertSignature_NormalizesStructuredLogTimestamp() + { + var first = BuildLogAlertSignature( + "hyperboost.log", + "warning", + "2026-05-30 02:47:28,186 - utils.shell - WARNING - Admin command blocked without elevation: powercfg /setactive 381b4222-f694-41f0-9685-ff5bb260df2e"); + var second = BuildLogAlertSignature( + "hyperboost.log", + "warning", + "2026-05-30 02:48:06,935 - utils.shell - WARNING - Admin command blocked without elevation: powercfg /setactive 381b4222-f694-41f0-9685-ff5bb260df2e"); + + Assert.Equal(first, second); + Assert.Contains("utils.shell|WARNING|Admin command blocked without elevation", first); + } + + [Fact] + public void BuildLogAlertSignature_NormalizesWpfLogTimestamp() + { + var first = BuildLogAlertSignature( + "hyperboost-wpf.log", + "error", + "[2026-05-30 02:47:28] DispatcherUnhandledException: boom"); + var second = BuildLogAlertSignature( + "hyperboost-wpf.log", + "error", + "[2026-05-30 02:49:28] DispatcherUnhandledException: boom"); + + Assert.Equal(first, second); + } + + private static string BuildLogAlertSignature(string sourceLog, string severity, string entry) + { + var method = typeof(App).GetMethod( + "BuildLogAlertSignature", + BindingFlags.Static | BindingFlags.NonPublic); + + Assert.NotNull(method); + return (string)method!.Invoke(null, new object[] { sourceLog, severity, entry })!; + } +} diff --git a/dotnet-tests/HyperBoostX.Tests/NvidiaCopilotServiceTests.cs b/dotnet-tests/HyperBoostX.Tests/NvidiaCopilotServiceTests.cs new file mode 100644 index 0000000..550a941 --- /dev/null +++ b/dotnet-tests/HyperBoostX.Tests/NvidiaCopilotServiceTests.cs @@ -0,0 +1,94 @@ +using HyperBoostX.Services; +using Xunit; + +namespace HyperBoostX.Tests; + +public class NvidiaCopilotServiceTests +{ + [Fact] + public void ParseResponseForTesting_ExtractsStructuredJsonFromLegacyShape() + { + var raw = """ + { + "output_text": "{\"intent\":\"network_fix\",\"confidence\":0.92,\"reply\":\"Reset network stack safely.\",\"safe_actions\":[\"network_fix\",\"scan_only\"]}" + } + """; + + var parsed = NvidiaCopilotService.ParseResponseForTesting(raw); + + Assert.Equal("network_fix", parsed.Intent); + Assert.Equal(0.92, parsed.Confidence, 2); + Assert.Contains("network_fix", parsed.SafeActions); + Assert.Contains("Reset network stack safely.", parsed.Reply); + } + + [Fact] + public void ParseResponseForTesting_ExtractsStructuredJsonFromChatCompletions() + { + var raw = """ + { + "choices": [ + { + "message": { + "content": "{\"intent\":\"gaming_prep\",\"confidence\":0.81,\"reply\":\"Prepare a safe gaming plan.\",\"safe_actions\":[\"gaming_prep\"],\"risk_level\":\"low\",\"requires_admin\":false,\"restore_available\":true,\"expected_result\":\"Lower background load\"}" + } + } + ] + } + """; + + var parsed = NvidiaCopilotService.ParseResponseForTesting(raw); + + Assert.Equal("gaming_prep", parsed.Intent); + Assert.Equal("low", parsed.RiskLevel); + Assert.False(parsed.RequiresAdmin); + Assert.True(parsed.RestoreAvailable); + Assert.Contains("gaming_prep", parsed.SafeActions); + } + + [Fact] + public void ModelRegistry_ContainsTenRequiredNvidiaModels() + { + var models = AiModelRegistry.GetAvailableModels(); + + Assert.Equal(10, models.Count); + Assert.Contains(models, model => model.Id == "nvidia/nemotron-3-nano-30b-a3b" && model.Label == "Fast Default"); + Assert.Contains(models, model => model.Id == "nvidia/nvidia-nemotron-nano-9b-v2" && model.Label == "Nano Lite"); + Assert.Equal("nvidia/nemotron-3-nano-30b-a3b", AiModelRegistry.GetDefaultModel()); + Assert.Equal("nvidia/nvidia-nemotron-nano-9b-v2", AiModelRegistry.GetFallbackModel()); + } + + [Fact] + public void NvidiaProvider_RedactsApiSecrets() + { + var provider = new NvidiaAiProvider(); + var redacted = provider.RedactSecret("Bearer nvapi-secret-token failed for nvapi-secret-token", "nvapi-secret-token"); + + Assert.DoesNotContain("nvapi-secret-token", redacted); + Assert.Contains("[REDACTED]", redacted); + } + + [Fact] + public void SafetyGuard_BlocksUnsafeActionsAndFallsBackToScanOnly() + { + var guard = new AiSafetyGuard(); + + var safe = guard.FilterSafeActions( + new[] { "disable_defender", "run_arbitrary_command", "delete_driver" }, + out var blocked); + + Assert.Equal(new[] { "scan_only" }, safe); + Assert.Contains("disable_defender", blocked); + Assert.Contains("run_arbitrary_command", blocked); + Assert.Contains("delete_driver", blocked); + } + + [Fact] + public void ApprovalService_RequiresApprovalForNonScanActions() + { + var approval = new AiActionApprovalService { RequireApproval = true }; + + Assert.False(approval.RequiresUserApproval(new[] { "scan_only" })); + Assert.True(approval.RequiresUserApproval(new[] { "cleanup" })); + } +} diff --git a/dotnet-tests/HyperBoostX.Tests/OpenAiCopilotServiceTests.cs b/dotnet-tests/HyperBoostX.Tests/OpenAiCopilotServiceTests.cs deleted file mode 100644 index a59d467..0000000 --- a/dotnet-tests/HyperBoostX.Tests/OpenAiCopilotServiceTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using HyperBoostX.Services; -using Xunit; - -namespace HyperBoostX.Tests; - -public class OpenAiCopilotServiceTests -{ - [Fact] - public void ParseResponseForTesting_ExtractsStructuredJson() - { - var raw = """ - { - "output_text": "{\"intent\":\"network_fix\",\"confidence\":0.92,\"reply\":\"Reset network stack safely.\",\"safe_actions\":[\"network_fix\",\"scan_only\"]}" - } - """; - - var parsed = OpenAiCopilotService.ParseResponseForTesting(raw); - - Assert.Equal("network_fix", parsed.Intent); - Assert.Equal(0.92, parsed.Confidence, 2); - Assert.Contains("network_fix", parsed.SafeActions); - Assert.Contains("Reset network stack safely.", parsed.Reply); - } -} diff --git a/launcher/Program.cs b/launcher/Program.cs index 4b5bdd2..0013029 100644 --- a/launcher/Program.cs +++ b/launcher/Program.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.IO; using System.Net.Http; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -29,6 +30,7 @@ internal class Program private static Process? _managedBackendProcess; private static bool _backendStartedByLauncher; private static Mutex? _singleInstanceMutex; + private static readonly string BackendToken = GenerateBackendToken(); static async Task Main(string[] args) { @@ -157,6 +159,7 @@ private static bool StartBackend() WindowStyle = ProcessWindowStyle.Hidden } }; + _managedBackendProcess.StartInfo.Environment["HYPERBOOSTX_BACKEND_TOKEN"] = BackendToken; if (!_managedBackendProcess.Start()) { @@ -201,11 +204,12 @@ private static async Task StartWpfClient() { FileName = WpfExe, WorkingDirectory = WpfDir, - UseShellExecute = true, + UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Normal } }; + process.StartInfo.Environment["HYPERBOOSTX_BACKEND_TOKEN"] = BackendToken; if (!process.Start()) { @@ -229,6 +233,7 @@ private static async Task IsBackendHealthy() { using var client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(2); + client.DefaultRequestHeaders.Add("X-HyperBoostX-Token", BackendToken); try { @@ -241,6 +246,15 @@ private static async Task IsBackendHealthy() } } + private static string GenerateBackendToken() + { + var bytes = RandomNumberGenerator.GetBytes(32); + return Convert.ToBase64String(bytes) + .TrimEnd('=') + .Replace('+', '-') + .Replace('/', '_'); + } + private static void Log(string message) { try diff --git a/release-notes-v1.1.0-beta.1.txt b/release-notes-v1.1.0-beta.1.txt index d914b23..66819ac 100644 --- a/release-notes-v1.1.0-beta.1.txt +++ b/release-notes-v1.1.0-beta.1.txt @@ -40,7 +40,7 @@ Updated areas include: - app config persistence behavior - installer verification logic - Discord webhook payload generation -- OpenAI copilot response parsing +- NVIDIA Copilot response parsing - launcher runtime layout resolution These changes improve maintainability and reduce reliance on manual-only validation. @@ -69,9 +69,9 @@ The optimization flow now uses a dedicated process filtering path for non-essent Discord reporting logic now exposes a reusable payload builder so webhook formatting can be tested directly without relying only on live POST calls. -#### OpenAI response parsing test hook +#### NVIDIA response parsing test hook -OpenAI copilot response parsing is now accessible through a test-friendly entry point so response extraction logic can be validated in isolation. +NVIDIA Copilot response parsing is now accessible through a test-friendly entry point so response extraction logic can be validated in isolation. #### Launcher runtime layout extraction @@ -92,7 +92,7 @@ Covered areas include: - installer validation contract behavior - Discord webhook payload structure - launcher runtime layout resolution -- OpenAI copilot response parsing +- NVIDIA Copilot response parsing ### Python regression tests diff --git a/release-notes-v1.1.0-beta.txt b/release-notes-v1.1.0-beta.txt index e4f094c..daabbfd 100644 --- a/release-notes-v1.1.0-beta.txt +++ b/release-notes-v1.1.0-beta.txt @@ -9,11 +9,11 @@ Highlights - Added persistent app config shared across automation, AI, and settings - Separated automation runtime mode from policy profile - Upgraded Scheduled Automation to use persistent rules, queue, deferred tasks, and audit state -- Added OpenAI-based HyperBoostX Copilot foundation with safe action approval flow +- Added NVIDIA-backed HyperBoostX Copilot foundation with safe action approval flow - Added Discord webhook reporting for important errors and crash events - Added modular localization foundation with en-US and id-ID language packs - Added in-app app-update checker for the latest GitHub release -- Added reinstall-safe secret storage for OpenAI and Discord via Windows Credential Manager +- Added reinstall-safe secret storage for NVIDIA and Discord via Windows Credential Manager - Added installer upgrade flow that removes the old app version while keeping user config/state - Added Sociabuzz donation shortcut in About App - Improved runtime stability for PowerShell execution, backend API error handling, and UI activity logging diff --git a/release-notes-v1.1.0.txt b/release-notes-v1.1.0.txt index de6f7a4..f09ff73 100644 --- a/release-notes-v1.1.0.txt +++ b/release-notes-v1.1.0.txt @@ -10,7 +10,7 @@ Highlights - Included Discord webhook reporting for important errors and crash events. - Included Discord release notifications from both in-app update detection and GitHub release publishing. - Included in-app release checking against the latest GitHub author build. -- Included reinstall-safe OpenAI and Discord secret persistence via Windows Credential Manager. +- Included reinstall-safe NVIDIA and Discord secret persistence via Windows Credential Manager. - Included installer upgrade flow that replaces the old app version while keeping user config and runtime state. - Included About App support shortcut for Sociabuzz. - Included modular localization foundation and cleaner runtime safety around backend and PowerShell actions. diff --git a/release-notes-v1.1.2.txt b/release-notes-v1.1.2.txt index e097a5d..825725f 100644 --- a/release-notes-v1.1.2.txt +++ b/release-notes-v1.1.2.txt @@ -3,8 +3,8 @@ HyperBoostX v1.1.2 Stable hotfix release focused on AI Copilot reliability and Feature Audit accuracy. Highlights -- Fixed HyperBoostX Copilot request reliability with a safer OpenAI fallback flow -- Improved OpenAI response parsing so Ask AI is less likely to fail or return empty output +- Fixed HyperBoostX Copilot request reliability with a safer NVIDIA fallback flow +- Improved NVIDIA response parsing so Ask AI is less likely to fail or return empty output - Added visible Last Test status for Test AI Connection in Settings - Persisted the latest AI connection-test result across restart - Improved Feature Audit runtime error tracking and stale-failure cleanup diff --git a/release-notes-v1.1.3.txt b/release-notes-v1.1.3.txt index dd5c09d..75d5231 100644 --- a/release-notes-v1.1.3.txt +++ b/release-notes-v1.1.3.txt @@ -1,11 +1,11 @@ HyperBoostX v1.1.3 -Stable hotfix release for Feature Audit accuracy and clearer OpenAI diagnostics. +Stable hotfix release for Feature Audit accuracy and clearer NVIDIA diagnostics. Highlights - Fixed Feature Audit so stale incidents from previous sessions no longer fail current audit runs -- Improved OpenAI Copilot diagnostics for 429 quota, 401 auth, and 403 permission failures -- Added endpoint labeling and request-id support to OpenAI error reporting when available +- Improved NVIDIA Copilot diagnostics for 429 quota, 401 auth, and 403 permission failures +- Added endpoint labeling and request-id support to NVIDIA error reporting when available - Fixed app update version parsing so current builds no longer show a false newer-version notification - Synced installer, launcher, backend, and update metadata to `1.1.3` diff --git a/scripts/verify_repo.ps1 b/scripts/verify_repo.ps1 index 119893b..b411429 100644 --- a/scripts/verify_repo.ps1 +++ b/scripts/verify_repo.ps1 @@ -38,6 +38,13 @@ try { Write-Host "HyperBoost X repo verification" -ForegroundColor Yellow Write-Host "Repo root: $repoRoot" + Invoke-Step "Version sync" { + & (Join-Path $repoRoot "scripts\verify_version_sync.ps1") + if (-not $?) { + throw "Version sync check failed." + } + } + if (-not $SkipPython) { if (-not (Test-Path $pythonExe)) { throw "Python virtual environment not found at '$pythonExe'." diff --git a/scripts/verify_version_sync.ps1 b/scripts/verify_version_sync.ps1 new file mode 100644 index 0000000..d024a3e --- /dev/null +++ b/scripts/verify_version_sync.ps1 @@ -0,0 +1,51 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$version = (Get-Content (Join-Path $repoRoot "VERSION") -Raw).Trim() +if ([string]::IsNullOrWhiteSpace($version)) { + throw "VERSION is empty." +} + +function Assert-Contains { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [string]$Pattern, + [Parameter(Mandatory = $true)] + [string]$Message + ) + + $content = Get-Content $Path -Raw + if ($content -notmatch $Pattern) { + throw $Message + } +} + +$escapedVersion = [regex]::Escape($version) +$assemblyVersion = "$version.0" +$escapedAssemblyVersion = [regex]::Escape($assemblyVersion) + +Assert-Contains ` + -Path (Join-Path $repoRoot "app\core\config.py") ` + -Pattern "VERSION\s*=\s*`"$escapedVersion`"" ` + -Message "app/core/config.py VERSION does not match $version." + +foreach ($project in @("wpf\HyperBoostX.csproj", "launcher\HyperBoostLauncher.csproj")) { + $path = Join-Path $repoRoot $project + Assert-Contains -Path $path -Pattern "$escapedVersion" -Message "$project does not match $version." + Assert-Contains -Path $path -Pattern "$escapedAssemblyVersion" -Message "$project does not match $assemblyVersion." + Assert-Contains -Path $path -Pattern "$escapedAssemblyVersion" -Message "$project does not match $assemblyVersion." + Assert-Contains -Path $path -Pattern "$escapedVersion" -Message "$project does not match $version." +} + +Assert-Contains ` + -Path (Join-Path $repoRoot "README.md") ` + -Pattern "\b$escapedVersion\b" ` + -Message "README.md does not mention current VERSION $version." + +Write-Host "Version sync verified: $version" diff --git a/tests/test_booster_service.py b/tests/test_booster_service.py index 2f78c80..ee1db26 100644 --- a/tests/test_booster_service.py +++ b/tests/test_booster_service.py @@ -1,5 +1,8 @@ +import logging + from app.core.profiles import Profile from app.core.profiles import ProfileManager +from app.services.optimization import booster_service from app.services.optimization.booster_service import BoosterService import winreg @@ -32,6 +35,75 @@ def test_apply_profile_skips_duplicate_request(monkeypatch): assert second.get("duplicate_request") is True +def test_duplicate_profile_skip_logs_as_info(monkeypatch, caplog): + BoosterService._last_profile_id = "" + BoosterService._last_profile_started_at = 0.0 + + monkeypatch.setitem( + booster_service.ProfileManager.PROFILES, + "qa-duplicate-log", + Profile( + name="QA Duplicate Log", + description="temporary", + settings={"disable_background_apps": False} + ), + ) + + with caplog.at_level(logging.INFO): + BoosterService.apply_profile("qa-duplicate-log") + BoosterService.apply_profile("qa-duplicate-log") + + duplicate_records = [record for record in caplog.records if "Skipped duplicate booster apply" in record.message] + assert duplicate_records + assert all(record.levelno == logging.INFO for record in duplicate_records) + + +def test_expected_limited_access_profile_logs_as_info(monkeypatch, caplog): + BoosterService._last_profile_id = "" + BoosterService._last_profile_started_at = 0.0 + + monkeypatch.setitem( + booster_service.ProfileManager.PROFILES, + "qa-limited-access", + Profile( + name="QA Limited Access", + description="temporary", + settings={ + "safe_setting": True, + "admin_setting": True, + } + ), + ) + + def fake_apply_setting(setting): + if setting == "safe_setting": + return { + "setting": setting, + "display_name": "Safe setting", + "success": True, + "reason_code": "applied", + "message": "Applied successfully.", + } + + return { + "setting": setting, + "display_name": "Admin setting", + "success": False, + "reason_code": "admin_required", + "message": "Admin setting requires Administrator privileges.", + } + + monkeypatch.setattr(BoosterService, "_apply_setting", staticmethod(fake_apply_setting)) + + with caplog.at_level(logging.INFO): + result = BoosterService.apply_profile("qa-limited-access") + + assert result["partial_success"] is True + limited_access_records = [record for record in caplog.records if "limited access" in record.message] + assert limited_access_records + assert all(record.levelno == logging.INFO for record in limited_access_records) + + def test_gaming_registry_tweaks_use_current_user(monkeypatch): calls = [] @@ -88,3 +160,85 @@ def test_apply_setting_reports_admin_requirement_from_registry_error(monkeypatch assert result["success"] is False assert result["reason_code"] == "admin_required" assert "elevated access" in result["message"] + + +def test_apply_profile_records_registry_restore_metadata(monkeypatch): + BoosterService._last_profile_id = "" + BoosterService._last_profile_started_at = 0.0 + saved_points = [] + + monkeypatch.setitem( + booster_service.ProfileManager.PROFILES, + "qa-registry-backup", + Profile( + name="QA Registry Backup", + description="temporary", + settings={"disable_visual_effects": True} + ), + ) + monkeypatch.setattr( + "app.services.optimization.booster_service.RegistryUtil.set_value", + lambda *args, **kwargs: True, + ) + + def fake_backup_registry(restore_point, hkey, path, key, new_value, new_value_type): + restore_point.registry.append( + { + "hive": "HKEY_CURRENT_USER", + "path": path, + "key": key, + "new_value": new_value, + } + ) + return True + + monkeypatch.setattr( + "app.services.optimization.booster_service.RestoreManager.backup_registry", + fake_backup_registry, + ) + monkeypatch.setattr( + "app.services.optimization.booster_service.RestoreManager.save_restore_point", + lambda point: saved_points.append(point) or True, + ) + + result = BoosterService.apply_profile("qa-registry-backup") + + assert result["success"] is True + assert result["registry_backups"] == 1 + assert result["restore_point"] == "profile_qa-registry-backup" + assert saved_points + assert BoosterService._current_restore_point is None + + +def test_apply_profile_records_power_plan_restore_metadata(monkeypatch): + BoosterService._last_profile_id = "" + BoosterService._last_profile_started_at = 0.0 + backed_up_schemes = [] + + monkeypatch.setitem( + booster_service.ProfileManager.PROFILES, + "qa-power-backup", + Profile( + name="QA Power Backup", + description="temporary", + settings={"balanced_performance": True} + ), + ) + monkeypatch.setattr( + "app.services.optimization.booster_service.RestoreManager.backup_power_plan", + lambda point, scheme: backed_up_schemes.append(scheme) or point.settings.append({"type": "power_plan"}) or True, + ) + monkeypatch.setattr( + "app.services.optimization.booster_service.ShellUtil.execute_command", + lambda *args, **kwargs: (True, ""), + ) + monkeypatch.setattr( + "app.services.optimization.booster_service.RestoreManager.save_restore_point", + lambda point: True, + ) + + result = BoosterService.apply_profile("qa-power-backup") + + assert result["success"] is True + assert result["settings_backups"] == 1 + assert backed_up_schemes == ["381b4222-f694-41f0-9685-ff5bb260df2e"] diff --git a/tests/test_health_api.py b/tests/test_health_api.py index e50524f..20f19f9 100644 --- a/tests/test_health_api.py +++ b/tests/test_health_api.py @@ -14,7 +14,10 @@ def test_cors_allows_localhost_origin(): response = server.app.test_client().get( "/api/health", - headers={"Origin": "http://localhost:5173"}, + headers={ + "Origin": "http://localhost:5173", + "X-HyperBoostX-Token": server.auth_token, + }, ) assert response.headers["Access-Control-Allow-Origin"] == "http://localhost:5173" @@ -25,7 +28,18 @@ def test_cors_rejects_non_local_origin(): response = server.app.test_client().get( "/api/health", - headers={"Origin": "https://example.com"}, + headers={ + "Origin": "https://example.com", + "X-HyperBoostX-Token": server.auth_token, + }, ) assert "Access-Control-Allow-Origin" not in response.headers + + +def test_backend_rejects_missing_token(): + server = HyperBoostBackendServer(auth_token="test-token") + + response = server.app.test_client().get("/api/health") + + assert response.status_code == 401 diff --git a/tests/test_registry_util.py b/tests/test_registry_util.py index aa75cb7..a3c0c52 100644 --- a/tests/test_registry_util.py +++ b/tests/test_registry_util.py @@ -1,3 +1,4 @@ +import logging import winreg from app.utils.registry import RegistryUtil @@ -41,3 +42,26 @@ def fake_close_key(reg_key): ) assert calls[1] == ("set", "Enabled", 0, winreg.REG_DWORD, 1) assert calls[2] == ("close",) + + +def test_set_value_access_denied_records_last_error_without_warning(monkeypatch, caplog): + def fake_create_key_ex(hkey, path, reserved, access): + raise PermissionError("denied") + + monkeypatch.setattr(winreg, "CreateKeyEx", fake_create_key_ex) + RegistryUtil.clear_last_error() + + with caplog.at_level(logging.INFO): + success = RegistryUtil.set_value( + r"SOFTWARE\HyperBoostX\Test", + "Enabled", + 1, + winreg.REG_DWORD, + hkey=winreg.HKEY_LOCAL_MACHINE, + ) + + assert success is False + assert RegistryUtil.get_last_error()["reason"] == "access_denied" + access_records = [record for record in caplog.records if "access denied" in record.message] + assert access_records + assert all(record.levelno == logging.INFO for record in access_records) diff --git a/tests/test_repair_cleanup.py b/tests/test_repair_cleanup.py index de2be6f..64158dd 100644 --- a/tests/test_repair_cleanup.py +++ b/tests/test_repair_cleanup.py @@ -69,7 +69,11 @@ def fake_cleanup(scope): monkeypatch.setattr("api.repair.repair_service.cleanup_temp_files", fake_cleanup) - response = client.post("/api/repair/cleanup", json={"scope": "browser_cache"}) + response = client.post( + "/api/repair/cleanup", + json={"scope": "browser_cache"}, + headers={"X-HyperBoostX-Token": server.auth_token}, + ) assert response.status_code == 200 payload = response.get_json() diff --git a/tests/test_shell_util.py b/tests/test_shell_util.py index 2d19766..b20b0e2 100644 --- a/tests/test_shell_util.py +++ b/tests/test_shell_util.py @@ -1,13 +1,19 @@ +import logging + from app.utils.shell import ShellUtil -def test_admin_command_returns_clear_message_when_not_elevated(monkeypatch): +def test_admin_command_returns_clear_message_when_not_elevated(monkeypatch, caplog): monkeypatch.setattr("app.utils.shell.Permissions.is_admin", lambda: False) - success, output = ShellUtil.execute_command("netsh int tcp set global autotuninglevel=normal", admin=True) + with caplog.at_level(logging.INFO): + success, output = ShellUtil.execute_command("netsh int tcp set global autotuninglevel=normal", admin=True) assert success is False assert "administrator privileges" in output.lower() + admin_records = [record for record in caplog.records if "Admin command skipped without elevation" in record.message] + assert admin_records + assert all(record.levelno == logging.INFO for record in admin_records) def test_non_admin_command_executes_through_powershell(): @@ -22,3 +28,32 @@ def test_run_powershell_handles_quoted_script(): assert success is True assert output == "quoted value" + + +def test_shell_util_blocks_non_allowlisted_command(): + success, output = ShellUtil.execute_command("Get-ChildItem C:\\") + + assert success is False + assert "not allowed" in output.lower() + + +def test_shell_util_allows_battery_display_timeout_command(monkeypatch): + calls = [] + + class FakeProcess: + returncode = 0 + stdout = "ok" + stderr = "" + + def fake_run(args, stdout, stderr, text, timeout): + calls.append(args) + return FakeProcess() + + monkeypatch.setattr("app.utils.shell.Permissions.is_admin", lambda: True) + monkeypatch.setattr("app.utils.shell.subprocess.run", fake_run) + + success, output = ShellUtil.execute_command("powercfg /change monitor-timeout-dc 300", admin=True) + + assert success is True + assert output == "ok" + assert calls diff --git a/tests/test_startup_api.py b/tests/test_startup_api.py index 888e356..d499ab0 100644 --- a/tests/test_startup_api.py +++ b/tests/test_startup_api.py @@ -12,7 +12,10 @@ def test_startup_list_returns_legacy_and_new_keys(monkeypatch): lambda: sample_items, ) - response = client.get("/api/startup/list") + response = client.get( + "/api/startup/list", + headers={"X-HyperBoostX-Token": server.auth_token}, + ) assert response.status_code == 200 payload = response.get_json() @@ -38,7 +41,10 @@ def fail_if_called(*args, **kwargs): monkeypatch.setattr("services.optimization.startup_service.StartupService._read_scheduled_tasks", staticmethod(fail_if_called)) monkeypatch.setattr("services.optimization.startup_service.StartupService._read_startup_services", staticmethod(fail_if_called)) - response = client.get("/api/startup/list") + response = client.get( + "/api/startup/list", + headers={"X-HyperBoostX-Token": server.auth_token}, + ) assert response.status_code == 200 payload = response.get_json() diff --git a/tests/test_triple_ai_engine.py b/tests/test_triple_ai_engine.py new file mode 100644 index 0000000..4f263b6 --- /dev/null +++ b/tests/test_triple_ai_engine.py @@ -0,0 +1,223 @@ +from app.backend_server import HyperBoostBackendServer +from app.services.ai.triple_ai_engine import TripleAIEngine + + +class FakeSystemInfoService: + def get_system_identity(self): + return {"os_version": "10.0.22631", "os_release": "11"} + + def get_cpu_info(self): + return {"processor": "AMD Ryzen Test CPU", "cores": 8, "threads": 16} + + def get_memory_info(self): + return {"total": 16 * 1024**3, "speed_mhz": 3200} + + def get_disk_info(self): + return {"C:": {"free": 80 * 1024**3}} + + def get_system_drive_info(self): + return {"storage_class": "NVMe"} + + def get_device_profile(self, stats=None): + return { + "bottleneck": "memory-bound", + "recommended_profile": "Low RAM", + "expected_gain": "Moderate", + "storage_class": "SSD", + } + + def get_os_info(self): + return {"version": "10.0.22631", "release": "11"} + + def get_gpu_info(self): + return { + "gpus": [ + { + "name": "NVIDIA GeForce RTX 4060", + "driver_version": "555.85", + "vram": 8 * 1024**3, + } + ] + } + + def get_temperature_info(self): + return {} + + +class FakeMonitorService: + def get_current_stats(self): + return { + "cpu": 35, + "cpu_cores": 8, + "cpu_threads": 16, + "memory": 84, + "memory_total_gb": 16, + "disk": 88, + "processes": 148, + "gpu": { + "name": "NVIDIA GeForce RTX 4060", + "load": 42, + "memory_total_mb": 8192, + "memory_percent": 51, + "temperature": 68, + }, + } + + def get_process_list(self, limit=15): + return [ + {"name": "chrome.exe", "memory": 2.4, "cpu": 3}, + {"name": "launcher.exe", "memory": 1.4, "cpu": 2}, + {"name": "overlay.exe", "memory": 1.2, "cpu": 2}, + ][:limit] + + +class FakeStartupService: + def get_startup_items(self): + return [ + {"name": "Game Launcher", "impact": "High", "impact_score": 72, "enabled": True}, + {"name": "Overlay", "impact": "Medium", "impact_score": 40, "enabled": True}, + ] + + +class FakeTweakService: + def __init__(self): + self.applied = [] + self.reverted = [] + + def apply_tweak(self, tweak_id, expert_mode=False, confirmed=False): + self.applied.append(tweak_id) + return { + "success": True, + "restore_point": f"tweak_{tweak_id}", + "restore_timestamp": "20260530-000000-000000", + "registry_backups": 1, + } + + def revert_tweak(self, tweak_id): + self.reverted.append(tweak_id) + return {"success": True, "registry_restored": 1} + + +def build_engine(fake_tweak_service=None): + return TripleAIEngine( + system_info_service=FakeSystemInfoService(), + monitor_service=FakeMonitorService(), + startup_service=FakeStartupService(), + tweak_service=fake_tweak_service or FakeTweakService(), + ) + + +def test_triple_ai_full_flow_returns_scan_analyze_safety_assistant_report(): + result = build_engine().run_full_flow(user_goal="gaming", game="Fortnite") + + assert result["scan"]["hardware"]["gpu_name"] == "NVIDIA GeForce RTX 4060" + assert result["analysis"]["role"] == "AI Analyzer" + assert result["safety"]["role"] == "AI Safety Guard" + assert result["assistant"]["role"] == "AI Assistant" + assert result["report"]["pc_health_score"] <= 100 + assert "guaranteed FPS" not in result["assistant"]["message"] + assert result["analysis"]["rag_context"] + + +def test_safety_guard_blocks_dangerous_tweaks(): + engine = build_engine() + safety = engine.safety_check([ + { + "tweak_id": "disable_defender", + "title": "Disable Windows Defender", + "description": "Disable Windows Security for FPS", + "risk_level": "high", + "reversible": False, + "can_auto_apply": True, + }, + { + "tweak_id": "auto_overclock_gpu", + "title": "Auto overclock GPU", + "description": "Guaranteed FPS boost", + "risk_level": "high", + "reversible": False, + "can_auto_apply": True, + }, + ]) + + assert len(safety["blocked"]) == 2 + assert safety["approved"] == [] + + +def test_safe_tweak_engine_requires_approval_and_runs_only_guard_approved_items(): + fake_tweaks = FakeTweakService() + engine = build_engine(fake_tweak_service=fake_tweaks) + recommendations = [ + engine._kb_recommendation("optimize_visual"), + { + "tweak_id": "disable_defender", + "title": "Disable Windows Defender", + "description": "Disable Windows Security", + "risk_level": "blocked", + "reversible": False, + "can_auto_apply": True, + }, + ] + + denied = engine.apply_safe_tweaks(recommendations, user_approved=False) + assert denied["success"] is False + assert fake_tweaks.applied == [] + + result = engine.apply_safe_tweaks(recommendations, user_approved=True) + assert fake_tweaks.applied == ["optimize_visual"] + assert result["applied"][0]["tweak_id"] == "optimize_visual" + assert result["blocked"][0]["tweak_id"] == "disable_defender" + + +def test_scan_contract_requires_backend_token_and_returns_scan(monkeypatch): + import api.triple_ai as triple_ai + + monkeypatch.setattr( + triple_ai.triple_ai_engine, + "scan_pc", + lambda: { + "scan_id": "scan_test", + "hardware": {}, + "windows": {}, + "nvidia": {}, + "apps": {}, + "timestamp": "2026-05-30T00:00:00Z", + }, + ) + server = HyperBoostBackendServer(auth_token="test-token") + client = server.app.test_client() + + assert client.post("/scan").status_code == 401 + response = client.post("/scan", headers={"X-HyperBoostX-Token": "test-token"}) + + assert response.status_code == 200 + assert response.get_json()["scan_id"] == "scan_test" + + +def test_game_optimizer_endpoint_requires_token_and_game_name(monkeypatch): + import api.triple_ai as triple_ai + + monkeypatch.setattr( + triple_ai.triple_ai_engine, + "optimize_game", + lambda game_name, scan_result=None: { + "game": game_name, + "risk_level": "low", + "manual_apply": True, + "recommendations": [{"setting": "NVIDIA Reflex", "risk_level": "low"}], + }, + ) + server = HyperBoostBackendServer(auth_token="test-token") + client = server.app.test_client() + + assert client.post("/game/optimize", json={"game": "Fortnite"}).status_code == 401 + + missing = client.post("/game/optimize", headers={"X-HyperBoostX-Token": "test-token"}, json={}) + assert missing.status_code == 400 + + response = client.post("/game/optimize", headers={"X-HyperBoostX-Token": "test-token"}, json={"game": "Fortnite"}) + payload = response.get_json() + + assert response.status_code == 200 + assert payload["game"] == "Fortnite" + assert payload["manual_apply"] is True diff --git a/tests/test_tweak_contract.py b/tests/test_tweak_contract.py index aa99860..91f2837 100644 --- a/tests/test_tweak_contract.py +++ b/tests/test_tweak_contract.py @@ -1,4 +1,8 @@ from app.services.optimization.tweak_service import TweakService +from app.core.restore import RestoreManager +from app.core.restore import RestorePoint +from app.core.config import Config +import winreg def test_unknown_tweak_returns_error(): @@ -38,3 +42,53 @@ def __init__(self): assert result["success"] is False assert restore_called["value"] is True + + +def test_high_risk_tweak_requires_expert_mode(): + result = TweakService.apply_tweak("disable_updates") + + assert result["success"] is False + assert result["requires_expert_mode"] is True + + +def test_revert_tweak_requires_real_restore_backup(monkeypatch): + monkeypatch.setattr( + "app.services.optimization.tweak_service.RestoreManager.find_latest_restore_point", + lambda name: None, + ) + + result = TweakService.revert_tweak("optimize_visual") + + assert result["success"] is False + assert "No restore backup" in result["error"] + + +def test_registry_restore_deletes_value_when_old_value_was_missing(monkeypatch, tmp_path): + monkeypatch.setattr(Config, "BACKUP_DIR", tmp_path) + deleted = [] + + class DummyKey: + pass + + def fake_open_key(hkey, path, reserved=0, access=0): + if access == winreg.KEY_READ: + raise FileNotFoundError("missing") + return DummyKey() + + monkeypatch.setattr(winreg, "OpenKey", fake_open_key) + monkeypatch.setattr(winreg, "CloseKey", lambda key: None) + monkeypatch.setattr(winreg, "DeleteValue", lambda key, value_name: deleted.append(value_name)) + + point = RestorePoint("tweak_test", "test") + assert RestoreManager.backup_registry( + point, + winreg.HKEY_CURRENT_USER, + r"SOFTWARE\HyperBoostX\Test", + "CreatedValue", + 1, + winreg.REG_DWORD, + ) + + assert point.registry[0]["old_value_exists"] is False + assert RestoreManager.restore(point) is True + assert deleted == ["CreatedValue"] diff --git a/wpf/App.xaml.cs b/wpf/App.xaml.cs index ac43f7b..8fe52e8 100644 --- a/wpf/App.xaml.cs +++ b/wpf/App.xaml.cs @@ -18,6 +18,12 @@ namespace HyperBoostX public partial class App : Application { private static readonly Regex StructuredLogSeverityRegex = new Regex(@"\s-\s(?DEBUG|INFO|WARNING|ERROR|CRITICAL)\s-\s", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex StructuredLogMessageRegex = new Regex( + @"^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2},\d{3}\s+-\s+(?.+?)\s+-\s+(?DEBUG|INFO|WARNING|ERROR|CRITICAL)\s+-\s+(?.*)$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex BracketedLogTimestampRegex = new Regex( + @"^\[\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\]\s*(?.*)$", + RegexOptions.Compiled); private static readonly string LogDirectory = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "HyperBoost X", @@ -203,7 +209,7 @@ private async Task ScanLogsAndReportAsync() if (severity == null || !ShouldSendForSeverity(severity, settings.MinimumLevel)) continue; - var signature = $"{Path.GetFileName(logPath)}|{severity}|{entry.Trim()}"; + var signature = BuildLogAlertSignature(Path.GetFileName(logPath), severity, entry); if (IsWithinDiscordCooldown(signature, settings.CooldownSeconds)) continue; @@ -288,6 +294,33 @@ private static string DetectLogSeverity(string sourceLogName, string line) return null; } + private static string BuildLogAlertSignature(string sourceLogName, string severity, string entry) + { + return $"{sourceLogName}|{severity}|{NormalizeLogAlertEntry(entry)}"; + } + + private static string NormalizeLogAlertEntry(string entry) + { + var text = entry?.Trim() ?? ""; + if (string.IsNullOrWhiteSpace(text)) + return ""; + + var structured = StructuredLogMessageRegex.Match(text); + if (structured.Success) + { + var loggerName = structured.Groups["logger"].Value.Trim(); + var level = structured.Groups["level"].Value.Trim().ToUpperInvariant(); + var message = structured.Groups["message"].Value.Trim(); + return $"{loggerName}|{level}|{message}"; + } + + var bracketed = BracketedLogTimestampRegex.Match(text); + if (bracketed.Success) + return bracketed.Groups["message"].Value.Trim(); + + return text; + } + private async Task<(bool Enabled, string WebhookUrl, string MinimumLevel, int CooldownSeconds)> LoadDiscordReportingSettingsAsync() { try @@ -295,13 +328,7 @@ private static string DetectLogSeverity(string sourceLogName, string line) var configService = new AppConfigService(); var config = await configService.LoadAsync(); var secrets = await _secureSecretStoreService.LoadAsync(); - var envWebhook = Environment.GetEnvironmentVariable("HYPERBOOSTX_DISCORD_WEBHOOK_URL")?.Trim() ?? ""; - - var webhookUrl = !string.IsNullOrWhiteSpace(envWebhook) - ? envWebhook - : !string.IsNullOrWhiteSpace(secrets.DiscordWebhookUrl) - ? secrets.DiscordWebhookUrl - : config?.Settings?.DiscordWebhookUrl ?? ""; + var webhookUrl = secrets.DiscordWebhookUrl ?? ""; return ( config?.Settings?.DiscordWebhookEnabled == true, diff --git a/wpf/MainWindow.xaml b/wpf/MainWindow.xaml index 4f92dce..bdc59cd 100644 --- a/wpf/MainWindow.xaml +++ b/wpf/MainWindow.xaml @@ -4,7 +4,7 @@ Title="HyperBoost X - WPF Client" Height="900" Width="1400" - Background="#1e1e1e" + Background="#0B1020" Foreground="#ffffff" ResizeMode="CanResize" WindowStartupLocation="CenterScreen" @@ -12,29 +12,54 @@ Closed="Window_Closed"> - - + + + + + + @@ -71,26 +98,31 @@ - - + + + + + + +