|
| 1 | +# Desktop Plugin Dependency Boundary Implementation Plan |
| 2 | + |
| 3 | +**Goal:** Prevent packaged desktop plugin installs from replacing bundled backend runtime dependencies. |
| 4 | + |
| 5 | +**Architecture:** Generate a runtime core dependency lock during desktop backend packaging, expose it through an environment variable at launch, and teach AstrBot's packaged pip installer to constrain and skip locked core modules. This keeps plugin-only dependencies installable while preventing unsafe in-process replacement of backend dependencies. |
| 6 | + |
| 7 | +**Tech Stack:** Node.js resource preparation scripts, Python launcher/runtime code, AstrBot pip installer tests with pytest, Node `node:test`. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +### Task 1: Generate the Desktop Runtime Core Lock |
| 12 | + |
| 13 | +**Files:** |
| 14 | +- Modify: `scripts/backend/build-backend.mjs` |
| 15 | +- Create: `scripts/backend/templates/generate_runtime_core_lock.py` |
| 16 | +- Test: `scripts/backend/runtime-core-lock.test.mjs` |
| 17 | + |
| 18 | +**Step 1: Write the failing test** |
| 19 | + |
| 20 | +Add a Node test that creates a fake runtime Python command, runs the lock generator entry point, and expects `runtime-core-lock.json` to be written under the backend app directory. |
| 21 | + |
| 22 | +**Step 2: Run test to verify it fails** |
| 23 | + |
| 24 | +Run: `node --test scripts/backend/runtime-core-lock.test.mjs` |
| 25 | +Expected: FAIL because the helper/export does not exist yet. |
| 26 | + |
| 27 | +**Step 3: Write minimal implementation** |
| 28 | + |
| 29 | +Add a Python helper that uses `importlib.metadata.distributions()` to collect: |
| 30 | +- distribution name |
| 31 | +- distribution version |
| 32 | +- `top_level.txt` modules when present |
| 33 | + |
| 34 | +Call it from `build-backend.mjs` after runtime dependency installation and before the manifest is written. |
| 35 | + |
| 36 | +**Step 4: Run test to verify it passes** |
| 37 | + |
| 38 | +Run: `node --test scripts/backend/runtime-core-lock.test.mjs` |
| 39 | +Expected: PASS. |
| 40 | + |
| 41 | +### Task 2: Expose the Lock to the Packaged Backend |
| 42 | + |
| 43 | +**Files:** |
| 44 | +- Modify: `scripts/backend/templates/launch_backend.py` |
| 45 | +- Test: `scripts/prepare-resources/backend-runtime.test.mjs` or a focused launcher template test |
| 46 | + |
| 47 | +**Step 1: Write the failing test** |
| 48 | + |
| 49 | +Add a test that verifies the launcher template contains `ASTRBOT_DESKTOP_CORE_LOCK_PATH` and points it at `APP_DIR / "runtime-core-lock.json"` only when the file exists. |
| 50 | + |
| 51 | +**Step 2: Run test to verify it fails** |
| 52 | + |
| 53 | +Run: `pnpm run test:prepare-resources` |
| 54 | +Expected: FAIL because the launcher does not set the env var yet. |
| 55 | + |
| 56 | +**Step 3: Write minimal implementation** |
| 57 | + |
| 58 | +Set `os.environ["ASTRBOT_DESKTOP_CORE_LOCK_PATH"]` in `launch_backend.py` before `runpy.run_path()` when the lock file exists. |
| 59 | + |
| 60 | +**Step 4: Run test to verify it passes** |
| 61 | + |
| 62 | +Run: `pnpm run test:prepare-resources` |
| 63 | +Expected: PASS. |
| 64 | + |
| 65 | +### Task 3: Apply Lock Constraints in the Pip Installer |
| 66 | + |
| 67 | +**Files:** |
| 68 | +- Modify: `vendor/AstrBot/astrbot/core/utils/core_constraints.py` |
| 69 | +- Modify: `vendor/AstrBot/astrbot/core/utils/pip_installer.py` |
| 70 | +- Test: `vendor/AstrBot/tests/test_pip_installer.py` |
| 71 | + |
| 72 | +**Step 1: Write the failing test** |
| 73 | + |
| 74 | +Add a test that sets `ASTRBOT_DESKTOP_CORE_LOCK_PATH` to a JSON lock containing `openai==2.32.0`, then verifies `PipInstaller.install(package_name="Cua")` adds `-c <constraints file>` and the constraints include `openai==2.32.0`. |
| 75 | + |
| 76 | +**Step 2: Run test to verify it fails** |
| 77 | + |
| 78 | +Run: `cd vendor/AstrBot && uv run pytest tests/test_pip_installer.py::<test_name> -q` |
| 79 | +Expected: FAIL because the lock is ignored. |
| 80 | + |
| 81 | +**Step 3: Write minimal implementation** |
| 82 | + |
| 83 | +Teach `CoreConstraintsProvider` to merge existing core metadata constraints with locked desktop runtime constraints in packaged mode. |
| 84 | + |
| 85 | +**Step 4: Run test to verify it passes** |
| 86 | + |
| 87 | +Run: `cd vendor/AstrBot && uv run pytest tests/test_pip_installer.py::<test_name> -q` |
| 88 | +Expected: PASS. |
| 89 | + |
| 90 | +### Task 4: Skip Locked Modules During Post-Install Preference |
| 91 | + |
| 92 | +**Files:** |
| 93 | +- Modify: `vendor/AstrBot/astrbot/core/utils/pip_installer.py` |
| 94 | +- Test: `vendor/AstrBot/tests/test_pip_installer.py` |
| 95 | + |
| 96 | +**Step 1: Write the failing test** |
| 97 | + |
| 98 | +Add a test where candidate modules include `openai` and `cua_agent`, the lock marks `openai` as a core module, and `_ensure_plugin_dependencies_preferred` calls `_ensure_preferred_modules` only with `cua_agent`. |
| 99 | + |
| 100 | +**Step 2: Run test to verify it fails** |
| 101 | + |
| 102 | +Run: `cd vendor/AstrBot && uv run pytest tests/test_pip_installer.py::<test_name> -q` |
| 103 | +Expected: FAIL because locked modules are still preferred. |
| 104 | + |
| 105 | +**Step 3: Write minimal implementation** |
| 106 | + |
| 107 | +Filter candidate modules using the lock's top-level module set before attempting `_prefer_module_from_site_packages`. |
| 108 | + |
| 109 | +**Step 4: Run test to verify it passes** |
| 110 | + |
| 111 | +Run: `cd vendor/AstrBot && uv run pytest tests/test_pip_installer.py::<test_name> -q` |
| 112 | +Expected: PASS. |
| 113 | + |
| 114 | +### Task 5: Full Verification and PR |
| 115 | + |
| 116 | +**Files:** |
| 117 | +- All touched files |
| 118 | + |
| 119 | +**Step 1: Run focused tests** |
| 120 | + |
| 121 | +Run: |
| 122 | +- `pnpm run test:prepare-resources` |
| 123 | +- `cd vendor/AstrBot && uv run pytest tests/test_pip_installer.py -q` |
| 124 | + |
| 125 | +**Step 2: Run repository tests if feasible** |
| 126 | + |
| 127 | +Run: |
| 128 | +- `make test` |
| 129 | + |
| 130 | +**Step 3: Commit and open PR** |
| 131 | + |
| 132 | +Commit only intentional files, push `codex/desktop-plugin-dependency-lock`, and create a draft PR against `AstrBotDevs/AstrBot-desktop:main`. |
0 commit comments