From 7fbd339743359cfbb8e9da917a67b3e1f7ba1beb Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 16:52:25 +0100 Subject: [PATCH] fix: install Slides Node dependencies during setup - install required Slides npm packages with legacy peer deps when missing or stale - install Node Playwright Chromium assets into the project browser cache - add a direct bootstrap smoke for CLI_PATCHES step 5c - bump OpenSwarm rc to 1.0.1-rc.6 --- .github/workflows/test-mac.yml | 4 +- package-lock.json | 4 +- package.json | 2 +- pyproject.toml | 2 +- run_utils.py | 100 +++++++++++++++----- scripts/smoke-bootstrap-onboard.py | 144 +++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 31 deletions(-) diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 14508e5..9e24b70 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -50,12 +50,12 @@ jobs: AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_PYTHON_ENVIRONMENT: standalone - AGENTSWARM_PRODUCT_VERSION: 1.0.1-rc.5 + AGENTSWARM_PRODUCT_VERSION: 1.0.1-rc.6 run: | bun install cd packages/opencode bun run script/build.ts --single --skip-install - test "$(./dist/agentswarm-cli-darwin-arm64/bin/agentswarm --version)" = "1.0.1-rc.5" + test "$(./dist/agentswarm-cli-darwin-arm64/bin/agentswarm --version)" = "1.0.1-rc.6" - name: Test startup shell: bash run: | diff --git a/package-lock.json b/package-lock.json index 1f1b28a..1165acb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vrsen/openswarm", - "version": "1.0.1-rc.5", + "version": "1.0.1-rc.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@vrsen/openswarm", - "version": "1.0.1-rc.5", + "version": "1.0.1-rc.6", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4537885..a53c977 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vrsen/openswarm", - "version": "1.0.1-rc.5", + "version": "1.0.1-rc.6", "description": "An open-source multi-agent AI team built on Agency Swarm", "license": "MIT", "publishConfig": { diff --git a/pyproject.toml b/pyproject.toml index 2cde281..7a32c93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "open-swarm" description = "An open-source multi-agent AI team built on Agency Swarm and the OpenAI Agents SDK" -version = "1.0.1-rc.5" +version = "1.0.1-rc.6" license = { text = "MIT" } keywords = ["agency-swarm", "openai", "multi-agent", "open-source", "openswarm"] requires-python = ">=3.12" diff --git a/run_utils.py b/run_utils.py index 683fded..6b387b3 100644 --- a/run_utils.py +++ b/run_utils.py @@ -151,23 +151,84 @@ def _resolve_tui_binary(repo: Path, download: bool) -> Path | None: return None -def _ensure_node_playwright_browsers(repo: Path) -> None: +_REQUIRED_SLIDES_NODE_PACKAGES = ( + "dom-to-pptx", + "playwright", + "pptxgenjs", + "react", + "react-dom", + "react-icons", + "sharp", +) + + +def _warn_slides_setup(message: str) -> None: + print( + f"Warning: {message}\n" + " OpenSwarm will continue, but Slides Agent export features may be unavailable.\n" + ) + + +def _run_optional_node_command( + cmd: list[str], + repo: Path, + label: str, + env: dict[str, str] | None = None, +) -> bool: + try: + result = subprocess.run(cmd, cwd=str(repo), env=env) + except Exception as exc: + _warn_slides_setup(f"{label} failed: {exc}") + return False + if result.returncode != 0: + _warn_slides_setup(f"{label} exited with code {result.returncode}") + return False + return True + + +def _ensure_node_playwright_browsers(repo: Path) -> bool: """Install Node Playwright browsers where the HTML-to-PPTX runner looks for them.""" - cli = repo / "node_modules" / "playwright" / "cli.js" - if not cli.exists(): - return + npx = shutil.which("npx") + if not npx: + _warn_slides_setup( + "npm is available but npx was not found; cannot install Node Playwright browsers" + ) + return False env = os.environ.copy() env["PLAYWRIGHT_BROWSERS_PATH"] = str(repo / ".playwright-browsers") - subprocess.check_call( - ["node", str(cli), "install", "chromium"], - cwd=str(repo), + return _run_optional_node_command( + [npx, "-y", "playwright", "install", "chromium", "chromium-headless-shell"], + repo, + "Node Playwright browser install", env=env, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, ) +def _ensure_node_dependencies(repo: Path, npm: str) -> bool: + _node_modules = repo / "node_modules" + _pkg_lock = repo / "package-lock.json" + _npm_marker = _node_modules / ".package-lock.json" + _need_npm = ( + not _node_modules.exists() + or not _npm_marker.exists() + or (_pkg_lock.exists() and _pkg_lock.stat().st_mtime > _npm_marker.stat().st_mtime) + or any(not (_node_modules / name).exists() for name in _REQUIRED_SLIDES_NODE_PACKAGES) + ) + _npm_ok = True + if _need_npm: + print("Installing Node.js dependencies, please wait…\n") + _npm_ok = _run_optional_node_command( + [npm, "install", "--legacy-peer-deps"], + repo, + "Node.js dependency install", + ) + if _npm_ok: + print("\nDone.\n") + _browsers_ok = _ensure_node_playwright_browsers(repo) + return _npm_ok and _browsers_ok + + def _uv_env() -> dict[str, str]: env = os.environ.copy() env.setdefault("UV_LINK_MODE", "copy") @@ -254,25 +315,14 @@ def _bootstrap() -> None: " Install it from: https://poppler.freedesktop.org\n" ) - # Install Node.js dependencies if node_modules is missing or outdated. + # Install Node.js dependencies if node_modules is missing, incomplete, or outdated. _npm = shutil.which("npm") if _npm and (_repo / "package.json").exists(): - _node_modules = _repo / "node_modules" - _pkg_lock = _repo / "package-lock.json" - _npm_marker = _node_modules / ".package-lock.json" - _need_npm = ( - not _node_modules.exists() - or not _npm_marker.exists() - or (_pkg_lock.exists() and _pkg_lock.stat().st_mtime > _npm_marker.stat().st_mtime) + _ensure_node_dependencies(_repo, _npm) + elif (_repo / "package.json").exists(): + _warn_slides_setup( + "npm was not found; cannot install Slides Agent Node.js dependencies" ) - if _need_npm: - print("Installing Node.js dependencies, please wait…\n") - subprocess.check_call([_npm, "install"], cwd=str(_repo)) - print("\nDone.\n") - try: - _ensure_node_playwright_browsers(_repo) - except Exception: - pass # Download the OpenSwarm TUI binary from GitHub Releases if missing. if not os.getenv("AGENTSWARM_BIN"): diff --git a/scripts/smoke-bootstrap-onboard.py b/scripts/smoke-bootstrap-onboard.py index 6480b18..feae882 100644 --- a/scripts/smoke-bootstrap-onboard.py +++ b/scripts/smoke-bootstrap-onboard.py @@ -144,9 +144,153 @@ def smoke_onboard_env_writes() -> None: raise RuntimeError(f"onboarding did not write expected .env values: {missing}") +def smoke_bootstrap_node_setup_installs_slides_dependencies() -> None: + sys.path.insert(0, str(ROOT)) + try: + import run_utils + finally: + sys.path.pop(0) + + calls: list[dict[str, object]] = [] + + def run(cmd: list[str], **kwargs: object) -> types.SimpleNamespace: + calls.append({"cmd": cmd, **kwargs}) + if cmd == ["npm", "install", "--legacy-peer-deps"]: + modules = Path(str(kwargs["cwd"])) / "node_modules" + modules.mkdir(exist_ok=True) + (modules / ".package-lock.json").write_text("{}\n", encoding="utf-8") + for name in run_utils._REQUIRED_SLIDES_NODE_PACKAGES: + (modules / name).mkdir(parents=True, exist_ok=True) + return types.SimpleNamespace(returncode=0) + if cmd[-3:] == ["install", "chromium", "chromium-headless-shell"]: + env = kwargs.get("env") + if not isinstance(env, dict): + raise RuntimeError("Playwright install did not receive an environment") + browsers = Path(str(env["PLAYWRIGHT_BROWSERS_PATH"])) + (browsers / "chromium-1000").mkdir(parents=True) + (browsers / "chromium_headless_shell-1000").mkdir() + return types.SimpleNamespace(returncode=0) + + with tempfile.TemporaryDirectory(prefix="openswarm-node-bootstrap-smoke-") as tmp: + repo = Path(tmp) + (repo / "package.json").write_text('{"dependencies":{"playwright":"^1.59.1"}}\n', encoding="utf-8") + modules = repo / "node_modules" + modules.mkdir() + (modules / ".package-lock.json").write_text("{}\n", encoding="utf-8") + present = ("dom-to-pptx", "playwright", "pptxgenjs", "react", "react-dom", "react-icons") + for name in present: + (modules / name).mkdir() + + with ( + patch.object( + run_utils.shutil, + "which", + lambda name: "/usr/local/bin/npx" if name == "npx" else None, + ), + patch.object(run_utils.subprocess, "run", run), + ): + if not run_utils._ensure_node_dependencies(repo, "npm"): + raise RuntimeError("bootstrap reported failed Node setup for successful commands") + + expected = [ + ["npm", "install", "--legacy-peer-deps"], + [ + "/usr/local/bin/npx", + "-y", + "playwright", + "install", + "chromium", + "chromium-headless-shell", + ], + ] + actual = [call["cmd"] for call in calls] + if actual != expected: + raise RuntimeError(f"bootstrap ran unexpected Node setup commands: {actual}") + + missing = [ + name + for name in run_utils._REQUIRED_SLIDES_NODE_PACKAGES + if not (repo / "node_modules" / name).exists() + ] + if missing: + raise RuntimeError(f"bootstrap left required Slides npm modules missing: {missing}") + + browsers = repo / ".playwright-browsers" + browser_prefixes = {path.name.split("-")[0] for path in browsers.iterdir()} + if {"chromium", "chromium_headless_shell"} - browser_prefixes: + raise RuntimeError(f"bootstrap left required Node Playwright browser assets missing: {sorted(browser_prefixes)}") + + for call in calls: + if call.get("cwd") != str(repo): + raise RuntimeError(f"bootstrap ran Node setup from wrong cwd: {calls}") + + env = calls[1].get("env") + if not isinstance(env, dict): + raise RuntimeError(f"bootstrap did not pass an environment to Playwright: {calls[1]}") + if env.get("PLAYWRIGHT_BROWSERS_PATH") != str(repo / ".playwright-browsers"): + raise RuntimeError(f"bootstrap set wrong Playwright browser path: {env.get('PLAYWRIGHT_BROWSERS_PATH')}") + + sink = io.StringIO() + with ( + patch.object(run_utils.shutil, "which", lambda _name: None), + patch("sys.stdout", sink), + ): + if run_utils._ensure_node_playwright_browsers(repo): + raise RuntimeError("bootstrap reported successful Node Playwright setup without npx") + if "npx was not found" not in sink.getvalue(): + raise RuntimeError("bootstrap did not warn when npx was unavailable") + + def fail_run(_cmd: list[str], **_kwargs: object) -> types.SimpleNamespace: + return types.SimpleNamespace(returncode=7) + + (repo / "node_modules" / "sharp").rmdir() + sink = io.StringIO() + with ( + patch.object(run_utils.subprocess, "run", fail_run), + patch.object( + run_utils.shutil, + "which", + lambda name: "/usr/local/bin/npx" if name == "npx" else None, + ), + patch("sys.stdout", sink), + ): + if run_utils._ensure_node_dependencies(repo, "npm"): + raise RuntimeError("bootstrap reported successful Node setup after command failures") + if "OpenSwarm will continue" not in sink.getvalue(): + raise RuntimeError("bootstrap did not continue visibly after failed Node setup") + + invoked: list[tuple[Path, str]] = [] + + def which(name: str) -> str | None: + if name == "npm": + return "npm" + if name in {"soffice", "soffice.com", "pdftoppm"}: + return f"/usr/bin/{name}" + return None + + replacements = { + "dotenv": module("dotenv"), + "rich": module("rich"), + "questionary": module("questionary"), + "agency_swarm": module("agency_swarm"), + } + with ( + swapped_modules(replacements), + patch.object(run_utils.shutil, "which", which), + patch.object(run_utils.subprocess, "check_call", lambda *_args, **_kwargs: None), + patch.object(run_utils, "_ensure_node_dependencies", lambda repo, npm: invoked.append((repo, npm))), + patch.dict(os.environ, {"AGENTSWARM_BIN": "test-bin"}), + ): + run_utils._bootstrap() + + if invoked != [(ROOT, "npm")]: + raise RuntimeError(f"bootstrap did not invoke Node dependency setup for package.json: {invoked}") + + def main() -> int: smoke_swarm_import_skips_bootstrap() smoke_onboard_env_writes() + smoke_bootstrap_node_setup_installs_slides_dependencies() print("OpenSwarm import bootstrap and onboarding smoke passed") return 0